Places & Refs
Writing through nested fields and array elements directly — and
ref, a binding that aliases a location instead of copying it.
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;
}
}
export struct Rot {
c: f32 = 1; // identity rotation
s: f32 = 0;
}
export struct Transform {
p: Vec2 = Vec2(0, 0); // structs nest
q: Rot = Rot();
}
Places: writing through chains
Anything that names a memory location — an embedded field, a
StaticArray element, a chain of both — is a place, and
stores through places compile to direct writes at computed addresses:
import { Vec2, Transform } from "./vec2";
class Body {
position: Vec2 = Vec2(); // embedded flat: no pointer, no header
transform: Transform = Transform();
mass: f32 = 1;
}
export function demo(): f32 {
let body = new Body();
body.transform.p.x = 3; // one store at a constant offset
body.transform.p.x += 2; // compound leaf assignment works
let arr = new StaticArray<Transform>(8);
arr[1].p = Vec2(1, 2); // element place: leaf stores
arr[1].q.s += 0.25; // chains through elements resolve statically
return body.transform.p.x + arr[1].p.y + arr[1].q.s; // 5 + 2 + 0.25
}
Reads chain to single loads the same way — body.transform.p.x is
one load at a constant offset. This flat embedding is the payoff shape ports care
about: a Box2D body is one allocation, not a tree of five.
Refs: places as values
A ref is a first-class place: a pointer plus a static element
type, formed from anything that names a location — value locals and params
(taking a ref spills them to a scratch frame so both names alias), struct
fields in objects, StaticArray<V> and
FixedArray elements including runtime indexes, and single leaves
like v.x. Reads copy out, writes go through, and a ref forwards to
other ref params without copying.
import { Vec2 } from "./vec2";
function bump(v: ref Vec2, dx: f32): void {
v.x += dx; // writes the caller's place
}
class Poly {
vertices: StaticArray<Vec2> = new StaticArray<Vec2>(4);
}
export function demo(): f32 {
let poly = new Poly();
ref p = poly.vertices[1]; // binds the element, no copy
p.x += 3; // W3001's hazard, done right
bump(poly.vertices[1], 2); // place straight into a ref param
let v = Vec2(1, 0);
ref q = v; // spills v; q and v alias
q.x += 4;
return poly.vertices[1].x + v.x; // 5 + 5
}
Refs are deliberately second-class: locals and params only. No ref fields,
returns, closures, generators, or arrays of refs — all explicit errors — so a
ref’s lifetime is lexical and the compiler pairs every ref rooted in a
managed object with a hidden root for its scope. One fence to know:
ref p = arr[i] into a growable Array<V> refuses
at any chain depth (“may reallocate; copy or index directly”) —
push can move the buffer. StaticArray and
FixedArray elements never move, so they bind freely.