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 the n elements
back to back in one block, and length stays n.
Array<V> is growable: sized construction, element places,
length reads and assignment, array literals,
push/pop, and for..of all work on it,
and its capacity starts at eight elements and doubles as it grows. The other
members (fill, slice, sort, …) work on
any struct element type too.
import { Vec2 } from "./vec2";
export function demo(): f32 {
let arr = new Array<Vec2>(2); // zero-filled
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.