The object model
The object model
Every class with no declared parent inherits Object, so a handful of methods
exist on every value in the language.
[public] class Object impl Hash {
[public] fn to_string() => string
[public] fn to_number() => number?
[public] fn to_bool() => bool?
[public] fn to_char() => char?
[public] fn on_state_changed(name: string)
[public] fn hash() => number
}Three things follow from this.
The conversions return optionals. to_number, to_bool, and to_char
default to None, because not every value has a meaningful rendering. Only
to_string returns a plain value, defaulting to the class name.
Everything is hashable, but not usefully. Object implements Hash with a
constant zero. That means any class satisfies an impl Hash bound without doing
anything, and a class used as a Map key without overriding hash() puts every
entry in one bucket. Override it for a key type.
Object does not implement Equals. Unlike Hash, equality has no default,
so a key type has to define it. See the Map keys page.
on_state_changed
on_state_changed is the hook behind the [state] modifier. Override it to
react when the object's own data changes:
class Document {
[state] title: string
constructor() {
this.title = ""
}
fn on_state_changed(name: string) {
io::println(`${name} changed`)
}
}It fires on assignment to a [state] attribute and on a call to a method that
mutates one. It does not fire inside a constructor, since the object is not
finished being built.
Box
Generics are erased, which means a type parameter is only usable through its
bounds and a raw machine value cannot be carried in one directly. Box<T> is the
way around that:
[public] class Box<T> {
constructor(value: T)
[public] fn get() => T
[mutates] fn set(value: T)
}Box is the one generic the compiler specializes per type, so it can hold a raw
FFI value where an erased generic cannot: a machine scalar, an opaque handle, a
pointer<T>.
let handles: Array<Box<opaque>> = new Array<Box<opaque>>()
handles.push(new Box<opaque>(native_handle))
let first: opaque = handles[0].get()Reach for it when you want a collection of raw values, or when a generic API needs to carry something the collector does not manage. For ordinary objects it is unnecessary, since those pass through generics unchanged.
The value types
number, bool, char, and string are ordinary classes wrapping a raw
machine scalar. That is why 1 + 2 routes through a trait method while raw
scalar arithmetic lowers straight to machine code.
| Class | Wraps | Notes |
|---|---|---|
number |
f64 |
The general numeric type. to_string gives the shortest round-tripping decimal. |
bool |
i1 |
|
char |
i8 |
One byte. The is_* tests are ASCII. |
string |
pointer<i8> + i64 |
Byte-indexed, not character-indexed. |
Boxing is one-directional: a raw scalar is promoted to its wrapper
automatically, but a wrapper is never silently treated as a raw scalar. Unbox
explicitly with .to_raw().