1.16.1. Two Ways to Make a Trait Generic
Traits can be generic in two ways:
- Generic type parameters. Example:
trait Foo<T> - Associated types. Example:
trait Foo { type Bar; }
The difference between the two is:
- With associated types, a given trait for a specific type has only one implementation
- With generic parameters, there can be multiple implementations
A simple suggestion: if possible, prefer associated types.
1.16.2. Generic (Type-Parameter) Traits
Generic traits require you to specify all generic type parameters and repeat their bounds.
This is somewhat harder to maintain. For example, if you add a generic type parameter to a trait, all implementers of that trait must update their code.
This form can also lead to the problem that a trait may have multiple implementations for a given type. Then the compiler has a harder time inferring which instance of the trait you actually want. Sometimes you must call an ambiguity-resolving function such as FromIterator::<u32>::from_iter.
In some cases this feature is also an advantage, for example:
-
impl PartialEq<BookFormat> for Book, whereBookFormatcan be different types - You can implement both
FromIterator<T>andFromIterator<&T> where T: Clone
1.16.3. Associated-Type Traits
Let’s use a piece of code as an example:
trait Contains {
type A;
type B;
// Updates syntax to refer to these new types generically
fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}
With associated types:
- The compiler only needs to know the type that implements the trait
- The bound can live entirely on the trait itself and does not need to be repeated
- Adding another associated type in the future does not affect users
- The concrete type determines the associated types inside the trait, so there is no need to use ambiguity-resolving functions. Look at this example:
impl Contains for Container {
// Specify what types `A` and `B` are. If the `input` type
// is `Container(i32, i32)`, the `output` types are determined
// as `i32` and `i32`.
type A = i32;
type B = i32;
// `&Self::A` and `&Self::B` are also valid here.
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// Grab the first number.
fn first(&self) -> i32 { self.0 }
// Grab the last number.
fn last(&self) -> i32 { self.1 }
}
- This example implements the
Containstrait for theContainertype - The associated types in
Container’sContainsimplementation are determined bytype A = i32;andtype B = i32;
You Cannot Implement Deref for Multiple Target Types
Look at the source code of the Deref trait:
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
-
Targetintype Target: ?Sized;is the target type we are talking about
Let’s write a Deref implementation to illustrate:
use std::ops::Deref;
struct Wrapper {
value: String,
}
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.value
}
}
In the code above, Wrapper can only be dereferenced as String. But if you want Wrapper to be dereferenced as both String and str at the same time, Rust does not allow you to implement Deref again, because Target can only have one concrete type.
In other words, this is illegal:
use std::ops::Deref;
struct Wrapper {
value: String,
}
// First Deref implementation, Target = String
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.value
}
}
// This is illegal: Rust does not allow a second `Deref` implementation
// for the same `Wrapper` type.
impl Deref for Wrapper {
type Target = str; // Conflict: Rust cannot infer which `Target` applies
fn deref(&self) -> &Self::Target {
&self.value
}
}
You Cannot Use Multiple Items to Implement the Iterator Trait
The reason is the same as the reason you cannot implement Deref for multiple target types: it mainly involves the uniqueness of associated types and the inference rules of the Rust compiler.