Chapter 5. Advanced topics

This chapter will discuss more advanced topics; these features make Nickle as powerful as it is. The semantics of copying and garbage collection, namespaces, exceptions, threading and mutual exclusion, and continuations will all be covered.

5.1. Copy Semantics and Garbage Collection

5.1.1. Copy by value

In Nickle, assignment, argument passing, and definitions--in short everything involving the values of variables--are all by-value. Nickle avoids the weird C-isms, like being by-value except for arrays and strings. Everything is copied. Consider the following example:

> int[*] foo = { 1, 2, 3 };
> int[*] bar = foo;
> foo[2] = 4;
> foo
[3] {1, 2, 4}

What will bar[2] be?

> bar
[3] {1, 2, 3}

Since assignment is by-value, bar has its own values--it is unchanged. Also consider function arguments:

> string s = "hello, world"; 
> (void func(string s) { s = "foobar"; printf("%s\n",s); })(s);

Does s still have its original value, or "foobar"? Since the function was modifying a copy--which was passed by-value--s will be unchanged.

> s
"hello, world"

What if you want to pass something by reference? Nickle has a reference type to accomplish just that. (You could also use pointers, but references are The Right Way. Anyway, pointers may eventually be removed from the language in preference to references.) For example, to reimplement the example above using references:

> string s = "hello, world";
> (void func(&string s) { s = "foobar"; printf("%s\n",s); })(&s);
> s

Notice that s was changed; it was passed as a reference (&string). See the section on Variables for a discussion of references.

5.1.2. Garbage collection

But if all those strings and arrays are copied entirely every time a function is called or an assignment made, won't there be a lot of unused, unreferenceable memory lying around? No. Nickle is fully garbage-collected; when a value no longer has any names, it is freed. This is invisible to the user/programmer, who need not worry about allocation, deallocation, or any other aspects of their assignments and argument passing.

In short, everything is by-value, and Nickle takes care of allocation and deallocation.

5.1.3. Type checking and subtyping

Type checking in Nickle is a combination of compile-time and runtime checking. At compile-time, Nickle will ensure that all assignments, argument passing, and other copying situations are sane, for instance that no strings are being assigned to integers. It will let some errors through if it cannot be sure they are errors. For instance, variables of type 'poly' can hold any type; at compile-time, nothing is ruled out, but this does not mean you can't break typing at run-time.

At runtime, Nickle makes sure all assignments are actually valid. It does so by determining if one type is a subtype of the other, i.e. if the set of all values that can fit in it also fit into the other. As a concrete example:

> int i = 1;
> rational r = i;
> i = r/3;
Unhandled exception "invalid_argument" at <stdin>:8
        "Incompatible types in assignment"

The int can hold the integer value 1 without difficulty, because they are the same type. The rational can accept the same value because integers are a subset of rationals. However, attempting to assign a rational (1/3) to the integer raises an exception. This demonstrates that int is a subtype of rational; conversely, rational is the supertype of int. A variable can take on a value from any of its subtypes, but not from its supertypes--and if the two values do not share a sub/supertype relationship, they will not get pass the compile-time checker.

A similar check occurs with structs. If one struct's elements are a subset of another's, it may take that value. For example,

> typedef struct { int i; string s; } i_and_s;
> typedef struct { int i; } just_i;
> i_and_s is = { i=2, s="hello" };
> just_i i = is;   
> just_i ji = { i=2 };
> is = ji;
Unhandled exception "invalid_argument" at <stdin>:17
        {i = 2}
        "Incompatible types in assignment"

Since just_i is a subtype of i_and_s (it has i but not s), the assignment to i from is worked. However, attempting to assign to is from a just_i failed, because it did not have an s to copy over.

Finally, in assignments of one function to another, the following must be the case:

  • The arguments of the right-side function must be able to be assigned to those of the left-side function. In other words, that on the left must accept a subset of the arguments of that on the right.

  • The return type of the left-side function must be able to be assigned to that of the right-side function. In other words, its value should be usable anywhere that of the one on the right could be used.