Status: Accepted Date: September 15, 2026 Related: ADR-281 D2 (a hull you can see is one you write — the sentence that rules out the other option), ADR-005 D7 (an error explains itself in plain language), ADR-081 (the concatenation on the same line), ADR-views-3 D7 (where the view is the compiler’s)
impl User {
fn name_of(ref self) -> String {
return self.username
}
}
error[E0507]: cannot move out of `self.username` which is behind a shared
reference
about a file nobody wrote. That is every accessor over a field that is not a
number: ref self is what Part I 4.2 writes for a method
that only reads, and a String, a Vec or a struct this program declares is
moved rather than copied in the language below.
A class, because the instance is never the point. A field of a borrowed subject leaks in exactly three positions, and three others work:
return self.name |
E0507 |
let x = self.name |
E0507 |
takes(self.name) |
E0507 |
return self.count, an i64 |
compiles |
return self.name.clone() |
compiles |
return self.name from fn m(self) |
compiles |
NK1131.Part I 6.8 decides this and it decides it already:
An ownership rule refuses a program that breaks it. Every such refusal explains itself in plain language and names the way out. Reading a Nikaia diagnostic requires no knowledge of Rust; a backend error that reaches user code is a defect of the compiler.
So the page does not ask whether this program should be rejected — it says an ownership rule does that — and it says where the rejection has to come from.
.clone() the emitter writes.Changed by ADR-views-3 D7: where the method takes ref self and declares a view as its result, return self.field is lent by the compiler and not refused, so the message’s ways out apply only where a field is moved, bound or passed.
That was the other option and it is against a decision already made. ADR-281 D2 wrote a hull you can see is one you write, and a copy the author cannot see is the same thing one position over: it would put an allocation in the program’s hot path that nothing in the source accounts for, in a chapter whose whole argument is that a value’s cost is visible.
And it would buy nothing, because the working program already exists and is
one word away. Both ways out are in the message: .clone(), written where it
happens, or a self receiver where the method is meant to consume its subject.
A number, a bool, a char and any view are copied rather than moved, so
handing one out takes nothing away. A field this compiler cannot type says
nothing at all, which is Part III C.4’s polarity: a
refusal that fires where it should not is worse than the leak it replaces.
The rule therefore reads the field’s type rather than counting ref selfs,
and the positions are the three in §1 rather than every place an expression can
stand. A position missed lets rustc answer, as it would without this rule; a
position wrongly included breaks a program that works.
Every accessor gets an answer in this language. The message names the field, what was done with it, the type that made it a move, and both ways out.
Part I 4.7 cannot write return "User: " + self.username, and that is
visible rather than hidden: it type-checks (ADR-081) and is
refused by this, correctly. The page keeps the
interpolated form, which borrows.
Reading a field is not taking it, and that is the property the polarity
rests on: self.name.to_uppercase(), self.name.len() and f"{self.name}" all
borrow and all still work.
ref self method
declaring a view result hands back (ADR-views-3 D7). A String
handed to a ref String slot anywhere else is the same refusal a parameter
gets (NK1102) and a let gets (NK1103), so the rule is uniform and this
position is not special.throw are not read here. Each is a
position to add when it is shown to leak, and D3’s polarity says an unshown
one stays out..clone() does to a value that is not text. String::clone is in
the ledger, because §5 says why it has to be; a Vec’s and a declared
struct’s are not, so the help text names a method the ledger describes only
for text.A written .clone() and a self receiver each take something: an allocation,
or the whole subject. A view costs nothing and is what a reader of Part I 6.5
would reach for, so it goes first and it names the type the field actually
has: ref String for text, ref Vec[i64] for a list of numbers.
A message that names a way out has to be a way out. Without a ledger entry
for .clone(), sync::infer cannot vouch for the call: the method around it
comes out as one that pauses, and ADR-080’s NK1129 refuses
it for disagreeing with its trait — take NK1131’s advice and a different
diagnostic fires. So String::clone is in std.contracts.