Ties: checked reference invalidation, and memory placed by the compiler
Decided: option A, all of it (r81, 2026-10-27, by the user). References are checked
against the ties a call kills, every function declares its kills clause, and ordinary code
names no allocator: the compiler places memory. The syntax is T-0107’s, as the user accepted
it: @name for a tie, in for where something points, kills a keyword. Spec: §6.7 (ties),
§14.6 (placement), revising §1.1, §5.2, §6.4, §14 and §14.1, §2.7 and §20; supersedes D-0010
and, in part, D-0047. T-0107 was the plan; the checker is built (r81–r88), and what is left
is split into T-0109–T-0120, listed at the top of T-0107. D-0093 makes the ties complete.
Settled on the way, 2026-10-30 (r88, by the user): every returned reference is tied.
With several view inputs and no in the signature must say which (an error, not a rule
picking self); in (a, b) names several; a reference, slice or str result with no view
input is an error, a raw pointer’s is not; and a struct, tuple or optional holding a view
follows the same rules. A view only through a type parameter stays unchecked.
Settled on the way, 2026-11-02 (r104, by the user): with the allocator parameters gone
(T-0119), a container learns where it was placed from the intrinsic #placement, a
^mut Allocator: in a constructor where the value being made goes, in a function returning
something that owns memory where its result goes, else the heap. It may be read in any
unsafe code, not only std’s, so a program’s own container is placed as std’s are;
freeing into the same allocator and building a copy on the heap are then its author’s to
get right. It cannot be a parameter’s default, which would bring back the caller-filled
#allocator this decision rejects. The speed hand-placed Allocators::Temp gave words
and expr is given up until placement reaches their allocations, and the loss recorded
(T-0119).
Built on that, 2026-11-02 (r105, T-0140, asked for by the user): #placement in a
copy is where the copy goes, as in a constructor, rather than the heap. That is §14.6’s
“a constructor and copy build into wherever self is placed”, which this decision already
holds, made true for copy; a copy is then placed like any other value, and a container
builds its copy from its own #placement, never the original’s allocator.
The question. Should Dazzle check that a reference is never used after the memory it points into has moved or been freed, and, with that check in place, take the choice of allocator out of ordinary code altogether?
Why it is asked. Two holes cause silent memory corruption today, and neither is checked (§1.1, §6.4):
- A reference into storage that a call moves.
let n = counts.get_or_insert(k, 0), thencounts.insert(other, 1)grows the table, andn += 1writes freed memory. The same happens withas_str()beforepush_str, or aforover a list that the loop pushes to. - A value used after its arena is released. Something built in
Allocators::Tempthat outlivesreset()is read from released memory (T-0104).
The regions prototype (T-0104, T-0105) tried to choose allocators automatically without a checker, and each stage’s safety rested on rules found by tests: copies through borrows, elements popped out of a region container. A checker that knows when memory dies would make any placement safe by construction, and would close the first hole whether or not placement is automated.
The proposal (in full, with the scenarios it was checked against, in T-0107):
- Ties. A tie is a span during which some memory is valid. A type declares named ties
for the storage it owns (a
Listhas'itemsand'contents), as public names for private fields. Every reference carries a tie: what it points into. killsclauses. Every function says in its signature which ties it may end:fn push(&mut self, v: T) kills self'items,fn spawn(world: &mut World, e: Enemy) kills world.spawned'items. No clause means it kills nothing. The body is checked against the clause, anddz fixwrites a missing one. Using a reference whose tie a call killed is an error.- No allocator in ordinary code. No allocator parameters, no
#allocator, noAllocators::Tempin user code. The compiler puts each value in the most efficient place it can prove safe: the scope’s region, wherever the caller wants the result, or (later) an arena owned by a container, else the heap. TheAllocatortrait stays as a std internal underunsafe.
What it would re-decide.
- §1.1, where memory safety is a non-goal: reference invalidation becomes checked. Raw pointers
and
unsafestay unchecked. - §6.4, “no lifetime system, by design”. Ties are a lifetime system, but a light one: they never appear on structs that hold references, since D-0003 stays.
- D-0010 and §14.1, the allocator as an explicit parameter, and D-0047,
Allocators::Tempin user code.
What it keeps. D-0003: references are never stored in fields. That is what keeps ties
off reference-holding structs, and the reason this is lighter than Rust’s lifetimes. It also
keeps aliasing: &mut xs[i] beside &xs[j] stays legal, and a call is rejected only when
it can move the memory a live reference points into.
The options.
- A. All of it: ties,
kills, and the compiler choosing every placement. Proposed. - B. Ties and
killsonly, with allocators explicit as today. This closes the first hole and leaves the second; it can be done first and stands on its own. - C. Neither: keep §1.1 and §6.4 as they are, and finish the regions prototype as an optimisation behind a flag.
A is B plus placement, so B is the first step either way. Deciding A means a §21 revision row, superseding D-0010 and D-0047, and revising §1.1, §6.4 and §14.
