100 exercises to learn Rust

This commit is contained in:
LukeMathWalker
2024-05-12 22:21:03 +02:00
commit 5edebf6cf2
309 changed files with 13173 additions and 0 deletions

View 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.