# proposals features i would like to see in zc, written after working through the compiler, the analyzer, the language server and the libraries. each one says what it is, why it would help, how it would sit on the machinery that already exists, and what it costs. they are ordered roughly by how much day-to-day pain they would remove. none of them is implemented. ## 1. source positions at run time a program that reads far out of range today can exit with status 0 and no message. `int f(word p) { return p[100000000]; }` called with 0 does exactly that. when something does go wrong at run time there is no line number, no function name and no backtrace, so the only tools are print statements and `memdebug`. the compiler already knows the position of every token (`tpack`), and check mode already turns positions into diagnostics. the proposal is a line table emitted next to each function: pairs of (code offset, packed position), written into a side section of the image the way `docs on;` already writes documentation. a fault, a failed `rt.abort`, or a new `rt.trace()` would walk the return stack, map each return address through the table and print `file:line in function` for every frame. the interpreter, the microcore and the javascript vm share the image, so they would all get it from one change to the runtime. the jit and llvm backends would need their own map from native addresses back to vm code; i haven't checked how much of that they keep today. cost: image size (a few words per statement; it could be gated by a `lines on;` switch like `docs on;`) and a little work in the fault path. the compiler's own genome could ship without it. ## 2. checked indexing in debug builds every heap block has a header that knows its payload size, and growable arrays know their length. a `bounds on;` switch, in the spirit of `memdebug on;`, could make `p[i]` on a pointer into the heap check `0 <= i < payload` and stop with the line from proposal 1. typed arrays (`T[n]`, `T[?]`) would check against their declared or current length. the analyzer already knows when an index is a constant inside a fixed array, so those checks can be dropped at compile time, and o3 regions would skip them as they skip other metadata. cost: one compare and branch per index when on, nothing when off. the enhancement system could make it a single opcode on the cores that matter. ## 3. a `drop` method for resources that are not memory the ownership analyzer already decides exactly where an owned value dies and emits `rt.free` or `rt.recycle` there. file descriptors, actor processes and arena handles die at the same points but nothing closes them. a struct could declare `fn drop() { ... }`, and the compiler would call it wherever it would have freed an owned value of that type, before the memory goes back. `actor.zc` (which closes processes by hand) and `std.Host` would be the first users. this keeps the language free of a `defer` keyword: the place where cleanup runs is already computed, and the analyzer would refuse the same leaks it refuses for memory. the open question is moves into unchecked code, which would have to either forbid owned values with a `drop` or document that unchecked code takes responsibility. ## 4. returning a borrow tied to an argument `find` in a string or `first` in a list wants to hand back a pointer into its argument. today that result is either a plain word (unchecked) or an owned copy. the lend rules added to the analyzer already track that a borrow came from a particular owner inside one function. extending that to signatures, ``` char*.from(s) skip_space(char*.m s); ``` would let the caller's analyzer treat the result as a borrow of whatever was passed as `s`, so freeing or moving `s` while the result is live becomes an error at the call site. the callee is checked to return only borrows of `s`. cost: a new tag form in the type grammar and one more case in `argcheck` and `retcheck`. no run-time cost. ## 5. test blocks ``` test "itoa handles negatives" { char[24] buf; expect(std.Text.eq(std.Text.itoa(-5, buf), "-5")); } ``` `zy --test file.zc` would compile the file with its `test` blocks, run each one, and print a line per test with the position of the first failed `expect`. without `--test` the blocks are skipped at parse time and cost nothing. the actor library already shows how a library can add a keyword with `run.syntax` and `run.statement_hook`, so this could live in a `test` module rather than in the compiler. the test suite's many small programs with expected output strings in `tests/test.sh` could gradually move into the files they test. ## 6. formatted output as a library syntax printing a value with a label today is a chain of `rt.put_s`, `rt.put_n` and `rt.put_c(10)`. a parse word in std could expand ``` std.say("x = {x}, name = {name}\n"); ``` at compile time into that chain, using the type of each name to pick `put_n`, `put_s`, `put_bs` or the float printer. nothing happens at run time that the hand-written chain doesn't already do, and the compile-time code is the kind `lang.zc` already demonstrates. ## 7. a parity mode between zc0 and the self-hosted compiler while testing the module scanner i found that the self-hosted compiler bound `||` tighter than `&&`, while zc0 and the readme follow c. the bootstrap fixpoint can't see this kind of bug: g1 and g2 are both compiled by the same wrong table, so they match. a `make parity` target could compile every program in the zc0 subset (the `tests/programs` that zc0 accepts, and the genome sources themselves) with both compilers and run both images, comparing output. a mismatch means the two front ends disagree about the language. the corpus is already there; only the runner is missing. ## 8. untyped const at the top level and in structs `const k = 5;` now works at the top level and inside functions. struct-level constants (`struct Text { const MAX = 24; }`, reached as `std.Text.MAX`) would let libraries keep their limits next to the functions that use them, instead of in the module's global namespace. ## 9. a typo pass over field and method names the missing-name diagnostics now suggest the closest function or module export. field access (`p.lenght`) and static calls (`std.Text.itao`) still only say that the name is wrong. the same edit-distance search over the struct's fields and methods would give "did you mean `length`?", and the language server's change-to quick fix would apply to it unchanged.