Skip to content
← All Rust articles

Rust explainer

How Rust chooses a method

About 5 min to read. Allow extra time to work through examples.

Follow automatic borrowing and dereferencing, understand a Borrow method collision, and choose an explicit call when a name is ambiguous.

This article uses Rust. You can make it your practice mode to browse related content across the site.

A method call such as values.len() can borrow its receiver automatically. It can also follow dereferences to find a method on another type. These conveniences explain why many calls need no explicit & or *, but they can also make two methods with the same name compete.

Follow the receiver types

For a call x.method(), lookup starts with the type of x and follows its dereference chain. References have built-in dereferencing; types such as Rc<T> provide it through Deref. An array-to-slice unsizing step can extend the end of the chain.

For every type U in that chain, the search considers U, then &U, then &mut U. This automatic borrowing is called autoref. The first applicable receiver determines which part of the search can supply the method.

For x: Rc<RefCell<i32>>, the list is:

Rc<RefCell<i32>>
&Rc<RefCell<i32>>
&mut Rc<RefCell<i32>>
RefCell<i32>
&RefCell<i32>
&mut RefCell<i32>

There is no i32 step. RefCell<i32> does not implement Deref to its contents. Its borrow() method returns a guard, Ref<'_, i32>, which implements dereferencing while keeping the runtime borrow active.

At a particular candidate receiver, inherent methods take priority over visible trait methods. For generic parameters, methods supplied by trait bounds receive priority within the trait search. These priorities do not let a method skip ahead of an earlier receiver candidate.

Finding a method is not the end of type checking. The call can still fail because it needs a mutable borrow, would move a borrowed value, or requires an unsafe context. Rust does not simply keep searching for a different method when the selected call fails those checks.

Check your understanding Why does let v = vec![1, 2, 3]; v.len() work without writing a reference? Show answer

Vec::len takes &self. Method-call syntax can borrow v to supply that receiver, so the call does not consume the vector. A method needing &mut self instead requires mutable access; automatic borrowing cannot make an immutable binding mutable.

For "hello".len(), the receiver already has type &str, which matches the receiver of str::len. No by-value str call is needed.

A trait method can be found first

Here the trait's shared receiver is considered before the inherent method's mutable receiver:

struct Counter;

trait Inspect {
    fn inspect(&self) -> &'static str;
}

impl Inspect for Counter {
    fn inspect(&self) -> &'static str { "trait" }
}

impl Counter {
    fn inspect(&mut self) -> &'static str { "inherent" }
}

fn main() {
    let mut counter = Counter;
    assert_eq!(counter.inspect(), "trait");
    assert_eq!(Counter::inspect(&mut counter), "inherent");
}

The first call reaches &Counter before &mut Counter. “Inherent always wins” would predict the wrong result. Naming the type in the second call selects its inherent method directly.

A collision between two different borrow methods

RefCell::borrow checks the cell's runtime borrow state and returns a guard. The Borrow trait instead provides a borrowed view of a value. Importing that trait makes its methods available to lookup:

use std::borrow::Borrow;
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let x = Rc::new(RefCell::new(5));
    let b = x.borrow(); // Error: the borrowed type needs an annotation.
}

At &Rc<RefCell<i32>>, trait lookup finds both Rc<T>: Borrow<T> and the blanket implementation T: Borrow<T>. The result could therefore be either &RefCell<i32> or &Rc<RefCell<i32>>. Without more type information, this example produces E0283. Lookup has not reached the cell's inherent method.

An annotation can select the borrowed cell:

use std::borrow::Borrow;
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let x = Rc::new(RefCell::new(5));
    let cell: &RefCell<i32> = x.borrow();
    assert_eq!(*cell.borrow(), 5);
}

That is useful if you wanted a reference to the cell. It is different from obtaining a guard to the integer, and does not itself borrow the cell's contents at runtime.

Check your understanding With Borrow in scope, how can you explicitly obtain a Ref<'_, i32> from x: Rc<RefCell<i32>>? Show answer

RefCell::borrow(&x) names the inherent method and lets the argument coerce from &Rc<RefCell<i32>> to &RefCell<i32>.

(*x).borrow() also works: its receiver is the cell, so its inherent method wins at &RefCell<i32>. <RefCell<i32>>::borrow(&x) spells out the type parameter in the first form. Choose the form that makes the intended method easiest to recognize; RefCell::borrow(&x) is a clear choice here.

If the Borrow import is unnecessary, removing it also removes this collision. Traits can be available through a prelude, a local definition or a generic bound as well as a use, so “there is no import” does not mean “no trait methods are available.” For example, a generic function with a T: module::Trait bound can call that trait's methods without a separate import.

Disambiguate two trait methods

Two traits can offer the same method for the same receiver. When lookup cannot choose, use <Type as Trait>::method to state which implementation you want:

struct Item;
trait Short { fn label(&self) -> &'static str; }
trait Long { fn label(&self) -> &'static str; }
impl Short for Item { fn label(&self) -> &'static str { "item" } }
impl Long for Item { fn label(&self) -> &'static str { "an item" } }

fn main() {
    let item = Item;
    // item.label() would be ambiguous (E0034).
    assert_eq!(<Item as Short>::label(&item), "item");
    assert_eq!(<Item as Long>::label(&item), "an item");
}

E0034 and the earlier E0283 describe different problems: competing methods versus an unresolved generic borrowed type. Read the candidates in the diagnostic before deciding whether to qualify the method or annotate a type.

Consider method lookup when designing a wrapper

Implementing Deref exposes target methods through your wrapper's lookup chain. Wrapper methods, visible traits and future additions to the target can affect which calls compile or which implementation they select.

Deref is appropriate when the wrapper is intended to behave transparently like its target and dereferencing is cheap and unsurprising. It is a public API commitment, not a general substitute for inheritance. An explicit accessor may communicate the relationship better when that transparency is unwanted.

When a call surprises you, identify the receiver's exact type, list the candidate receiver forms, and check which traits are available. Then use a type-qualified or trait-qualified call to express the intended operation.

step 14 of 24

Generics and Traits

Up next Operator overloading: + is just a trait method