100 exercises to learn Rust
This commit is contained in:
116
book/src/04_traits/02_orphan_rule.md
Normal file
116
book/src/04_traits/02_orphan_rule.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 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:
|
||||
|
||||
```rust
|
||||
impl u32 {
|
||||
fn is_even(&self) -> bool {
|
||||
self % 2 == 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
the compiler will complain:
|
||||
|
||||
```text
|
||||
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.
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
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:
|
||||
|
||||
```text
|
||||
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 `A` defines the `IsEven` trait
|
||||
- Crate `B` implements `IsEven` for `u32`
|
||||
- Crate `C` provides a (different) implementation of the `IsEven` trait for `u32`
|
||||
- Crate `D` depends on both `B` and `C` and calls `1.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](https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence)
|
||||
if you want to get familiar with its nuances.
|
||||
Reference in New Issue
Block a user