structs & refs

Value Arrays

Arrays of structs store their elements inline rather than as pointers — and FixedArray embeds a fixed-size array anywhere a value can live.

Examples on this page import this shared module — it is live too:

vec2.as
export struct Vec2 {
    x: f32 = 0;
    y: f32 = 0;
    constructor(x: f32, y: f32) {
        this.x = x;
        this.y = y;
    }
}

Arrays of values

Both array containers store value-class elements flat, with copy semantics at every boundary. StaticArray<V> is fixed-length: new StaticArray<V>(n) allocates n × flatStride bytes and length derives from the stride. Array<V> is growable: sized construction, element places, length reads and assignment, array literals, push/pop, and for..of are compiler-emitted with the flat stride — an eight-element reserve doubles through growth, and the backing buffer survives collection. The members that size by shift in the stdlib (fill, slice, sort, …) come from a stride-generic lib layer instead, so they work on any struct element type.

value_array.as
import { Vec2 } from "./vec2";

export function demo(): f32 {
    let arr = new Array<Vec2>(2);   // zero-filled; needs a runtime
    arr[0] = Vec2(3, 4);            // element place: leaf stores
    arr[1].y += 1;                  // chains through elements resolve statically
    arr.push(Vec2(5, 6));           // grows with the flat stride
    let v = arr.pop();              // copies out
    return arr[0].x + arr[1].y + v.y;   // 3 + 1 + 6
}

Deliberate deviations from reference-class arrays:

Map stores struct values flat in its entries: set copies in, get copies out, and values() iterates by copy. Struct keys (and Set elements) work through key identity — see Keys & Equality.

three births, one rule

V() runs field initializers. Uninitialized locals (let v: V;) and fresh array elements are zeros. Consistent with C — but if a type’s zero state is not meaningful (like a rotation), initialize elements explicitly.

Fixed arrays

FixedArray<T, N> is a first-class fixed-size value type: N elements embedded flat wherever it lives — a struct field, a local, a param, a return, another FixedArray. No separate allocation, no header, no indirection; length is the constant N. This is C’s b2Vec2 points[2] member, expressible directly.

fixed_array.as
import { Vec2 } from "./vec2";

struct Manifold {
    points: FixedArray<Vec2, 2>;
    count: i32 = 0;
}

export function demo(): f32 {
    let m = Manifold();
    m.points[0] = Vec2(3, 1);          // element place: leaf stores
    m.points[1].y += 2;                // compound leaf chains work
    let c = m;                         // the owner copies with its elements
    c.points[0].x = 100;               // m.points[0] is untouched
    let row = m.points;                // a FixedArray is a value: whole copies work

    let sum: f32 = 0;
    for (let i = 0; i < m.points.length; i++) {   // length is the constant
        sum += m.points[i].x + row[i].y;
    }
    return sum;                        // 3 + 1 + 0 + 2
}

Elements are structs or primitives (FixedArray<f32, 4>); nesting composes (FixedArray<FixedArray<f32, 2>, 3>). Element access goes through the place machinery — constant indexes fold into the offset, a runtime index on a memory-backed owner costs one bounds check per chain level (arr[i].points[j] composes), and a runtime index over a flattened local dispatches through a bounded compare chain of the element leaves. Lengths are pinned to 1–255 and the flattened fan-out to 1024 leaves — past that the engine’s frame limits would refuse the module, so the compiler refuses first. Fresh elements are zeroed, not field-initialized, like array elements everywhere else.