structs & refs

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.

inherit.as
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.