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, where BookFormat can be different types
  • You can implement both FromIterator<T> and FromIterator<&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;
}

Enter fullscreen mode Exit fullscreen mode

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 }
}

Enter fullscreen mode Exit fullscreen mode

  • This example implements the Contains trait for the Container type
  • The associated types in Container’s Contains implementation are determined by type A = i32; and type 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;
}

Enter fullscreen mode Exit fullscreen mode

  • Target in type 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
    }
}

Enter fullscreen mode Exit fullscreen mode

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
    }
}

Enter fullscreen mode Exit fullscreen mode


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.