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:
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.
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:
- Elements are zeroed, not field-initialized — a fresh
element of
Rothasc == 0, not the initializer’s identity rotation. Same rule in both containers. - Out-of-bounds traps via
unreachablewith no message, where stdlib arrays throw a RangeError. sorton a struct array needs an explicit comparator — there is no default ordering for a struct.- Growing through
lengthassignment zero-fills the new tail; shrinking just truncates. - Members that hand out interior pointers (
buffer,dataStart) stay fenced — the backing store may move on growth.
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.
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.
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.