I was just reading this article about generics and Traits in the Rust by Example, where they seem to implement a Trait on a generic caller. Check their article for the full example, but here's a minimal working example:
struct Example;
trait Foo {
fn print_foo(&self);
}
impl<T> Foo for T {
fn print_foo(&self) {
println!("foo");
}
}
fn main() {
let example = Example;
example.print_foo();
}
This example works and prints foo
. How does this work? For context, I read the full Rust book and didn't see this mentioned until the aforementioned article. How exactly does a generic implementation like this work? How does the compiler know to associate print_foo
with Example
? How is this impl
scoped? Could I have crate-scoped generic implementations?
How exactly does a generic implementation like this work?
You can more or less think about generics as if they were lazy code generation: impl<T> Foo for T
means “For every¹ concrete type T
, if an implementation of Foo
for T
is needed, create one using this function.”
How does the compiler know to associate
print_foo
withExample
?
When the compiler sees a method call, here .print_foo()
,
impl Foo { fn print_foo(&self) {...} }
. It doesn't find one in this case.use
d) to see if any of them define a method called print_foo
.Example
implements any of these traits. Example
implements Foo
, because everything (except unsized types) implements Foo
, so that trait is used.These rules are described in the Method-call expressions section of the Rust Reference.
How is this
impl
scoped? Could I have crate-scoped generic implementations?
Trait implementations are not scoped. They effectively exist everywhere, regardless of what might be brought into the current scope. (The trait implementation coherence rules are designed to ensure that which implementations are found never depends on which crates are in the current crate's dependencies, i.e. which code the compiler compiled.)
However, traits are scoped: a trait method will never be found unless the trait is brought into scope with use
or by defining it in the same module.
In your particular case, impl<T> Foo for T
means that it is not possible to write any other implementations for the trait¹ — there is no such thing as a “crate-scoped generic implementation”.
¹ <T>
does not quite mean “every type”. It means “every sized type”, because most generic code can't handle dynamically-sized/“unsized” types like dyn Foo
and [i32]
, so it's a useful default restriction. If you write <T: ?Sized>
instead, then the generic is truly over every possible type.