access Reads and update Writes, and the One Block That Changes a Value Says mutStatus: Superseded by ADR-281 — its D1 is D11 there, and D2 and D3 are D12
Supersedes: ADR-039 D10’s access row — “in place, large
values”, whose block mutated through its parameter
Related: ADR-057 (what Locked[T] is; this is what a program
writes to reach one), ADR-049 (the one lambda form this keeps),
Part I 5.3 (that form), Part II 12.2 (the four doors)
ADR-039 D10 gave locked data four doors, and two take a lambda.
update is settled by its own row — handed the old value, returns the new one.
access was not: its block mutates through its parameter (to.balance += 100),
and a lambda’s parameter has no spelling that says it may be. The open question
was which spelling to add, or whether to let mutability arrive invisibly from the
callee’s contract the way a parameter’s type already does.
Two facts, looked up rather than assumed, decide it.
mut is unbroken. Everywhere a method changes
a value, the binding says so — let mut names before names.sort(),
let mut rows before rows.push(…). Every program in this repository.mut at all, and the grammar has exactly one place
where a callee may change what it was handed: a method’s receiver. That one is
spelled — &mut self.So an invisible answer would be the first place in this language where something is changed and the line does not say so, and the visible answer means a second lambda form, which is what ADR-049 was written to protect.
And the question turned out to rest on a cost that is not there. access’s
whole justification is that copying is too expensive — “a list of ten thousand
entries is not copied to append one”. Run:
fn grow(old: Vec[i64]) -> Vec[i64] {
let mut v = old
v.push(4)
return v
}
fn grow(old: Vec<i64>) -> Vec<i64> {
let mut v = old;
v.push(4);
v
}
That is a move: three words, whether the list holds ten entries or ten
thousand. update can already append without copying anything, in ordinary
Nikaia, with the mutation spelled by the word this language already has, in the
place it already has it. The door that needed a new spelling was earning nothing
that the door without one could not.
What update cannot do is read part of a large value: it must return the new
state, and get() copies. Asking a shared list for its length would copy the
list.
access reads in place and may not change what it is handed.kasse.access fn(state) { state.len() }
It keeps its name and its purpose — in place, large values — and loses the mutation. What it hands the lambda is the value where it lies, to read; what it returns is what the lambda computed.
This is the one thing neither other door does, and it is why access stays
rather than going: without it, asking a ten-thousand-entry list for its length
copies the list.
update is where locked data changes, and its block says mut.kasse.update fn(mut v) { v += 100 }
kasse.update fn(mut v) { v.push(entry) }
The block takes the value as mut v, changes it and returns nothing
(ADR-110 D1). Whether v is a copy or the address of the value
in the lock is the compiler’s, by type, and is what decides whether the block
may be retried instead of locked (D2 and D3 of that record).
mut.The word is ADR-094 D3’s, with its meaning unchanged: a
parameter written mut is changed in place, and the caller’s value — here the
lock’s — is what changes. Part I 5.3’s one lambda form is untouched, and a
reader sees a change where it happens.
One row of Part II 12.2’s table changes, and the sentence naming the doors
that require a sync lambda keeps all of them: access still runs user code
while the lock is open, so it still may not pause, and reading is no weaker a
reason than writing.
get and set are untouched, including the reason they need no condition at
all: while the lock is open in either, no code of the user’s runs.
Two accesses to one lock keep their order, and access moves sides.
ADR-039 D10 reasoned that get is a read and set, update and
access are writes, so that two gets are unordered under
ADR-033 D2 and the three writing forms stay ordered. access is a
read now, so two accesses of one lock are unordered too, and the writing
forms are set and update.
What it costs: a program that changed a large value in place writes update
and the word mut. The word is the change, said out loud.
access_all names several at once,
and the same reading applies to it — it reads. update writes one. So
Chapter 12’s own transfer between two accounts, which takes from one and gives
to the other under both locks, has no door after this record. That is a
consequence of D1 rather than an oversight of it, and it is left open rather
than answered in passing: what such a door is called, and whether it hands a
value back per lock, is a decision and not a footnote. Part II 12.2 carries a
status note saying so, and #245 carries the entry.update takes, and whether it is retried. That is
ADR-110 D2 and D3, decided by type and never by the program.All three decisions, on top of ADR-057’s two shapes.
nikaia_std::lock carries all four doors on both shapes: get, set, access
taking &T, and update — still taking T and returning one, with the
value in an Option<T> so that it can be moved out and back; D2’s mut form,
and the copy-or-address choice behind it, are ADR-110’s and are
listed in that record’s §5. std.contracts describes all four, each
sync = "from(f)" where it takes a lambda.