10. Classes
10.1 Declaration and construction#
class Document:
let title: str
var dirty: bool = false
var pages: list[Page] = []
func init(self, title: str):
self.title = title
func append(self, page: Page):
self.pages.append(page)
self.dirty = true
let document = Document("Notes")A class is a shared object: assignment and passing share it, and it lives while anything refers to it. Construction is Name(args), the same spelling as a struct; the declaration, not the use, says which is which. A class has one init(self, ...), which assigns every field without a default exactly once before it ends and cannot publish self before that; init may be fallible, -> unit!, when construction can fail. Name(args) then propagates in a fallible caller or is handled with catch. Like any method, init is private to its module unless pub, so another module constructs a class or struct with its own init only when that init is pub (§15.2). A class without an init and with defaults for every field is constructed with no arguments. Classes are final: no inheritance, no override, no base class. Alternative construction is a type function returning the class, Document.from_file(path).
10.2 Identity and mutation#
let first = Document("Draft")
let second = first
second.dirty = true
assert(first.dirty)
assert(first is second)is and is not compare identity. == is not defined for a class unless it declares Equatable. A let field is assigned in init and never again; a var field is assigned through any binding of the object.
10.3 Lifetime#
Nothing in the language allocates, retains, releases or frees; the compiler and the runtime do. An object is destroyed when the last reference to it goes away, deterministically: a binding releases its reference when it leaves its scope or is reassigned, a field when it is reassigned or its owner is destroyed, and an object that no binding took, the result of a call or a construction inside an expression, at the end of the statement that produced it. A cycle of objects that nothing else refers to is reclaimed by the runtime's cycle collector, which runs at the end of the program and periodically before; the runtime states the exact order. A class may declare deinit(self), run once at destruction, taking no arguments, returning unit, unable to fail, spawn, or publish self. Fields are then released in reverse declaration order. A class that holds a resource also offers close(), so that with can close it on time; deinit is the safety net.
10.4 Weak references#
Weak[T] is a library type holding a non-owning reference to a class instance: Weak(object) makes one and .get() yields T?, none once the object is gone. A Weak is a value: copying it copies the reference, and it has no display and no equality. It is for observer lists and caches. An ordinary back edge, a child's parent, may be a plain field; the collector handles the cycle.
10.5 Display#
Every scalar, str, bytes, tuple, struct and enum of displayable members, optional, and collection of displayable elements has a display used by print, str(x) and f-strings. A class or struct may declare its own by conforming to Display (§13.3), and then displays through it everywhere. print(x) writes the display and a newline to standard output; print(a, b) separates with a space.
The structural display of a struct shows every field, so it is available only where every field is visible (§15.2): in the struct's own module, or anywhere when all its fields are pub. Elsewhere a struct with a private field has no display, and neither has anything that holds one: a collection, an optional, a tuple, an enum payload or another struct. Displaying it there is a compile error; the struct declares Display to be printed outside its module. An enum's payloads are as visible as the enum. Whether a type argument meets a Display bound (§13.2) is decided in the module whose code fixes it, so a generic function or type displays a value only where its caller could. Equality and hashing stay structural: they reveal no field.