Status: Accepted
Date: September 26, 2026
Answers: #175’s last item, ADR-281 D33’s
stored lambda: a lambda the callee stores, so that it may run while a lock
is open somewhere this walk cannot see
Refines: ADR-281 D29, whose premise it holds exactly while
the lambda is immediate has an answer where it does not
Related: ADR-277 (a parameter or field that is code),
ADR-312 D5 (handing a hull on duplicates the count),
ADR-055 (a function that can pause is an async fn)
The lock column (contracts::locks) gives touches a lock to every function
that reaches a door, through the calls it makes. A lambda’s body belongs to
the function that writes it, because it runs during the call. A lambda
stored in a function field does not: it runs wherever the field is called,
and a call through a field has no callee the column knew. Three shapes
follow, and without the decisions below none is refused in this language’s words:
struct Button { on_click: fn() sync }
let button = Button { on_click: fn() { let y = n.get() } }
a.access fn(x) { button.on_click() } // accepted: a lock inside a lock (ADR-281 D28)
struct Slow { on_click: fn() } // may pause
a.access fn(x) { slow.on_click() } // rustc: `.await` in a closure
let button = Button { on_click: fn() { a.update fn(mut n) { n += 1 } } }
println(f"{a.get()}") // rustc: use of moved value `a`
Where a struct literal puts a lambda into a function field, the checker
records what that lambda calls: the methods it resolves to, the free calls it
makes, and whether one did not resolve. A named function put there is a call
of it. locks::infer makes each Type.field a node of the same least
fixpoint, and every function that calls through the field reaches that node.
So a field only quiet code is stored in is quiet, and one that a lock-taking
lambda is stored in takes a lock - wherever it is called, and through any
chain of calls.
Where the column cannot read what is stored, the field is undecided, never
quiet: code assigned into the field (button.on_click = …), put there by a
with, or handed in as a value from elsewhere; and a published field of a
published struct, which packages that do not exist yet may store into.
Undecided is ADR-281 D29’s third value: not a refusal, and the runtime check is
where such a program stands, as every undecided one does. The answer is the
package’s own and is not written to the ledger file.
NK2203 asks a call through a function field, as it asks a call of a function.Inside a door’s block, a call through a field whose stored code takes a lock
is NK2203, with its message and its way out - directly, and through a
function that makes the call.
Changed by ADR-277 D11: a lambda handed to a parameter whose type may pause is an async closure or a closure returning a boxed future, and a pausing field call in it is awaited like any pause.
A door’s block is a synchronous closure below, and so is a lambda the lowering
writes as a plain closure. A call through a field whose type may
pause, inside such a lambda, is the refusal a pausing function gets there,
naming the field; written into it, its .await would be rustc’s error.
A kept lambda is one shared closure that owns what it captures. A Shared, a
SharedMut or a Locked it names is a handle, and handing one on duplicates
the count (ADR-312 D5), so the closure is given a clone and the name it was
written beside stays usable. Nothing else it captures is cloned.
A lambda assigned into a field is read as undecided rather than walked; a kept function parameter called inside a door is undecided the same way. Both could be read as a literal is, and neither was needed by a program yet.
Over the corpus the column reads 0 hold, 24 undecided, 35 clear, which is what any future refusal reading the undecided value has to be weighed against.