Open and answer this issue on GitHub.
compiler, note · opened 2026-10-10 · updated 2026-10-10 · 0 comments
From the decision on #576 / #558 (2026-10-10, ADR-094 D7). Two ways change a tree in Nikaia:
mut parameter: take the parts in a match arm, change them, assign the whole again. ADR-094 D7 allows this, with two conditions: the parameter is whole on every way out, and no throw and no call that can fail stands between taking and giving back (the owner chose this strict form, “A1”).fn wrap(e: E) -> E { return match e { E::Add(a, b) => E::Add(wrap(a), wrap(b)), … } }. This runs today.Below, a recursive enum’s children are Box<E>. The by-value form frees each node’s box and allocates a new one of the same size, once per node. Lean 4 and Koka write the same functional code. Where the old node is no longer referenced, their compilers hand its memory to the new node (“reuse analysis”, Perceus / “functional but in place”), so the program reads functionally and runs in place.
mut parameter and needs no new node at all. That path depends on how #576 lowers it: it should keep the boxes (*e = E::Add(a, b) with the same Boxes) rather than unbox and rebox them. That belongs in #576, not here.free immediately followed by a malloc of the same size class usually returns the same block from the thread cache. What is left is the cost of the two calls, and that is not measured.throws must be written by value. This is the main remaining case. Checker and emitter walks that report errors may be among them once they are Nikaia (#558).fold.nika), text_tiers-style rewrites that build a new tree from an old one, and code moved from Rust that was already written that way.mut or by-value parameter has one owner, which the compiler already knows (ADR-005, ADR-094). So the reuse can be decided statically, with no check left in the program.Vec: Rust’s into_iter().map(..).collect() already reuses the buffer when the element layouts match. The gain is boxed enum nodes, and possibly String.wrap above) and once by the D7 in-place form, both through nikaia run. Count instructions and mispredicted branches with callgrind, against hand-written Rust that reuses the box (CLAUDE.md: count, never time). The same output first.callgrind_annotate: what share of the by-value version is malloc/free.match arm takes a node by value and builds a node of the same variant size, write into the old box), and a record (ADR) for it.Until 1 and 2 are measured this issue stays note.