Inheritance
A derived struct starts with its base’s exact layout, so it passes anywhere the base is expected — no vtable, no runtime dispatch.
A struct extends exactly one other struct. The base’s leaves become
the derived prefix — same offsets, same order — so a derived value is
a base value with extra leaves after it. That prefix carries everything:
inherited mutating methods take the same address, inherited
readonly methods copy just the prefix, and a derived value passes
wherever the base is expected by slicing — dropping the extra leaves on a copy,
or viewing the prefix in place through a ref param. There is no
vtable and no runtime dispatch: redeclaring a method shadows the base
statically, by the receiver’s declared type, and super names
the base implementation.
export struct Vec2 {
x: f32 = 0;
y: f32 = 0;
constructor(x: f32, y: f32) { this.x = x; this.y = y; }
scale(s: f32): void { this.x *= s; this.y *= s; }
readonly sum(): f32 { return this.x + this.y; }
}
export struct Vec3 extends Vec2 {
z: f32 = 0;
constructor(x: f32, y: f32, z: f32) {
super(x, y); // base factory fills the prefix
this.z = z;
}
scale(s: f32): void { // shadows Vec2.scale, statically
super.scale(s);
this.z *= s;
}
}
struct Weighted extends Vec3 {
w: f32 = 1; // no constructor: inherits Vec3's
}
function planar(v: Vec2): f32 { return v.sum(); }
export function demo(): f32 {
let v = Vec3(1, 2, 3);
v.scale(2); // Vec3.scale: 2, 4, 6
let w = Weighted(1, 1, 1); // inherited ctor, w keeps its default
return planar(v) // slices to the Vec2 prefix: 6
+ v.sum() // inherited readonly: 6
+ w.z + w.w; // 1 + 1
}
Constructors inherit like TS: a struct without its own uses the
base’s, and the derived fields take their initializers after the base
factory runs. super(...) is legal only in a derived
constructor; defaults have already run when the body starts, so
super overwrites the prefix.
The fences, all compile errors: a struct and a reference class never extend
each other, extension cycles refuse, a base field cannot be redeclared, and
override is rejected because shadowing is static — there is
nothing to override. Downcasts refuse in both directions
(Vec2 as Vec3 has no z to invent), and arrays stay
invariant — Array<Vec3> never passes as
Array<Vec2>, the strides differ.
Two things deliberately do not inherit, because the base’s
functions are compiled against the base’s leaf shape:
operators, and the
key-identity pair. a + b on a
derived struct wants its own operator+ rather than silently
slicing both operands, and a derived struct keys a Map only
through its own ==/hash() pair or the derived
integer-leaf identity — if an ancestor declared the pair, the derived struct
must redeclare it, so new fields never silently drop out of equality. Calling
an inherited method explicitly (k.hash()) still works; it reads
the prefix, and says so at the call site.