1. 1. Introduction
  2. 2. Getting Started
  3. 3. Tutorial: Guessing Game
  4. 4. Syntax and Semantics
    1. 4.1. Variable Bindings
    2. 4.2. Functions
    3. 4.3. Primitive Types
    4. 4.4. Comments
    5. 4.5. if
    6. 4.6. Loops
    7. 4.7. Vectors
    8. 4.8. Ownership
    9. 4.9. References and Borrowing
    10. 4.10. Lifetimes
    11. 4.11. Mutability
    12. 4.12. Structs
    13. 4.13. Enums
    14. 4.14. Match
    15. 4.15. Patterns
    16. 4.16. Method Syntax
    17. 4.17. Strings
    18. 4.18. Generics
    19. 4.19. Traits
    20. 4.20. Drop
    21. 4.21. if let
    22. 4.22. Trait Objects
    23. 4.23. Closures
    24. 4.24. Universal Function Call Syntax
    25. 4.25. Crates and Modules
    26. 4.26. `const` and `static`
    27. 4.27. Attributes
    28. 4.28. `type` aliases
    29. 4.29. Casting between types
    30. 4.30. Associated Types
    31. 4.31. Unsized Types
    32. 4.32. Operators and Overloading
    33. 4.33. Deref coercions
    34. 4.34. Macros
    35. 4.35. Raw Pointers
    36. 4.36. `unsafe`
  5. 5. Effective Rust
    1. 5.1. The Stack and the Heap
    2. 5.2. Testing
    3. 5.3. Conditional Compilation
    4. 5.4. Documentation
    5. 5.5. Iterators
    6. 5.6. Concurrency
    7. 5.7. Error Handling
    8. 5.8. Choosing your Guarantees
    9. 5.9. FFI
    10. 5.10. Borrow and AsRef
    11. 5.11. Release Channels
    12. 5.12. Using Rust without the standard library
  6. 6. Nightly Rust
    1. 6.1. Compiler Plugins
    2. 6.2. Inline Assembly
    3. 6.3. No stdlib
    4. 6.4. Intrinsics
    5. 6.5. Lang items
    6. 6.6. Advanced linking
    7. 6.7. Benchmark Tests
    8. 6.8. Box Syntax and Patterns
    9. 6.9. Slice Patterns
    10. 6.10. Associated Constants
    11. 6.11. Custom Allocators
  7. 7. Glossary
  8. 8. Syntax Index
  9. 9. Bibliography

Associated Types

Associated types are a powerful part of Rust’s type system. They’re related to the idea of a ‘type family’, in other words, grouping multiple types together. That description is a bit abstract, so let’s dive right into an example. If you want to write a Graph trait, you have two types to be generic over: the node type and the edge type. So you might write a trait, Graph<N, E>, that looks like this:

trait Graph<N, E> {
    fn has_edge(&self, &N, &N) -> bool;
    fn edges(&self, &N) -> Vec<E>;
    // etc
}Run

While this sort of works, it ends up being awkward. For example, any function that wants to take a Graph as a parameter now also needs to be generic over the Node and Edge types too:

fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 { ... }Run

Our distance calculation works regardless of our Edge type, so the E stuff in this signature is a distraction.

What we really want to say is that a certain Edge and Node type come together to form each kind of Graph. We can do that with associated types:

trait Graph {
    type N;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
    // etc
}Run

Now, our clients can be abstract over a given Graph:

fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> u32 { ... }Run

No need to deal with the Edge type here!

Let’s go over all this in more detail.

Defining associated types

Let’s build that Graph trait. Here’s the definition:

trait Graph {
    type N;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
}Run

Simple enough. Associated types use the type keyword, and go inside the body of the trait, with the functions.

These type declarations work the same way as those for functions. For example, if we wanted our N type to implement Display, so we can print the nodes out, we could do this:

use std::fmt;

trait Graph {
    type N: fmt::Display;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
}Run

Implementing associated types

Just like any trait, traits that use associated types use the impl keyword to provide implementations. Here’s a simple implementation of Graph:

struct Node;

struct Edge;

struct MyGraph;

impl Graph for MyGraph {
    type N = Node;
    type E = Edge;

    fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
        true
    }

    fn edges(&self, n: &Node) -> Vec<Edge> {
        Vec::new()
    }
}Run

This silly implementation always returns true and an empty Vec<Edge>, but it gives you an idea of how to implement this kind of thing. We first need three structs, one for the graph, one for the node, and one for the edge. If it made more sense to use a different type, that would work as well, we’re going to use structs for all three here.

Next is the impl line, which is an implementation like any other trait.

From here, we use = to define our associated types. The name the trait uses goes on the left of the =, and the concrete type we’re implementing this for goes on the right. Finally, we use the concrete types in our function declarations.

Trait objects with associated types

There’s one more bit of syntax we should talk about: trait objects. If you try to create a trait object from a trait with an associated type, like this:

let graph = MyGraph;
let obj = Box::new(graph) as Box<Graph>;Run

You’ll get two errors:

error: the value of the associated type `E` (from the trait `main::Graph`) must
be specified [E0191]
let obj = Box::new(graph) as Box<Graph>;
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24:44 error: the value of the associated type `N` (from the trait
`main::Graph`) must be specified [E0191]
let obj = Box::new(graph) as Box<Graph>;
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

We can’t create a trait object like this, because we don’t know the associated types. Instead, we can write this:

let graph = MyGraph;
let obj = Box::new(graph) as Box<Graph<N=Node, E=Edge>>;Run

The N=Node syntax allows us to provide a concrete type, Node, for the N type parameter. Same with E=Edge. If we didn’t provide this constraint, we couldn’t be sure which impl to match this trait object to.