🎓 A1Learner
🔍

Rust Associated Types

An associated type is a type named inside a trait. Each implementation fills in that name once.

The trait leaves a blank

Inside the trait, type Item; is a named blank — the trait says the blank exists, not what fills it:

trait Words {
    type Item;
    fn next_word(&mut self) -> Option<Self::Item>;
}

Each impl fills it in once

The implementation names the type one time — from then on, Self::Item means String for this type:

impl Words for OneWord {
    type Item = String;
}

Name the associated type from outside

Code outside the trait names the filled-in type through the implementor: I::Item. head works for every iterator and answers in that iterator's own item type:

fn head<I: Iterator>(mut it: I) -> Option<I::Item> {
    it.next()
}
println!("head = {:?}", head(vec![9, 8, 7].into_iter()));

No default yet in stable Rust

The trait may not pre-fill its own blank — associated type defaults are still experimental, so this is refused:

trait Words {
    type Item = i32; // error E0658: associated type defaults are unstable
}

Example

use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Point { x: i32, y: i32 }
impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } }
}
fn main() { println!("{:?}", Point { x: 1, y: 2 } + Point { x: 3, y: 4 }); }

Key points

  • Use an associated type when there is exactly one sensible choice per implementation
  • Use a generic parameter on the trait instead.
  • Do it when one type may implement it in several ways, like From<T> for many source types
  • Iterator's Item is an associated type.
  • Add's Output is an associated type.
  • An associated type names one type inside a trait for each implementation to fill in.

In this example, Point gets the + operator by implementing the Add trait. Add requires an associated type, Output: the type that Point + Point produces. The impl sets type Output = Point and writes add to combine the x values and the y values. So (1, 2) + (3, 4) gives Point { x: 4, y: 6 }. The {:?} in the println! shows the new point. The #[derive] lines give Point its Debug printing and equality tests for free.

Note

An associated type keeps signatures short.

You write Self::Item instead of carrying an extra generic letter through every function.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why does Iterator use an associated type Item instead of a generic parameter?