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
with Example
?
When the compiler sees a method call, here .print_foo()
,
- It first looks for a matching inherent impl,
impl Foo { fn print_foo(&self) {...} }
. It doesn't find one in this case.
- It looks at all traits that are visible in the current scope (defined or
use
d) to see if any of them define a method called print_foo
.
- It checks whether
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.