Method resolution¶
Level: 201 · working knowledge
One line: x.f() is not a lookup on x's type — it is a search that dereferences and borrows until something named f fits, and the first thing it finds wins.
fn main() {
let name = String::from("Ada");
println!("{}", name.len()); // 3 <- str::len, one deref down
println!("{}", (&&&name).len()); // 3 <- same method, three refs peeled off
}
String has no len. str does, and it takes &self — so the call you wrote became str::len(&*name), with both the deref and the borrow inserted. That insertion is what makes the dot operator feel like it works on everything.
The search¶
For x.f() where x: T, the compiler builds a list of candidate receiver types:
- Start with
T. - Dereference repeatedly —
T,*T,**T, … — adding each to the list, followingDerefimpls as well as plain references. - At the very end, try one unsized coercion (
[T; N]→[T]).
Then it walks that list in order, and for each candidate U looks for a method whose receiver is U, then &U, then &mut U. The first match ends the search.
Two things follow, and both are the subject of the rest of this page. Nearer wins: a method one rung up beats an identical one further down, however much better the deeper one fits. And inherent beats trait at the same rung, which is the rule A trait must be in scope is about.
For a Vec<i32> the ladder reads Vec<i32> → [i32], which is why v.iter(), v.first() and v.sort() — all slice methods — answer a call made on a Vec.
The trap: an inherent method shadows the target's¶
Give a type a Deref impl and it inherits the target's whole method surface. Add an inherent method with a name the target already uses, and every call silently changes meaning:
struct Tally(Vec<i32>);
impl Deref for Tally {
type Target = Vec<i32>;
fn deref(&self) -> &Vec<i32> { &self.0 }
}
impl Tally {
fn len(&self) -> String { format!("{} entries", self.0.len()) } // added later
}
Now t.len() is a String. It was a usize before that impl block existed, nothing warns, and every caller that was doing arithmetic on it stops compiling somewhere else entirely.
| Spelling | Reaches | Returns |
|---|---|---|
t.len() |
Tally::len — first rung |
String |
(*t).len() |
Vec::len — deref written out |
usize |
Vec::len(&t) |
Vec::len — named, not searched |
usize |
t.first() |
[i32]::first — nothing shadows it |
Option<&i32> |
Vec::len(&t) works because a function argument is a coercion site, so &Tally coerces to &Vec<i32> on the way in — a different mechanism from the receiver search, doing the same job. See Coercion.
The practical rule for anyone writing a Deref impl: the target's method names are now yours to collide with. This is the main reason Deref is discouraged for anything that is not a smart pointer, and the reason the Book recommends writing Rc::clone(&a) rather than a.clone() — naming the type says which clone you meant instead of leaving it to the ladder.
It crosses as many pointers as it needs¶
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
shared.borrow_mut().push(4); // Rc -> RefCell::borrow_mut, then Vec::push
No * anywhere, and three types involved. This is also where the search's convenience turns into its worst error message: when nothing matches, E0599 reports the method as missing from the original type, with a help: line listing what it tried — see "No method named …" for reading it.
If you are coming from another language¶
- C++.
operator->chaining is the near relative —a->bre-appliesoperator->until it reaches a raw pointer — andDerefis doing the same thing for the dot. The differences are worth holding onto: Rust's search also inserts the borrow (&or&mut), which C++ has no need for; and overload resolution does not exist here, so the ambiguity C++ resolves by argument types is resolved in Rust purely by position on this ladder. Where C++ would report an ambiguous call, Rust silently picks the nearer one. - Python. Attribute lookup walks the MRO and then
__getattr__, which is a similar "keep looking" story with a very different failure mode: Python resolves at call time and tells you at run time, so shadowing a method is something you find in production. Rust's ladder is resolved at compile time, so theTally::lenshadowing above cannot produce a wrong answer — it produces a type error at whoever was using the result. The habit to port is the same though: adding a method to a base class, or an inherent method to aDereftype, is an API change even though nothing was removed. - ABAP. Method resolution is much flatter — a method belongs to a class or an interface, and
me->method( )versusif_x~method( )is the whole disambiguation story, with the interface-alias spelling as the escape hatch. That alias spelling is the closest thing to<Fox as Shout>::shout(&fox). What has no counterpart is the deref half: ABAP references are dereferenced explicitly with->*and nothing chains them for you, so the "which type is this method actually on?" question does not arise.
The verified output¶
Verified output of method_resolution.rs — regenerated by tools/run_examples.py, never hand-typed.
1. The receiver you did not write: `&` is inserted for you
seats.len() -> 3
the method takes &self, so the receiver was `&seats`
2. Deref first, then borrow — `String` has no `len`, `str` does
name.len() -> 3
name.to_uppercase() -> ADA
both live on `str`, one deref down
3. The ladder is walked as far as it goes: Vec -> [i32]
seats.iter().sum() -> 6 (`iter` is a slice method)
seats.first() -> Some(3)
4. Extra references cost nothing — they are peeled off first
(&&&name).len() -> 3
5. The trap: an inherent method on a Deref wrapper shadows the target's
t.len() -> "3 entries" <- Tally::len, and it returns a String
(*t).len() -> 3 <- Vec::len, reached by dereferencing first
Vec::len(&t) -> 3 <- the same method, named instead of found
t.first() -> Some(3) <- unshadowed, so it still reaches the slice
6. The ladder crosses as many smart pointers as it needs
Rc -> RefCell -> Vec -> [1, 2, 3, 4]
See also¶
- A trait must be in scope — the inherent-beats-trait rule, and the three spellings that name a method instead of searching for one
- "No method named …" — what
E0599means when the ladder runs out - Coercion — the argument-side insertion, which is how
Vec::len(&t)compiles - Reborrowing — the receiver the search inserts is a reborrow, not a move
Stringvs&str— the single most-used rung of this ladder
Sources¶
The Reference on method-call expressions ↗, which states the candidate-list construction and the U / &U / &mut U order; std::ops::Deref ↗, whose own docs carry the warning about Deref on non-pointer types.
Po polsku¶
Wywołanie x.f() nie jest zwykłym odczytem metody z typu x. To wyszukiwanie: kompilator buduje listę kandydatów na odbiorcę — najpierw T, potem kolejne wyłuskania (*T, **T, …, podążając też za implementacjami Deref), a na samym końcu jedno rozmiarowanie [T; N] → [T] — po czym przechodzi tę listę po kolei i dla każdego kandydata U szuka metody o odbiorcy U, następnie &U, następnie &mut U. Pierwsze trafienie kończy poszukiwania. Dlatego name.len() na String trafia w str::len (bo String nie ma len), a (&&&name).len() działa tak samo — nadmiarowe referencje są zdejmowane po drodze.
Z tego wynika pułapka, którą warto znać, zanim napisze się własne impl Deref. Typ z Deref dziedziczy całą powierzchnię metod celu, więc dodanie własnej metody o nazwie, której cel już używa, po cichu zmienia znaczenie każdego wywołania: t.len() zaczyna zwracać String zamiast usize, nic nie ostrzega, a błąd kompilacji pojawia się dopiero u tego, kto z wyniku korzystał. Do metody celu wciąż można dotrzeć — przez (*t).len() albo Vec::len(&t) — ale trzeba wiedzieć, że jest co omijać. Stąd praktyczna zasada: Deref implementuje się dla wskaźników inteligentnych, nie dla zwykłych opakowań, a Rc::clone(&a) pisze się zamiast a.clone() właśnie po to, żeby nazwać typ, a nie zdać się na drabinę.
Czytelnikowi znającemu C++ najbliższym odpowiednikiem jest łańcuchowanie operator->, z dwiema różnicami: Rust wstawia też pożyczenie (& albo &mut), a przeciążania metod tu nie ma, więc niejednoznaczność, którą C++ rozstrzyga typami argumentów, w Ruscie rozstrzyga wyłącznie pozycja na drabinie — cicho, na korzyść bliższego kandydata.
Szukaj po polsku: rozstrzyganie wywołania metody · automatyczne wyłuskanie odbiorcy · przesłanianie metody przez Deref · rust method resolution order · rust autoref autoderef