Fully Qualified Method Calls
How Rust resolves ambiguous method names when traits and types define the same method, and the fully qualified syntax for calling the right implementation
When a type implements multiple traits that each define a method with the same name, and the type itself might also have an inherent method with that same name, Rust needs an unambiguous way for you to specify which implementation you want to call. Fully qualified method calls are the mechanism that makes this possible.
Why Name Conflicts Happen
Rust's trait system is deliberately open: any crate can define a trait, any crate can implement that trait for any type it owns, and multiple traits can all declare a method with the same signature. The language does not forbid these overlaps because forbidding them would make trait composition unpredictable—adding a new trait to a codebase could suddenly break unrelated code that happened to use the same method name.
A typical situation looks like this. Two traits both describe behavior that might reasonably be called fly, but for completely different reasons. A Human type might implement both traits and also have its own idea of what flying means.
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
The compiler accepts this without complaint. The ambiguity only becomes a problem when you actually call fly—at that point, Rust must decide which of the three implementations to dispatch to.
Default Method Resolution
When you call a method with the usual dot syntax, Rust applies a fixed set of rules to pick the right function. For person.fly(), the compiler first looks for an inherent method defined directly on the type. If one exists, it calls that. Trait methods are only considered if there is no inherent match.
So this code:
fn main() {
let person = Human;
person.fly();
}
prints:
*waving arms furiously*
The inherent method on Human wins. The fly methods from Pilot and Wizard are still there—they are not overridden or hidden in a way that makes them uncallable—but the default resolution picks the inherent implementation.
Inherent methods always win:
When a type defines a method with the same name as a trait method, calling the method on an instance will always choose the inherent method. This is not a tiebreaker—it is a strict priority rule. If you want the trait version, you must use explicit syntax.
This rule is important because it keeps ordinary code predictable. When you see my_vec.len(), you do not need to check whether some trait you imported provides a conflicting len; the inherent len on Vec always gets called.
Calling a Trait Method Explicitly
When you want the version from a specific trait, you switch from dot syntax to a qualified call that names the trait. For methods that take &self, you pass the instance as the first argument.
fn main() {
let person = Human;
// Call the inherent method (same as person.fly())
Human::fly(&person);
// Call the trait methods explicitly
Pilot::fly(&person);
Wizard::fly(&person);
}
This prints:
*waving arms furiously*
This is your captain speaking.
Up!
The syntax Trait::method(&instance) tells Rust exactly which trait's method to call. The compiler no longer needs to guess. Even though Pilot and Wizard both define fly, the trait name before the double colon removes the ambiguity.
You always know which code runs:
When you write Pilot::fly(&person), there is zero uncertainty. If Human ever stops implementing Pilot, the code will not silently fall back to another fly—it will fail to compile, pointing you to the exact change you need to make.
This pattern covers the vast majority of real-world disambiguation cases. If you are implementing a trait method and need to call another trait's method on the same self, the explicit syntax lets you do that without accidentally recursing into your own implementation.
When Self Is Not Enough: Associated Functions Without Self
Associated functions—those defined on a trait without a self parameter—pose a harder problem. With methods that take self, the compiler can use the type of the instance to narrow down the possible implementations. But an associated function has no instance parameter, so writing Type::function() is ambiguous: the function could come from an inherent impl block or from any trait the type implements.
Consider this case:
trait GroundRobot {
fn robot_name() -> String;
}
struct Robot;
impl Robot {
fn robot_name() -> String {
String::from("WalkerBot")
}
}
impl GroundRobot for Robot {
fn robot_name() -> String {
String::from("GroundMaster")
}
}
fn main() {
// This calls the inherent function
println!("Robot is called: {}", Robot::robot_name());
}
The output is Robot is called: WalkerBot. The inherent robot_name wins, just as it would for a method with self. To reach the trait's version, you need a stronger syntax.
Fully Qualified Syntax: <Type as Trait>::function
For associated functions (and anywhere else you want absolute clarity), Rust provides the fully qualified syntax:
<Type as Trait>::function(args)
The angle brackets and the as keyword bind a concrete type to a specific trait, telling the compiler exactly which implementation to use. Here is how you call the GroundRobot version of robot_name:
fn main() {
println!(
"Robot is called: {}",
<Robot as GroundRobot>::robot_name()
);
}
Now the output is Robot is called: GroundMaster. The <Robot as GroundRobot> prefix removes all ambiguity: it says "the function robot_name as defined in the implementation of GroundRobot for Robot."
This syntax also works for methods that take self. Writing <Human as Pilot>::fly(&person) is equivalent to Pilot::fly(&person). The shorter form works when the trait alone is enough to disambiguate; the fully qualified form is necessary when you need to specify both the type and the trait.
Associated functions require fully qualified syntax:
If you write GroundRobot::robot_name(), Rust will complain that it cannot infer the type. The trait alone is not enough because multiple types could implement GroundRobot. You must specify the concrete type with <Type as Trait>::.
Calling an Inherent Method Over a Conflicting Trait Method
There is another case where fully qualified syntax matters: inside a trait implementation, you might need to call the type's inherent method rather than the trait's own method. Without explicit qualification, a call to self.method() inside a trait impl block will recurse into the trait method, not the inherent one.
This example from the standard library's ecosystem is instructive. Suppose you implement a trait that happens to define a method named get on Vec<i32>:
use std::any::Any;
trait Foo {
fn get(&self, index: usize) -> Option<&dyn Any>;
}
impl Foo for Vec<i32> {
fn get(&self, index: usize) -> Option<&dyn Any> {
// DANGER: This calls Foo::get, not Vec::get, causing infinite recursion
// Vec::get(self, index).map(|v| v as &dyn Any)
todo!()
}
}
If you uncomment the line that calls Vec::get(self, index), the compiler warns about unconditional recursion. Why? Because Vec does not have its own inherent get method. The get you expect comes from the slice type [i32] via deref coercion, and writing Vec::get inside impl Foo for Vec<i32> resolves to the trait method Foo::get. The fix is to use fully qualified syntax to call the slice's get:
impl Foo for Vec<i32> {
fn get(&self, index: usize) -> Option<&dyn Any> {
<[i32]>::get(self, index).map(|v| v as &dyn Any)
}
}
Here, <[i32]>::get unambiguously calls the inherent method on the [i32] slice. The as keyword is omitted because we are specifying only a type, not a trait, and [i32] has an inherent get method. This pattern prevents infinite recursion and makes the call site self-documenting.
Beware of recursive trait method calls:
Inside impl Trait for Type, writing Type::method(self, ...) will call the trait method itself if the trait defines method. To reach the inherent implementation, you must use the fully qualified syntax for the type that actually owns the inherent method—which might be a different type due to Deref coercion, as with Vec and [T].
The Role of Deref Coercion in Method Resolution
Method calls in Rust go through a two‑stage lookup. The compiler first checks the type itself for inherent methods. If none match, it walks the Deref chain, checking each deref target for inherent methods. Only after exhausting inherent options does it consider trait methods.
This means that when you write my_vec.get(0), the compiler sees that Vec has no inherent get, derefs to [T], finds get there, and calls it. The fully qualified syntax mirrors this: <Vec<i32>>::get does not exist, but <[i32]>::get does. Understanding this chain helps you write correct qualified calls when inherent methods live on a deref target rather than on the nominal type.
A Practical Real‑World Use: ToString::to_string
One of the most common places you will encounter fully qualified syntax in real code is when passing function pointers. The Iterator::map method can take a function pointer instead of a closure. If you want to convert numbers to strings using the standard library's ToString trait, you might write:
let numbers = vec![1, 2, 3];
let strings: Vec<String> = numbers
.iter()
.map(ToString::to_string)
.collect();
But ToString::to_string is ambiguous: ToString is a trait, and several types implement it. Rust resolves this because the closure context provides enough type information. However, in a different context—say, storing the function pointer in a variable—you might need the fully qualified form:
let f: fn(&i32) -> String = <i32 as ToString>::to_string;
Here, <i32 as ToString>::to_string is a function pointer that you can pass around. This pattern appears in larger codebases where you build pipelines of operations that accept function pointers rather than closures.
Common Pitfalls and How to Avoid Them
Forgetting That Inherent Methods Always Win
A developer might see Pilot::fly(&person) in a code example and assume that writing person.fly() will call the trait method. It will not. The inherent method on Human takes priority. The only way to call a trait method when an inherent method with the same name exists is to qualify it with the trait name.
Using the Wrong Type in Fully Qualified Syntax
When calling <[i32]>::get, you must use the type that actually owns the inherent method, not the type you are implementing for. A common mistake is to write <Vec<i32>>::get(self, index), which fails because Vec has no inherent get. This is especially confusing because the same method might work with dot syntax (self.get(index)) but fails when qualified, due to deref coercion happening only in dot notation.
Infinite Recursion Inside Trait Implementations
As shown earlier, calling Self::method(self, ...) inside a trait impl block where the trait defines method results in a call to the trait method itself. The compiler often warns about unconditional recursion, but in more complex cases the recursion might be indirect and harder to detect. When you need to call an inherent method that shadows a trait method, use the fully qualified syntax with the exact type that provides the inherent implementation.
Forgetting to Import the Trait for Qualified Calls
Qualified syntax still requires the trait to be in scope. Writing Pilot::fly(&person) will not compile if you haven't imported Pilot. This is different from inherent methods, which are always available. The trait must be in scope for its methods to be callable, even with explicit qualification.
Summary
Fully qualified method calls are Rust's solution to a deliberate design choice: traits and types are allowed to have identically named methods, and the language must provide a way to call any of them unambiguously. The rules are simple:
instance.method()prefers inherent methods over trait methods.Trait::method(&instance)calls the trait method, provided the trait is in scope.<Type as Trait>::function(args)calls an associated function from a specific trait implementation, and is necessary when there is noselfparameter to guide resolution.
This mechanism appears anywhere names collide: in code that implements multiple traits for the same type, in code that passes function pointers to trait methods, and inside trait implementations that need to call the type's inherent methods without recurring into the trait. Mastering the syntax ensures that when you write a method call, you know exactly which code runs—and that knowledge survives the addition of new traits and implementations across a growing codebase.