RFC-0044: Typed arrays / SoA user types
Status: Accepted (implemented). First-class fixed-length arrays in world
state, generalizing vecN name and raw s[i] indexing into a bounds-checked,
named array type. Shipped surface (this build):
array N name [{ v0, … }] in an entity body (composes with state = (…) in
either order; an entity may declare several array fields);
name[j] read (constant index → static slot; runtime index → ReadSlotDyn);
name[j] = expr, name[j] += expr, and inte name[j] = rate writes;
constant indices are bounds-checked at compile time (detail 52) and a
runtime index (name[k] for non-constant k) is bound-checked at
execution time by the BoundsCheck EIR opcode: a non-finite, fractional, or
out-of-range index is a load-class trap (detail 18) instead of a silent read or
write of an arbitrary State slot. An unknown array name is detail 109 — all
fail loudly instead of silently reading 0.0.
vecN and s[i] are unchanged (s[i] has no declared length and stays
unchecked, exactly as before). A for bound may be len(name) — the
declared length of a named array, a compile-time integer (for j in
0..len(samples)); an unknown array name is detail 109. Conformance:
pwe-conformance "RFC-0044 typed arrays (static + runtime index + len bound,
cross-backend)"; tests
lang::tests::typed_arrays_read_write_and_bounds,
lang::tests::for_bound_can_be_array_len,
lang::tests::array_len_unknown_name_is_detail_109,
typed_array_runtime_index_read_matches_static,
typed_array_constant_index_out_of_range_is_rejected,
typed_array_initializer_arity_is_checked, typed_array_fields_compose_and_allow_several_per_entity,
typed_array_dynamic_write_lands_on_the_target_layout,
typed_array_unknown_array_in_ode_rule_is_rejected,
typed_array_runtime_index_out_of_range_traps,
array_reductions_fold_named_arrays, array_reduction_unknown_array_is_detail_109,
array_dot_needs_two_array_names; EIR:
eir::tests::eir_bounds_check_maps_and_traps.
Deferred (future, see Deferred below): array element unit annotations and a
general scalar len(name). The EntityDecl.arrays map records each array's
length, so these are additive. (len(name) in a for bound is implemented; as
a general scalar expression it is not — it resolves at parse time against the
world's array layout, so it is only meaningful as a bound.)
Motivation
SoA ("structure of arrays") is the storage default (AGENTS.md §9), and the
language already has vecN pos (N consecutive named slots pos.0 … pos.{N-1},
with pos aliasing pos.0) plus a runtime-indexed s[i]. What is missing is a
named, length-aware array so that arr[j] is checked and self-documenting
rather than requiring the author to know the base slot and the length.
Design
Declaration
world {
entity swarm {
state = ( x = 0.0 )
array 8 samples # 8 f64 slots named `samples.0 … samples.7`
}
}
array N name is equivalent to vecN name but records the length N with
the name (EntityDecl.arrays: BTreeMap<String, usize>), which vecN does not.
Access
name[j]— read. Ifjis an integer constant in[0, N)it lowers to the slotname.jdirectly (compile-time); otherwise it lowers to aBoundsCheck(j, base, N)followed by a runtime-indexed readReadSlotDyn(check(j)), whereBoundsCheckyields the absolute slotbase + jor traps (detail 18).name[j] = expr— write, same rule (constant index → static slot write; runtime index → the sameBoundsCheckthenWriteSlotDyn).len(name)— a compile-time integer literalN, usable inforbounds (as a general scalar expression it is deferred; see below).- Array reductions —
sum(a),mean(a),norm(a)(Euclidean),asum(a)(Σ|aᵢ|),prod(a),min_of(a),max_of(a), and the two-arraydot(a, b). The argument is a literal array name (its length is compile-time), so each unrolls to native EIR over the elements — no new opcode, cross-backend, and a kernel primitive for statistics / DSP / linear-algebra rules. An unknown name is detail 109;dotof unequal lengths (or a non-name argument) is rejected (52 / 59).
Lowering
A new Expr::Index(String, Box<Expr>) resolves at lowering against the target
entity's layout (ctx.state_names carries the run name.0 … name.k; the
compile-time check re-derives the length from that run, which equals the N
recorded in EntityDecl.arrays):
- constant index → the static slot
name.j(0≤j<N checked at compile time, detail 52); - dynamic index →
BoundsCheck(index, base, N)thenReadSlotDyn/WriteSlotDynon the checked absolute slot, withbase/Ntaken from the layout of the entity the system targets.
BoundsCheck = 252 (operands index, base, len) is the RFC-0021 load-class
check: it traps (detail 18) when index is not a finite integer in [0, len),
else yields base + index. It is interpreter-only today (not in
native.rs::eligible), so array programs run on the interpreter while the
step_cross cross-check treats the interpreter as the reference.
Diagnostics
- constant index out of range → detail 52 (slot index out of range);
- runtime index out of range / fractional / non-finite → detail 18 (load-class
trap,
BoundsCheck); - unknown array name (no
name.0 … name.krun on the entity) → detail 109.
Validation
- Cross-backend (
step_cross) equality forname[j]static and dynamic forms. - Diagnostics for out-of-range constant indices and unknown array names.
- Runtime out-of-range / fractional indices trap (detail 18), proving no silent read/write of an arbitrary State slot.
- Conformance case exercising an in-range dynamic array read/write on both
backends;
lang::testscovers the OOB trap (a trap aborts the step, so it is not a cross-backend success case).
Deferred
Specified here but not part of this revision; EntityDecl.arrays already
records the length, so each item is additive:
- Bounds checks on a raw runtime
s[i]index (s[i]has no declared length, so it stays unchecked, exactly as before). - Per-element unit annotations.
len(name)outside aforbound (a general scalar expression).
Backward compatibility
Additive: vecN and s[i] continue to work; array/name[j] are new
surface (len is deferred). name[j] with name a plain scalar is a compile
error (detail 109).
Alternatives
- Only
vecN(status quo): works but has no length and no bounds check. - Dynamic/flexible arrays (heap): rejected for the deterministic, fixed ABI/memory profile (AGENTS.md §3, §8); arrays are fixed-length by construction.