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>Stringstr 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 named X" on a type you know implements a trait with method X, the method exists, but the trait isn't imported, so it's not a candidate during lookup. The fix is use the_trait::TheTrait; (or a prelude). This is why extension-trait crates tell you to import their trait, and why use std::io::Write; is needed before calling .write_all(). Method resolution only considers traits currently in scope.


Resolving a Method Call

SituationWhat to do
Normal call, one candidatejust value.method()
Inherent and trait method share a nameinherent wins; use Trait::method(&value) to force the trait one
Two traits define the same methoddisambiguate: Trait::method(&value)
Method has no self (associated fn)<Type as Trait>::method()
"no method named X" but the type implements ituse the trait into scope
Calling through a smart pointerauto-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 as T, &T, &mut T, and follows Deref chains, which is why boxed.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 no self (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 use the trait.