ADR-094: The Caller Writes No & — Whether a Parameter Is Lent or Kept Is the Callee’s, Inferred Into the Ledger

Status: Accepted Date: September 15, 2026 Related: ADR-005 D3 and §3 (the inference this extends, and the clone that is still never inserted), ADR-312 D1 (a borrow is not a clone), ADR-288 (the two spellings sync has, copied here), ADR-281 D2 (a hull you can observe, you write — and why a kept parameter is not one), ADR-023 D8 and ADR-055 D1 (the ? and the .await the compiler already writes off the ledger), ADR-083 (NK1131, which stays)

1. Context

A parameter’s type says whether the callee looks at a value or keeps it, and Nikaia has no overloading, so there is never a second callee for a & at the call to choose between. Whether an argument is lent or handed over is therefore a fact about the callee — derived from its body, true for every caller — of exactly the kind the ledger records: sync is inferred from a body and written down with an asserted spelling beside the inferred one (ADR-288); the ? a failing call carries and the .await a pausing one carries are written by the compiler off the ledger (ADR-023 D8, ADR-055 D1); a method’s receiver is auto-referenced. A caller that repeats the fact is doing the compiler’s bookkeeping, and a caller that forgets it is refused by rustc — use of moved value — about a file nobody wrote, which Part III C.1 calls a bug in this compiler.

Across examples/, every & at a call or a loop head repeated what the callee’s declaration said. And a for e in entries that consumes its list makes entries.len() on the next line type-check clean and fail below.

2. Decisions

D1 — The caller writes no &. Whether an argument is lent or handed over is the callee’s declaration’s, and the compiler writes the reference.

Changed by ADR-184 D1: the borrow an expression writes is spelled ref x, so what is refused before an argument is a written ref.

fs::map(path), serve(db), into.merge(stats). What the argument becomes in the emitted code — &path, &db, stats — is read off the callee’s parameter (D2) and written by the emitter, the way the .await is.

A & written before an argument is refused, with a message that says the callee already decides it. Not accepted-and-ignored: a redundant spelling that parses is a second way to say one thing (ADR-277).

There is no callee without a parameter mode to read: a foreign crate is described before it is called (ADR-290), so the & has nowhere left to stand at a call.

D2 — A parameter written with a plain type is lent; one the body keeps says keep, and the compiler checks it.

Changed by ADR-views-3 D2: a parameter that keeps its argument says keep and a plain parameter is lent; the inference below checks the signature instead of writing it, and the ref T assertion is gone.

Changed by ADR-184 D1: the assertion is written ref T; what it asserts is unchanged.

Keeps means: stores it into something that outlives the call (a field of the subject, a struct or collection handed back), returns it by value, gives it to a task, or passes it to a callee whose own parameter keeps it. Inferred over the call graph with a fixpoint for recursion — ADR-005 D3’s engine, asked one more question — and recorded per parameter in the ledger as keeps, beside what the result points into.

Fail closed. A use the inference cannot resolve — a call nothing describes, a method on a receiver of type ? — counts as keeping. The wrong answer in the other direction emits a &T parameter whose body moves the value, and that is rustc’s error about a file nobody wrote; the wrong answer in this direction is a caller refused with this compiler’s words. Same polarity as sync (ADR-288) and as ADR-010 D1.

The signature says it, the inference checks it. A parameter whose body keeps the value is written keep x: T (keep self for the receiver); the inference above no longer writes the mode, it holds the signature to it: a body that keeps a parameter not marked keep is refused, and a keep the body does not need is warned about (ADR-views-3 D2). There is no ref T assertion left to write: a plain parameter already promises a view. The ledger still records keeps, so a diff shows a signature’s promise change.

Ordinary data is still moved where it is handed on — to a callee that keeps it. ADR-312 D1 and ADR-005 §3 stand: no clone is ever inserted, and a caller that hands a value to a keeping callee and uses it again is refused with the clone named as the way out, in the shape NK2101 already has for a task.

D3 — Mutation of a parameter is written in the declaration, never at the call.

fn fill(mut out: Vec[i64]) is a parameter the callee changes in place, and the caller’s value is what changes — &mut self’s rule, for every parameter. It lowers to &mut T. The call, fill(xs), shows nothing, exactly as xs.push(1) shows nothing; a language that hides mutation through a receiver and shows it through an argument has two rules for one thing.

A callee that wants a mutable copy of an owned parameter writes let mut v = x inside, which is what it writes for any other value.

D4 — A for lends, and a let over a place is a view of it.

for x in xs { … } iterates views of xs’s elements and leaves xs where it was; xs.len() on the next line is a program. Iteration that takes the elements away is written — for x in xs.drain() — because removing a name from scope is the rare case and the one worth a word.

let s = totals.stations[name] and let name = config.name are views: the language below refuses to move a value out of a container or out of a borrowed field, so a move there was never what the line meant, and the compiler writes the & it would otherwise have refused. let y = x over a whole variable stays a move — it is a rename — and a let whose initialiser is a call or a literal owns what it is given.

A & before the place in either position is refused as in D1.

D5 — The cleanup point moves with the value, invisibly at the call, and that is accepted and narrated.

This is the one semantic cost, and ADR-281 D2’s line is what it has to answer: a hull you can observe, you write. A callee that keeps a File runs its Cleanup at the end of the callee, not at the end of the caller’s block, and the call site shows nothing.

Accepted, because the language already does this and calls it correct: file.close() takes self (Part III 17.1) and the call shows nothing; a struct literal that stores a handle moves it and shows nothing. What is observable is written in the declaration and read from the ledger, not spelled at every use. The difference from a hull is that a hull changes a value’s type of ownership for the rest of its life, and a kept parameter changes only where one owner ends.

What is owed is the narration: a change to a parameter’s keeps, on a type whose teardown has an effect (Drop written in user code, or Cleanup), is a ledger diff that names the callers whose cleanup moved — the NK2401 shape, one more thing it narrates.

D6 — Modes live in declarations: mut and keep on a parameter or the receiver; views in fields and results are inferred.

Changed by ADR-views-3 D4: ref leaves the language outside extern "C"; a declaration writes the plain type, with mut or keep on a parameter, and views in fields and results are inferred (D5, D8).

Changed by ADR-184 D1 and ADR-251 D4: the view in a declaration is spelled ref T (ref self, ref mut self), and a result says what it points into as -> ref(a | b) T.

A declaration writes the plain type. A parameter or the receiver carries mut when the body changes it and keep when the body keeps it (ADR-views-3 D2, D3); ref stands only in extern "C" (D4). A struct field or element that views a buffer is inferred from what is stored in it (D5), and what a result points into is inferred or written -> T from a | b (D8). NK1102’s and NK1104’s help texts say what the callee’s declaration is, never write a mark about an argument.

D7 — A part taken out of a mut parameter is given back before the function is left, and no call that can fail stands in between.

Widened by ADR-views-1 D1-D3: the same rule holds for a let mut view of a place, given back before the view’s block is left.

A match arm that changes a part it binds takes the part (Part I 3.4), and an assignment gives back what was taken (ADR-293 D31, D32). For a mut parameter, and for the receiver ref mut self, which D3 makes the same thing, what was taken belongs to the caller: the function may take a part out, change it and put it back, but on every way out - its end, a return, a throw - the parameter is whole again. Between the taking and the giving back there is no throw and no call that can fail. Both are NK2108.

So no program ever sees a value with a part missing. A panic in between needs no rule of its own: it ends the task, and what a crashed task left is never read (ADR-327). What stands in the place while the part is out is the lowering’s choice and not observable.

Rejected:

3. Consequences

What a library author gets. A signature is a smaller thing to write and a consumer reads the mode off the ledger, the way they read sync and throws. A callee that starts keeping its argument breaks the callers that used the value again — loudly, with this compiler’s words and the ledger’s narration — which is ADR-005 D3’s action-at-a-distance answered the way it already is for a borrow that lengthens.

What a reader loses. At fill(xs) nothing says xs changes; at keep(file) nothing says the file is flushed inside. D3 and D5 put both in the declaration, and a reader who wants them at the call has the ledger. That is a real cost and it is the same cost method receivers already carry.

What this costs at run time: nothing. A view is a pointer and a move is a copy of a header; the emitted Rust is the signature a person would otherwise write, written by the compiler instead.

4. What this does not decide

5. How it is lowered

One function answers both halves. contracts::keeps::lends is what the emitter reads to write the declaration and what the checker reads to write the argument, because the two disagreeing is a double reference or a moved value in the language below.

What is not lent, each the polarity spent rather than a gap: a parameter the column says is kept; a value that copies, where a reference buys nothing; an argument that is already a view; and a method’s argument — which entry acc.record(m) goes to is the type checker’s answer and the emitter has none (ADR-288), so a call the checker does not walk would hand a value into a reference.

A for lends through .iter(), which reads the same through any number of references; xs.drain() is the written form of taking the elements, lowered as into_iter. A let over a place that copies does not lend, because a reference there is a borrow held across whatever writes the same place.

A written ref before an argument or a for’s list is NK1137, raised after the type fit and never on a type nothing pinned: an argument of the wrong type is NK1102, not a lesson in punctuation.

A mut parameter (D3) lowers to &mut T and the call gains its &mut; keeps::lends withholds its own claim on such a position. A body that changes an owned parameter it did not declare mut is NK1138, raised only where the change is certain.

This site is open source. Improve this page.