In general, names must be declared before they are used.
The first exception to this rule is that a function local to a module need not have a declaration at all; it is sufficient to give its definition, and that definition can appear anywhere in the module.
The general rule implies that no abstract data type can contain, as a member, an adt
not previously declared (including an instance of itself). A second exception to this rule applies to ref
adt
types. An adt
can contain a member whose type is a ref
to itself, or to another adt
even if the second adt
has not yet been declared. Unless a special notation is used, such references are restricted: all mutual or self references among adt
's are checked statically throughout all the adt
's visible in a module to determine which adt
members refer to other adt
. Any member of an adt
of ref
adt
type that refers directly, or indirectly through a chain of references, back to its own underlying type can not be assigned to individually; it can gain a value only by an assignment to the adt
as a whole.
Tree : adt { l : ref Tree; r : ref Tree; t : ref Ntree; }; Ntree : adt { t : ref Tree; }; t1 := Tree(nil, nil, nil); # OK t2 := Tree(ref t1, ref t1, nil); # OK t1 = Tree(ref t1, ref t2, nil); # OK t1.l =... ; # not OK nt := ref Ntree(nil); # OK nt.t = ... # not OKthe first three assignments are correct, but any assignment to
t1.l
is forbidden, because it is self-referential. The situation is the same with the mutually referential fields of the Tree
and Ntree adt
.
These restrictions suffice to prevent the creation of circular data structures. Limbo implementations guarantee to destroy all data objects not involved in such circularity immediately after they become non-referenced by active tasks, whether because their names go out of scope or because they are assigned new values. This property has visible effect because certain system resources, like windows and file descriptors, can be seen outside the program. In particular, if a reference to such a resource is held only within an adt
, then that resource too is destroyed when the adt
is.
The default rules are burdensome because they impede the construction even of harmless structures like trees. Therefore an escape is provided: using the word cyclic
before the type in an adt
member removes the circular-reference restriction for that member.
Tree : adt { l : cyclic ref Tree; r : cyclic ref Tree; t : ref Ntree; }; Ntree : adt { t : cyclic ref Tree; }; t1 := Tree(nil, nil, nil); # OK t2 := Tree(ref t1, ref t1, nil); # OK t1 = Tree(ref t1, ref t2, nil); t1.l = ... ; # OK now nt := ref Ntree(nil); # OK nt.t = ... # OK nowWith the use of
cyclic
, circular data structures can be created. When they become unreferenced except by themselves, they will be garbage-collected eventually, but not instantly.