Queue generic structs and array lengths, after the core lane and not beside it

This commit is contained in:
Joseph Ferano 2026-09-13 14:29:14 +07:00
parent b5d2b5eabd
commit d66d717716

53
NEXT.md
View File

@ -1,3 +1,56 @@
## Queued, decided by the author: generic structs and generic array lengths
**Sequenced after the core generics lane, not beside it.** Both change `Types.t`, which that lane is inside.
The author's question settled it: Odin has both, and the normal way to write them is together.
`core/math/linalg/extended.odin:488` binds three things in one pattern —
```odin
is_nan_array :: proc(x: $A/[$N]$T) -> (out: [N]bool) where IS_FLOAT(T)
```
`A` is the array type, `N` its length, `T` its element, and `[N]bool` in the *return* type uses `N`, so the result
is an array of the same length as the argument. And `core/container/small_array/small_array.odin:23` is the generic
struct over both, with a predicate over a **value** rather than a type:
```odin
Small_Array :: struct($N: int, $T: typeid) where N >= 0 { ... }
```
### Why they are one project
`Types.Named` is a bare string with no parameters and `Types.Array` is `int64 * t`. Either feature means `Types.t`
gains a parameterised case, and `Types.t` is consumed by `emit.ml`'s layout calculator, `x86.ml`, `render.ml`, the
DWARF path and the map key-pair emitter. **Same price for one as for both**, which is the whole reason to do them
together.
What the spike measured for the length half specifically: `Ast.len` is `Lint | Lname`, so `$n` parses today with no
reader or parser change — the same free ride the type sigil got. Everything after that is the expensive part.
`bind_ty` gains a length-binding case (`Array (n, p)` against `Array (m, a)` binds `n := m`), `mangle_ty` gains a
number, and `array_len` has to answer "a variable" rather than failing — which means the checker's compile-time
constant folding has to accept that a length can stay symbolic until instantiation.
### The motivating case, and it is not `$n` on its own
`$n` alone buys little. `swap!` does not need it — it takes a slice and two indices, and the length is a runtime
field. Nor does a `pop` from a `Vec`. The case that wants both is **a fixed-capacity array with a count and no
allocation**, which is Odin's `Small_Array` and a good fit for a game that refuses to allocate in a frame:
```lisp
(defvar enemies (Small-Array 64 Enemy))
```
`PORTING.md`'s recommendation for the game's state was fixed arrays with counts held in `defvar` globals, kept
deliberately away from `Vec` so nothing is move-only. `(Small-Array $n $t)` is that pattern with a type behind it
instead of two variables kept in step by hand. That is the argument for building this, and it is stronger than
"Odin has it".
### Also worth deciding when it is built
Odin's `where N >= 0` is a predicate over a **value**, not a type. The `where` mechanism just decided here takes
type predicates (`ordered?`, `copyable?`). Whether it also takes value predicates over a length parameter is a
separate question and should be answered deliberately rather than falling out of the implementation.
## To discuss: two decisions parked during the ownership discussion
Both came out of the lane that closed the enum-layout and constant-checking gaps. Its work is sound and committed;