@@ -14,20 +14,20 @@ pub trait Deref {
|
||||
}
|
||||
```
|
||||
|
||||
They both feature type parameters.
|
||||
In the case of `From`, it's a generic parameter, `T`.
|
||||
They both feature type parameters.\
|
||||
In the case of `From`, it's a generic parameter, `T`.\
|
||||
In the case of `Deref`, it's an associated type, `Target`.
|
||||
|
||||
What's the difference? Why use one over the other?
|
||||
|
||||
## At most one implementation
|
||||
|
||||
Due to how deref coercion works, there can only be one "target" type for a given type. E.g. `String` can
|
||||
only deref to `str`.
|
||||
Due to how deref coercion works, there can only be one "target" type for a given type. E.g. `String` can
|
||||
only deref to `str`.
|
||||
It's about avoiding ambiguity: if you could implement `Deref` multiple times for a type,
|
||||
which `Target` type should the compiler choose when you call a `&self` method?
|
||||
|
||||
That's why `Deref` uses an associated type, `Target`.
|
||||
That's why `Deref` uses an associated type, `Target`.\
|
||||
An associated type is uniquely determined **by the trait implementation**.
|
||||
Since you can't implement `Deref` more than once, you'll only be able to specify one `Target` for a given type
|
||||
and there won't be any ambiguity.
|
||||
@@ -51,7 +51,7 @@ impl From<u16> for WrappingU32 {
|
||||
}
|
||||
```
|
||||
|
||||
This works because `From<u16>` and `From<u32>` are considered **different traits**.
|
||||
This works because `From<u16>` and `From<u32>` are considered **different traits**.\
|
||||
There is no ambiguity: the compiler can determine which implementation to use based on type of the value being converted.
|
||||
|
||||
## Case study: `Add`
|
||||
@@ -73,7 +73,7 @@ It uses both mechanisms:
|
||||
|
||||
### `RHS`
|
||||
|
||||
`RHS` is a generic parameter to allow for different types to be added together.
|
||||
`RHS` is a generic parameter to allow for different types to be added together.\
|
||||
For example, you'll find these two implementations in the standard library:
|
||||
|
||||
```rust
|
||||
@@ -109,10 +109,10 @@ because `u32` implements `Add<&u32>` _as well as_ `Add<u32>`.
|
||||
|
||||
### `Output`
|
||||
|
||||
`Output` represents the type of the result of the addition.
|
||||
`Output` represents the type of the result of the addition.
|
||||
|
||||
Why do we need `Output` in the first place? Can't we just use `Self` as output, the type implementing `Add`?
|
||||
We could, but it would limit the flexibility of the trait. In the standard library, for example, you'll find
|
||||
Why do we need `Output` in the first place? Can't we just use `Self` as output, the type implementing `Add`?
|
||||
We could, but it would limit the flexibility of the trait. In the standard library, for example, you'll find
|
||||
this implementation:
|
||||
|
||||
```rust
|
||||
@@ -125,9 +125,9 @@ impl Add<&u32> for &u32 {
|
||||
}
|
||||
```
|
||||
|
||||
The type they're implementing the trait for is `&u32`, but the result of the addition is `u32`.
|
||||
It would be impossible[^flexible] to provide this implementation if `add` had to return `Self`, i.e. `&u32` in this case.
|
||||
`Output` lets `std` decouple the implementor from the return type, thus supporting this case.
|
||||
The type they're implementing the trait for is `&u32`, but the result of the addition is `u32`.\
|
||||
It would be impossible[^flexible] to provide this implementation if `add` had to return `Self`, i.e. `&u32` in this case.
|
||||
`Output` lets `std` decouple the implementor from the return type, thus supporting this case.
|
||||
|
||||
On the other hand, `Output` can't be a generic parameter. The output type of the operation **must** be uniquely determined
|
||||
once the types of the operands are known. That's why it's an associated type: for a given combination of implementor
|
||||
@@ -138,7 +138,7 @@ and generic parameters, there is only one `Output` type.
|
||||
To recap:
|
||||
|
||||
- Use an **associated type** when the type must be uniquely determined for a given trait implementation.
|
||||
- Use a **generic parameter** when you want to allow multiple implementations of the trait for the same type,
|
||||
- Use a **generic parameter** when you want to allow multiple implementations of the trait for the same type,
|
||||
with different input types.
|
||||
|
||||
## References
|
||||
@@ -146,6 +146,5 @@ To recap:
|
||||
- The exercise for this section is located in `exercises/04_traits/10_assoc_vs_generic`
|
||||
|
||||
[^flexible]: Flexibility is rarely free: the trait definition is more complex due to `Output`, and implementors have to reason about
|
||||
what they want to return. The trade-off is only justified if that flexibility is actually needed. Keep that in mind
|
||||
when designing your own traits.
|
||||
|
||||
what they want to return. The trade-off is only justified if that flexibility is actually needed. Keep that in mind
|
||||
when designing your own traits.
|
||||
|
||||
Reference in New Issue
Block a user