This library is in flux. APIs may change without notice. Generated from source with koruc 0.1.7 on 11/4/2026.

Grid

import std/grid

Koru Standard Library: Grid — a fixed, positionally-addressed table

grid.kz · 9 tors

Koru Standard Library: Grid — a fixed, positionally-addressed table · 40 more lines
Koru Standard Library: Grid — a fixed, positionally-addressed table A grid is the OTHER kind of table, and the distinction is one question: CAN A ROW MOVE? std/store — yes. `take` swap-removes, so position is not identity, so a row is named by a generational HANDLE and every access pays a brand + bounds + generation check. The store earns that machinery by supporting removal. std/grid — no. Every cell exists from declaration and none is ever inserted, removed or relocated, so POSITION IS IDENTITY. A grid pays nothing: no handles, no generation word, no sparse indirection, no freelist, no `len`. A grid is the degenerate, fastest table — it gets there by forbidding, not optimising. USAGE: import std/grid std/grid:new(cells, size: 4096) { count: 0[i64], sx: 0.0[f32] } std/grid:stored { cells[h].count: cells[h].count + 1 } SIZE, not capacity. `capacity: N` means "up to N, currently `len`". A grid is EXACTLY N cells, all live from the moment it is declared. Different contract, different word, so the two declarations cannot be misread. OUT OF RANGE TRAPS (ruled 2026-08-03). An index outside `0..size` is a panic naming the grid, matching the store's stale-handle floor: fail loud, never silently address a neighbour. Wrapping is the PROGRAM's decision and belongs in the source as an explicit `% size` where a reader can see it — a spatial hash wants exactly that and should say so. The check is a floor, not a tax to be paid forever: an index the compiler can prove in range needs none, and a `! sweep` arm over a grid is precisely that case — the loop constructs the index from `0..size`, so validating it would be validating a fact established one line above. That is the same floor-then-prove-away arc the store's handle check follows. NOT REACTIVE, deliberately — reactivity in Koru is spliced at comptime and costs nothing when nobody listens.

~part read koru_std/grid.read.kz

Koru Standard Library: the read path of grid.kz — the grid_read namespace (refs, parseTarget, lower/lowerCont/lowerItems, trusted index). Joined via `~part read` in grid.kz; merged into the grid module at load.

~part slice koru_std/grid.slice.kz

Koru Standard Library: the `slice` part of grid.kz. Joined via `~part slice` in grid.kz; merged into the grid module at load. std/grid:slice(name) { grid.field } Borrows a column-layout grid's field array as a raw slice: `const name = __koru_grid_<g>.<f>[0..]` lands next to the grid in the module, so `name[i]` reads splice verbatim through generic indexing and `stored { name[i]: v }` writes lower to a direct `name[i] = v` — the write unit threads the slice through as an argument, the way every write threads its target. THE BORROW IS THE CONTRACT: the grid's per-access __koru_at floor is the thing the program is declining — a raw view buys vectorization-shaped code with the program's own index obligation, and Zig's own slice bounds check still runs in Debug. Pools are refused: a pool column is a heap slice whose extent arrives at alloc — the borrow would only shadow what is already raw.

slice

keyworddeclaration koru_std/grid.slice.kz:18

~part readwalk koru_std/grid.readwalk.kz

Koru Standard Library: the continuation-tree walk of grid.kz's read path — lowerCont/lowerContTrusted/lowerItems. Joined via `~part readwalk` in grid.kz; merged into the grid module at load. Split out of grid.read.kz at the read-arm-expr x sweep-trusted-cursor mergeback: the union of both sides' work pushed the file over its line baseline, and the wall does not widen.

~part new koru_std/grid.new.kz

Koru Standard Library: the `new` part of grid.kz. Joined via `~part new` in grid.kz; merged into the grid module at load. GRID.NEW — declare a fixed, positionally-addressed table

new

keywordcomptimetransform koru_std/grid.new.kz:8

~part stored koru_std/grid.stored.kz

Koru Standard Library: the `stored` part of grid.kz. Joined via `~part stored` in grid.kz; merged into the grid module at load. There is no write EVENT and no envelope. The store routes every write through one centralizing subflow because that is where reactivity splices; a grid has nothing to splice, so a write is a write and lowers to an assignment.

stored

keywordcomptimetransform koru_std/grid.stored.kz:8

~part idx koru_std/grid.idx.kz

Koru Standard Library: the `idx` part of grid.kz — index-typed cells. Joined via `~part idx` in grid.kz; merged into the grid module at load. `0[<grid>]` — a field typed by a grid's own name is AN INDEX INTO THAT GRID: `nd: 0[tn]` is "a cell holding an index into tn". The declaration buys three things the bare `[i64]` spelling cannot express: WIDTH — the cell is the narrowest unsigned int covering the target's extent (grid.new emits `__koru_idx_<target>` = IntFittingRange(0, target.__koru_size)), so a `size:` bump re-derives the storage rather than silently overflowing it. CERTIFICATION — the WRITE pays the bounds check once (stored wraps the value in the target's `__koru_at`), so the contents are `< size` by construction. A read of the target through the cell — `tn[que[i].nd]` — is then certified and re-collects no floor. PROVENANCE THAT SURVIVES EMISSION — the const's NAME carries the target (`__koru_idx_tn`), so re-collection (scanLoweredFields) and the decl-site parse (scanFlowFields) recover the same fact from two different surfaces. One encoding, no drift. The arithmetic rule: an index value leaves its type the moment it feeds `+`/`-`/`*` — the emitted cell is a plain int, so `nd + 1` is an int and `tn[nd + 1]` floors again, as it must: the proof was about `nd`, not about arithmetic on it. Storing the result back re-earns the proof through the checked write.

~part sweep koru_std/grid.sweep.kz

Koru Standard Library: the `sweep` part of grid.kz. Joined via `~part sweep` in grid.kz; merged into the grid module at load. keeps the body Koru all the way down — a nested `std/store:stored`, a `print.ln`, a guard, all behave exactly as they do anywhere else, because nothing about them has been moved.

sweep

keywordcomptimetransformclaims_descendants koru_std/grid.sweep.kz:8

~part cursor koru_std/grid.cursor.kz

Koru Standard Library: the minted-cursor convention of grid.kz — the name a grid-owned loop gives the index it has already proven. Joined via `~part cursor` in grid.kz; merged into the grid module at load.

~part for koru_std/grid.for.kz

Koru Standard Library: the `for` part of grid.kz — a bounded loop the GRID owns, so the index's proof is established once, at the door. Joined via `~part for` in grid.kz; merged into the grid module at load. std/grid:for(0..n, cells) ! each i |> ... cells[i] ... An ordinary `for(0..n)` hands the body an index whose bound lives in a VALUE `n`; nothing connects it to the grid it is about to index, so every `cells[i]` pays the `__koru_at` floor — and, more expensively, the panic edge that keeps the vectorizer out of the loop. This construct takes the range AND the grid, checks `lo..hi` against `__koru_size` ONCE before the loop begins, and then mints the binding as a trusted cursor: the same provenance mechanism `sweep` built for its `0..__koru_size` cursor. The panic the check carries is real — a range that outruns the grid fails loud, never clamped, because truncation is the program's to write. The trust is exactly as wide as the proof: the minted name on the named grid. `cells[i+1]` keeps the floor, `other[i]` keeps the floor, and an `i` spelled by a different scope keeps it too — the match is on the raw index text, which is the thing nobody else can spell. The binding is RENAMED to the minted cursor at source level (`i` -> `__koru_gi_<grid>_L<line>`) before the body lowers. That is what admits `stored { cells[i].v: ... }` to the unchecked write path: the write unit names an index trusted when its text is a cursor minted for THAT grid — a name only a grid-owned loop can mint, which is the whole reason for the rename. A body that leaves the binding spelled `i` would read trusted and write checked, an asymmetry with no principle under it.

for

comptimetransformclaims_descendants koru_std/grid.for.kz:31

~part walk koru_std/grid.walk.kz

Koru Standard Library: the `walk` part of grid.kz. Joined via `~part walk` in grid.kz; merged into the grid module at load. std/grid:walk { s: inp.b, 2, d: enc.c, 3 } ! each |> ... s[k] ... stored { d[k]: v } ... | tail |> ... leftover window ... | done |> ... A RESLICING window walk: each labeled arg names a cursor (borrowed off `grid.field` like a `slice` decl) and the positional after it is its stride. The driver emits moving-base cursors and ONE guard at the loop head — `while (s.len >= 2 and d.len >= 3)` — so every `s[k]`/`d[k]` in the body is a raw slice access whose bound was already proven. This is the shape LLVM auto-vectorizes (fixed-offset reads/writes on advancing bases) — the same shape Goose's `s = s[3..]` loops emit — where an indexed `for (0..n) |q| g[4*q]` keeps per-element structure the vectorizer cannot see through. The borrow contract carries over from `std/grid:slice`: the program asked for the raw view, so the per-access __koru_at floor is declined and Zig's own slice checks still run in Debug. `| tail` fires once with whatever remains (`s.len < stride`); the remainder is the program's to write — a codec's partial group lives there, never silently dropped. `| done` chains after the walk, unlowered. Zig only this rung: a JS reslice is `s.slice(n)` — a COPY — and writes through it never reach the grid, which is a silently-wrong lowering no honest surface may ship.

walk

comptimetransformclaims_descendants koru_std/grid.walk.kz:30

~part pool koru_std/grid.pool.kz

Koru Standard Library: the `pool` part of grid.kz — a positionally- addressed table whose EXTENT is a run-time value. Joined via `~part pool` in grid.kz; merged into the grid module at load. std/grid:pool(ops) { o: 0[u8], j: 0[i64] } -- declares the SHAPE std/grid:pool-alloc(ops, src.len) -- decides the extent ... ops[i].j ... stored { ops[i].o: 1 } ... -- reads/writes as usual std/grid:pool-free(ops) -- hands the slices back Why a sibling and not a flag on `new`: a grid's every cell exists from declaration, so `size:` is comptime-only and its storage is static BSS. A kernel that learns its table size from its input over-declares to the worst case and pays the bytes on every run — brainfuck's `ops` grid was four megabytes of i64 for programs a few kilocells long. A pool declares the shape comptime and the extent at the point the program knows it: one heap slice per column and a `var __koru_size` the alloc step sets. Everything else is the grid's own ABI, deliberately: the emitted struct wears the same `__KoruGridT_<name>`/`__koru_grid_<name>` names, so `collect`/`scanLowered` register it in the same namespace and reads, `stored` writes, `sweep`/`for` cursors and `[unsafe(bounds)]` all ride the existing path unchanged. The ONE difference the lowerers see is the `.pool` flag on GridRef: slice columns cannot be `(&x)[i]`-indexed, so pool access names the column without the `&`. The bounds floor is unchanged in spirit and sharper in message: an index outruns `__koru_size`, which is `0` until `pool-alloc` runs — so touching a pool before its extent arrives is the same loud trap as any other index error, never a silent neighbour. Declared before first use, same as `new`. No `size:`/row/`dimensions` facets — the extent is the alloc call's business and the layout is column for now.

pool

keywordcomptimetransform koru_std/grid.pool.kz:72

pool-alloc

comptimetransform koru_std/grid.pool.kz:262

pool-free

comptimetransform koru_std/grid.pool.kz:479