Step 5: The dot takes the first receiver that fits¶
How to learn ToOwned › Step 5 of 10 · back: Step 4 — Clone hands back Self · next: Step 6 — One blanket impl covers every Clone type
Level: 201 · a step on a learning path
One line: x.method() is a search — the receiver's own type, then & of it, then &mut of it, then dereference and repeat — and the first method whose self type matches wins. That is why .clone() gives a String on a &String and a &str on a &str, and why .to_owned() on a &str needs no dereference at all.
The candidate list¶
For a receiver of type R, the compiler writes out a list: R, then everything R dereferences to, one rung at a time. After each type U in that list it inserts &U and &mut U. Then it walks the list, and at each entry looks for a method whose receiver type is exactly that entry — &self on a str impl counts as receiver type &str. The first entry with a match ends the search.
For r: &String the list is:
&String · &&String · &mut &String · String · &String · &mut String · str · &str · &mut str
Walk it for .clone()¶
r: &String. First entry,&String.String'sclone(&self)has receiver type&String— match. Result: aString. The reference's ownclone, receiver type&&String, sat one entry later and was never reached.name: &str. First entry,&str. Aclonewith receiver type&strwould have to bestr's, andstris notClone(step 4). Second entry,&&str: the reference's ownclone— match. Result: a&str.Clone::clone(&r). No search at all. The argument is a&&String, which fixesSelf = &String, so the result is a&String. Writing the trait's name is how you choose the impl yourself — A trait must be in scope has the three spellings.
Name the trait, and the & you pass decides¶
Clone::clone(x) is fn clone(&self) -> Self called as a plain function. Its one argument has to be a &Self, and with only the trait named, the compiler reads Self straight off the argument's type:
Clone::clone(r)—ris a&String, soSelf = String, and you get a newString: the same functionr.clone()found.Clone::clone(&r)—&ris a&&String, soSelf = &String, and you get the reference, copied.
So naming the trait did not pick the reference's impl. It switched the dot's search off, and then the & you wrote picked it.
Name the type as well — <String as Clone>::clone(…) — and Self is no longer read off the argument; it is fixed. The argument then becomes an ordinary function argument that may be coerced to fit, so <String as Clone>::clone(&r) still hands back a String, the &&String shortened to a &String by deref coercion. Coercion only ever removes a &, though: <&String as Clone>::clone(r) is E0308, expected &&String, found &String (rustc 1.98.0).
That long form is fully qualified syntax, the name The Book uses. Older texts call all of these calls UFCS, universal function call syntax, after RFC 132 ↗. A trait must be in scope has the three spellings side by side, and the case where only the long one works.
Walk it for .to_owned() — and see where a deref happens¶
t: &str. First entry,&str.str'sto_owned(&self)has receiver type&str— match, aString. No dereference happened. And if one had been needed, the search would have reached&&strfirst, where&str's ownto_owned(from the blanket impl, step 6) was waiting, and handed back a&str. Getting aStringis the proof that the first entry matched. Nor was a reference added: there was no autoref either. The&strwent in exactly as it was —s.to_owned()is<str as ToOwned>::to_owned(s), withsitself as the argument, which step 6's output runs both ways.m: &mut str. Nothing fits&mut str,&&mut stror&mut &mut str— a&mut stris notClone. Dereference tostr: nothing by value; then&str:str'sto_owned— match, aString. This is a call where autoderef does the work.tt: &&str. First entry,&&str:&str's ownto_owned— match, a&str. The search stops two rungs before it could reachstr.
Where .clone() goes further — and where it never does¶
r2: &&String. First entry,&&String.&StringisClone, and itsclonetakes exactly a&&String— match, a&String. However many&s you stack,.clone()peels one and stops, because a shared reference is alwaysClone. rustc warns about it,suspicious_double_ref_op;Clone::clone(*r2)is the spelling that reaches theString.mu: &mut String. A&mutis notClone— std says so with a negative impl,impl !Clone for &mut T— so nothing fits&mut String,&&mut Stringor&mut &mut String. Dereference toString: nothing by value; then&String:String::clone— match, a newString. On a&mut,.clone()does reach the value, one deref and one autoref later.r2.capacity(). Nothing namedcapacitytakes a&&String, a&&&Stringor a&mut &&String. Dereference once to&String:String::capacity(&self)takes exactly that — match. This is what autoderef is for: a method the outer type does not have..clone()rarely gets there on a shared reference, because the reference always has acloneof its own.
Checkpoint¶
Predict before you open the answer. What types are name.clone() for name: &str, r.clone() for r: &String, and Clone::clone(&r)? Then Clone::clone(r) without the &, the two fully qualified calls, and the three to_owned calls from the sections above.
The answer
Verified output of the_dot_picks_first.rs — regenerated by tools/run_examples.py, never hand-typed.
Checkpoint. What type does each clone return?
name.clone() name: &str -> &str
r.clone() r: &String -> alloc::string::String
Clone::clone(&r) -> &alloc::string::String
Name only the trait, and the argument's type decides Self
Clone::clone(r) r: &String Self = String -> alloc::string::String
Clone::clone(&r) &r: &&String Self = &String -> &alloc::string::String
Name the type too, and the argument is coerced to fit it
<String as Clone>::clone(&r) Self = String -> alloc::string::String
<&String as Clone>::clone(&r) Self = &String -> &alloc::string::String
The same search, for to_owned
t.to_owned() t: &str -> alloc::string::String
m.to_owned() m: &mut str -> alloc::string::String
tt.to_owned() tt: &&str -> &str
Where the search goes past the first entry, and where it does not
r2.clone() r2: &&String -> &alloc::string::String the inner reference, copied
Clone::clone(*r2) -> alloc::string::String
mu.clone() mu: &mut String -> alloc::string::String &mut is not Clone: deref, then &
r2.capacity() r2: &&String -> usize capacity takes &String: one deref
What this step sets up¶
- Step 6: the blanket impl puts a
to_ownedon every&T, which is one more candidate for this search to find first. - Step 8: on an
Rc<String>or aCow, the outer type is the first entry, and it isClone. - Step 10: at the same entry an inherent method beats a trait method, which is how a
to_ownedof your own wins.
Practice¶
Predict the receiver, then take it away from the dot. With owner: String, r = &owner, rr = &r, s: &str = "Ada", ss = &s, shared = Rc::new(String::from("Ada")) and m: &mut String, write down the type of r.clone(), rr.clone(), ss.to_owned(), shared.clone() and m.clone() before you run anything. Then print each with std::any::type_name_of_val.
Three of the five are not a String. Fix each of those three two ways: once by dereferencing to the rung you want, and once with a fully qualified call that names the type, so the dot never searches at all.
Solution
the_dot_picks_first_kata.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.
//! Kata solution for step 5: predict which receiver the dot settles on, then
//! take the choice away from it — once by dereferencing to the rung you want,
//! once with a fully qualified call that names the type, so nothing is searched.
//!
//! rustc --edition 2024 the_dot_picks_first_kata.rs -o /tmp/tdpfk && /tmp/tdpfk
use std::any::type_name_of_val;
use std::rc::Rc;
fn main() {
let owner = String::from("Ada");
let r: &String = &owner;
let rr: &&String = &r;
let s: &str = "Ada";
let ss: &&str = &s;
let shared: Rc<String> = Rc::new(String::from("Ada"));
let mut scratch = String::from("Ada");
let m: &mut String = &mut scratch;
println!("1. Predict, then check");
println!(" r.clone() r: &String -> {}", type_name_of_val(&r.clone()));
// rustc warns here (`suspicious_double_ref_op`) — the warning is the point.
#[allow(suspicious_double_ref_op)]
let peeled = rr.clone();
println!(" rr.clone() rr: &&String -> {}", type_name_of_val(&peeled));
println!(" ss.to_owned() ss: &&str -> {}", type_name_of_val(&ss.to_owned()));
println!(" shared.clone() shared: Rc -> {}", type_name_of_val(&shared.clone()));
println!(" m.clone() m: &mut String -> {}", type_name_of_val(&m.clone()));
println!();
println!("2. The three that were not a String, fixed by dereferencing to the right rung");
println!(" (*rr).clone() -> {}", type_name_of_val(&(*rr).clone()));
println!(" (*ss).to_owned() -> {}", type_name_of_val(&(*ss).to_owned()));
println!(" (*shared).clone() -> {}", type_name_of_val(&(*shared).clone()));
println!();
println!("3. The same three with no search at all: name the type, and the argument is coerced");
println!(" <String as Clone>::clone(rr) -> {}", type_name_of_val(&<String as Clone>::clone(rr)));
println!(" <str as ToOwned>::to_owned(ss) -> {}", type_name_of_val(&<str as ToOwned>::to_owned(ss)));
println!(" <String as Clone>::clone(&shared) -> {}", type_name_of_val(&<String as Clone>::clone(&shared)));
}
Verified output of the_dot_picks_first_kata.rs — regenerated by tools/run_examples.py, never hand-typed.
1. Predict, then check
r.clone() r: &String -> alloc::string::String
rr.clone() rr: &&String -> &alloc::string::String
ss.to_owned() ss: &&str -> &str
shared.clone() shared: Rc -> alloc::rc::Rc<alloc::string::String>
m.clone() m: &mut String -> alloc::string::String
2. The three that were not a String, fixed by dereferencing to the right rung
(*rr).clone() -> alloc::string::String
(*ss).to_owned() -> alloc::string::String
(*shared).clone() -> alloc::string::String
3. The same three with no search at all: name the type, and the argument is coerced
<String as Clone>::clone(rr) -> alloc::string::String
<str as ToOwned>::to_owned(ss) -> alloc::string::String
<String as Clone>::clone(&shared) -> alloc::string::String
The fully qualified calls accept rr and &shared although neither is a &String: once Self is named, the argument is an ordinary function argument, and deref coercion shortens it — name the trait, and the & you pass decides.
Go deeper¶
Read: Method resolution — the search, it crosses as many pointers as it needs and the trap: an inherent method shadows the target's.
Around it in the library:
- Coercion — method calls are a different rule that looks the same
- A trait must be in scope, the
usethat puts a trait's methods in the search at all - Reading the
Clone for &Thover, the impl the dot lands on for.clone()on a&str, as the editor shows it - "No method named …", what
E0599means when the search finds nothing - Reborrowing, what the
&mut Uentries do to a&mutreceiver Stringvs&str, wheres.len()reachingstr::lenfirst shows up- Returned by value — a common explanation, checked, where "no
str::clonefor auto-deref to find" meets this page's walk
Docs: Method call expressions ↗, in the Reference: the candidate list, in order · The dot operator ↗, in the Rustonomicon · noop_method_call ↗
Words: method and receiver in the glossary.
Kata: Predict the receiver, then take it away from the dot, below.
Po polsku¶
x.metoda() to wyszukiwanie. Kompilator bierze typ odbiorcy, potem kolejne typy po dereferencji, a po każdym typie U wstawia &U i &mut U. Idzie po tej liście i zatrzymuje się na pierwszej metodzie, której typ odbiorcy (self) pasuje dokładnie.
Dla t: &str pierwszy kandydat to &str, a to_owned(&self) z implementacji dla str ma odbiorcę &str — pasuje od razu, wynik to String, bez żadnej dereferencji. Gdyby dereferencja była potrzebna, wyszukiwanie trafiłoby wcześniej na &&str i zwróciło &str. Autodereferencja naprawdę pracuje np. dla m: &mut str. Wywołanie z nazwą cechy pomija wyszukiwanie w ogóle — i wtedy o Self decyduje typ argumentu: Clone::clone(r) daje String, a Clone::clone(&r) daje &String, bo to & wybrało implementację dla referencji. Pełna składnia <String as Clone>::clone(&r) ustala Self z góry, a argument zostaje dopasowany przez deref coercion.
Szukaj po polsku: wyszukiwanie metod w Ruscie · rust method resolution autoref autoderef · rust clone on &str returns &str
How to learn ToOwned › Step 5 of 10 · back: Step 4 · next: Step 6 — One blanket impl covers every Clone type