ADR-230: A Function Field Is Reached Through What Is Stored in It

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)

1. Context

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`

2. Decisions

D1 — A function field is a node of the lock column, over everything the package stores in it.

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.

D2 — 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.

D3 — A function field that may pause, called inside a lambda, is refused as a pausing function is.

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.

D4 — A lambda kept in a function field takes its own handle of a hull it uses.

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.

3. Where it does not reach

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.

4. Consequences

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.

This site is open source. Improve this page.