ADR-236: A Cleanup Point That Moves With a Contract Is Narrated at the Call

Status: Superseded by ADR-297 — its D1-D2 are D12-D13 there Date: September 27, 2026 Target Version: Nikaia 0.0.222 Answers: #186, the cleanup point the ledger should narrate — ADR-094 D5, the only part of that record left Related: ADR-101 D1 (NK2402, the same narration for throws), Part III 13.5 (the ledger diff)

1. Context

A callee that keeps what it is given runs its cleanup when it is done with it; one that only reads it is handed a view, and the cleanup runs at the end of the caller’s block. ADR-094 D5 accepted that the call site shows neither, because the declaration does and the ledger records it — and it owed, in return, a narration: a change to a parameter’s keeps, on a type whose teardown has an effect, is a ledger diff that names the callers whose cleanup moved.

Nothing narrated it. Building the test for it found a second thing: a parameter put into a list literal — let held: Vec[Handle] = [h] — was not counted as kept, so the callee was declared to take &Handle and rustc refused the list.

2. Decisions

D1 — NK2403, a warning at each call that hands the callee a name.

The build compares the committed nikaia.contracts with the one it inferred, as it does for throws (ADR-101 D1). A parameter the callee keeps now and did not, or kept and only reads now, is a moved cleanup point where its type’s teardown does something: an impl Drop or an impl Cleanup in any ledger the build reads, on the type or — through its fields — on a type it holds. Each call in the package that hands that parameter a name is told, once:

warning[NK2403]: `look` keeps `h` now, so `h`'s cleanup runs when `look` is done
                 with it rather than at the end of this block

A warning and not a refusal, for NK2402’s reason: the program is correct either way, and committing the ledger diff is the acknowledgement — after it the note is not given again. A temporary is not narrated: its cleanup was at the end of its statement, and where inside a call that is, nobody can observe. A parameter the old signature does not have is new rather than moved.

D2 — A list or a tuple literal keeps what it is given.

The clause a struct literal already had, one shape over: [h] and (h, 1) build a value that holds h, so a parameter standing there is kept.

3. What it does not reach

A call into another package whose keeps moved is not narrated: that package’s own build rewrites its ledger before this one reads it (ADR-100 D5), so the consumer has no committed contract of the callee to compare with — the same limit NK2402 has.

4. What is built

All of it, in 0.0.222: check::Newly (what moved, throws and keeps), project::keeps_that_moved, the checker’s a_cleanup_that_moved and tears_down, the keeps clause, crates/nikaia/tests/cleanup_moves.rs and project.rs’s a_moved_cleanup_point_is_narrated_once.

This site is open source. Improve this page.