Trait Method Resolution
Most of the time value.method() just works, and you never think about how the compiler decided which method to call. Then two traits define a method with the same name, or your type has both an inherent method and a trait method called len, and you get a "multiple applicable items in scope" error with no obvious fix. This tutorial explains the rules Rust uses to resolve .method() calls, why inherent methods quietly win, how auto-ref/deref makes &self/&mut self calls transparent, and the fully-qualified syntax that disambiguates when the compiler can't. Understanding this turns those errors from mysterious to mechanical.
The Lookup Order: Inherent Beats Trait
When you write value.method(), the compiler searches for a matching method in a specific order, and the first match wins. The key rule: inherent methods (defined in a plain impl Type) take priority over trait methods. This is deliberate, it lets a type "override" a trait method's name with its own.
trait Greet {
fn hello(&self) -> String { "trait hello".into() }
}
struct Robot;
impl Robot {
fn hello(&self) -> String { "inherent hello".into() } // inherent
}
impl Greet for Robot {} // trait, uses default
let r = Robot;
println!("{}", r.hello()); // "inherent hello" — the inherent method wins
r.hello() calls the inherent method silently, the trait's hello is shadowed. This matters in practice when you add an inherent method whose name collides with a trait already in scope: the inherent one takes over, sometimes surprisingly. It's also why a library adding an inherent method can shadow an extension-trait method a user relied on.
Auto-Ref and Auto-Deref: Why &self Calls Just Work
You call v.len() on a Vec, not (&v).len(), even though len takes &self. The compiler inserts the reference for you. During method lookup it tries the receiver as T, then &T, then &mut T, and also follows Deref chains, so it auto-references and auto-dereferences to find a method that fits.
let v = vec![1, 2, 3];
v.len(); // len takes &self; compiler calls (&v).len() automatically
let boxed = Box::new(String::from("hi"));
boxed.len(); // Box<String> → derefs to String → derefs to str; str::len found
This is why smart pointers (Deref, AsRef, and Borrow) feel transparent: boxed.len() walks Box<String> → String → str via Deref until it finds a len. The mechanism is invisible when it works, but it explains otherwise-confusing behavior, e.g. a method resolving on the pointed-to type rather than the pointer, or an unexpected method appearing because a Deref target provides it. The lookup tries each step (T, &T, &mut T, then deref, repeat) until something matches.
The Ambiguity Error: Two Traits, One Name
The classic wall: two traits in scope both define a method with the same name, and the compiler can't tell which you mean:
trait Pilot { fn fly(&self); }
trait Wizard { fn fly(&self); }
struct Person;
impl Pilot for Person { fn fly(&self) { println!("pilot"); } }
impl Wizard for Person { fn fly(&self) { println!("wizard"); } }
let p = Person;
// p.fly(); // ERROR: multiple applicable items in scope
error[E0034]: multiple applicable items in scope
Neither trait is "more correct", p.fly() is genuinely ambiguous. The dot syntax has no room to say which trait's fly you want, so the compiler stops and asks you to be explicit. This isn't a bug to work around; it's the compiler refusing to guess.
Fully-Qualified Syntax: Saying Exactly Which Method
The disambiguator is fully-qualified syntax, naming the trait explicitly so there's exactly one candidate. There are two forms, depending on whether the method takes a receiver:
// method with &self: Trait::method(receiver)
Pilot::fly(&p); // "pilot"
Wizard::fly(&p); // "wizard"
// the most explicit form, needed for associated functions with no self:
<Person as Pilot>::fly(&p);
The <Type as Trait>::method() form is the fully general one, and it's required (not just optional) when the method has no self receiver, because then there's no value for the compiler to infer the type from. The canonical example is Default::default(): let x = <Vec<i32> as Default>::default(); when the type can't otherwise be inferred. Whenever you see "multiple applicable items" or "type annotations needed" on a method call, the fix is to reach for Trait::method(recv) or the full <Type as Trait>::method().
Gotcha: a trait's method is only callable via the dot syntax if the trait is in scope (imported with
use). A common confusing error is "no method namedX" on a type you know implements a trait with methodX, the method exists, but the trait isn't imported, so it's not a candidate during lookup. The fix isuse the_trait::TheTrait;(or a prelude). This is why extension-trait crates tell you to import their trait, and whyuse std::io::Write;is needed before calling.write_all(). Method resolution only considers traits currently in scope.
Resolving a Method Call
| Situation | What to do |
|---|---|
| Normal call, one candidate | just value.method() |
| Inherent and trait method share a name | inherent wins; use Trait::method(&value) to force the trait one |
| Two traits define the same method | disambiguate: Trait::method(&value) |
Method has no self (associated fn) | <Type as Trait>::method() |
| "no method named X" but the type implements it | use the trait into scope |
| Calling through a smart pointer | auto-deref finds it; usually nothing to do |
The mental model, in order: the compiler looks for inherent methods first, then trait methods for traits currently in scope, trying the receiver as T, &T, &mut T, and following Deref. If exactly one matches, done; if none match, check the trait is imported; if several match, use fully-qualified syntax to pick one. Nearly every method-resolution error is one of those three cases.
Key Takeaways
- Method lookup checks inherent methods before trait methods, an inherent method silently shadows a trait method of the same name, which can surprise you when a name collides with a trait in scope.
- Auto-ref/auto-deref makes
.method()transparent: the compiler tries the receiver asT,&T,&mut T, and followsDerefchains, which is whyboxed.len()resolves on the pointed-to type. - Two in-scope traits with the same method name produce an "E0034: multiple applicable items" error, the call is genuinely ambiguous and the compiler won't guess.
- Disambiguate with fully-qualified syntax:
Trait::method(&value), or the fully general<Type as Trait>::method()which is required for associated functions with noself(e.g.<Vec<i32> as Default>::default()). - A trait's methods are only lookup candidates when the trait is in scope; "no method named X" on a type that implements it usually means you forgot to
usethe trait.