Classes
Classes
[public] class Counter {
count: i64
label: string
/// A counter that starts at zero.
constructor(label: string) {
this.count = constant<i64>(0)
this.label = label
}
fn size() => number {
return new number(danger_cast<f64>(this.count))
}
}Fields
Fields are declared without let and always state their type. The constructor
has to initialize every one of them.
Constructors
constructor runs at new:
let counter = new Counter("requests")A class can declare more than one constructor when the parameter lists differ.
Methods and this
Methods are declared with fn inside the class body and reach the instance
through this.
A method that assigns to an attribute of this is a mutating method. The
compiler works this out on its own, so [mutates] is inferred and does not have
to be written. Writing it is allowed when the intent is worth stating outright:
class Holder {
value: string?
constructor() {
this.value = None
}
[mutates] fn set(next: string) {
this.value = next
}
}Inference also follows calls: a method that calls a mutating method on this is
itself mutating. This matters because mutation is what drives the state system,
which the state page covers.
Inheritance
from declares a base class:
[public] class JsonString from JsonValue {
// ...
}A subclass inherits the base's fields and methods. Inheritance is single: a class has at most one base, though it can implement any number of traits.
Every class ultimately inherits from the root object type, which is where
to_string, hash, and on_state_changed come from. That is why any value can
be printed or used as a map key without the class doing anything.
Calling the base constructor
A subclass constructor calls the base constructor with => after its parameter
list, before the body:
[public] class JsonString from JsonValue {
text: string
constructor(text: string) => super() {
this.text = text
}
}Arguments pass through to the base:
constructor(name: string, size: number) => super(name) {
this.size = size
}The call belongs to the declaration rather than the body, so it runs before the
body does and the base is fully initialized by the time the body starts. A class
with no base cannot use it: a super call without a parent class is an error.
super in methods
super also refers to the base class from inside a subclass method. It is what lets an
override extend the base behavior instead of replacing it:
[public] class JsonString from JsonValue {
fn to_string() => string {
return `"${super.to_string()}"`
}
}Calling super.to_string() runs the base implementation of that method rather
than the override, which would otherwise call itself.
super reaches any inherited member, not only the one being overridden:
class Document from Node {
fn describe() => string {
return `${super.name()} (${super.kind()})`
}
}The distinction from this matters when a method is overridden. this.method()
dispatches to the most derived implementation, so inside an override it calls the
override again. super.method() always means the base's version, one level up
the chain.
Where a base method is being wrapped rather than replaced, calling super first
and then adding to the result is the usual shape, since the base half of the work
still has to happen.
Implementing traits
impl lists the traits a class satisfies, separated by commas:
[public] class Array<T> impl Index<number, T>, IndexRef<number, T> {
// ...
}A class may both inherit and implement, with from first.