3.0 KiB
Implementing traits
When a type is defined in another crate (e.g. u32, from
Rust’s standard library), you can’t directly define new methods for it.
If you try:
impl u32 {
fn is_even(&self) -> bool {
self % 2 == 0
}
}the compiler will complain:
error[E0390]: cannot define inherent `impl` for primitive types
|
1 | impl u32 {
| ^^^^^^^^
|
= help: consider using an extension trait instead
Extension trait
An extension trait is a trait whose primary purpose
is to attach new methods to foreign types, such as u32.
That’s exactly the pattern you deployed in the previous exercise, by
defining the IsEven trait and then implementing it for
i32 and u32. You are then free to call
is_even on those types as long as IsEven is in
scope.
// Bring the trait in scope
use my_library::IsEven;
fn main() {
// Invoke its method on a type that implements it
if 4.is_even() {
// [...]
}
}One implementation
There are limitations to the trait implementations you can
write.
The simplest and most straight-forward one: you can’t implement the same
trait twice, in a crate, for the same type.
For example:
trait IsEven {
fn is_even(&self) -> bool;
}
impl IsEven for u32 {
fn is_even(&self) -> bool {
true
}
}
impl IsEven for u32 {
fn is_even(&self) -> bool {
false
}
}The compiler will reject it:
error[E0119]: conflicting implementations of trait `IsEven` for type `u32`
|
5 | impl IsEven for u32 {
| ------------------- first implementation here
...
11 | impl IsEven for u32 {
| ^^^^^^^^^^^^^^^^^^^ conflicting implementation for `u32`
There can be no ambiguity as to what trait implementation should be
used when IsEven::is_even is invoked on a u32
value, therefore there can only be one.
Orphan rule
Things get more nuanced when multiple crates are involved. In particular, at least one of the following must be true:
- The trait is defined in the current crate
- The implementor type is defined in the current crate
This is known as Rust’s orphan rule. Its goal is to make the method resolution process unambiguous.
Imagine the following situation:
- Crate
Adefines theIsEventrait - Crate
BimplementsIsEvenforu32 - Crate
Cprovides a (different) implementation of theIsEventrait foru32 - Crate
Ddepends on bothBandCand calls1.is_even()
Which implementation should be used? The one defined in
B? Or the one defined in C?
There’s no good answer, therefore the orphan rule was defined to prevent
this scenario. Thanks to the orphan rule, neither crate B
nor crate C would compile.
References
- The exercise for this section is located in
exercises/04_traits/02_orphan_rule
Further reading
- There are some caveats and exceptions to the orphan rule as stated above. Check out the reference if you want to get familiar with its nuances.