]>
Commit | Line | Data |
---|---|---|
c34b1796 AL |
1 | % Associated Types |
2 | ||
bd371182 AL |
3 | Associated types are a powerful part of Rust’s type system. They’re related to |
4 | the idea of a ‘type family’, in other words, grouping multiple types together. That | |
5 | description is a bit abstract, so let’s dive right into an example. If you want | |
c34b1796 AL |
6 | to write a `Graph` trait, you have two types to be generic over: the node type |
7 | and the edge type. So you might write a trait, `Graph<N, E>`, that looks like | |
8 | this: | |
9 | ||
10 | ```rust | |
11 | trait Graph<N, E> { | |
12 | fn has_edge(&self, &N, &N) -> bool; | |
13 | fn edges(&self, &N) -> Vec<E>; | |
14 | // etc | |
15 | } | |
16 | ``` | |
17 | ||
18 | While this sort of works, it ends up being awkward. For example, any function | |
19 | that wants to take a `Graph` as a parameter now _also_ needs to be generic over | |
20 | the `N`ode and `E`dge types too: | |
21 | ||
22 | ```rust,ignore | |
23 | fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 { ... } | |
24 | ``` | |
25 | ||
26 | Our distance calculation works regardless of our `Edge` type, so the `E` stuff in | |
27 | this signature is just a distraction. | |
28 | ||
29 | What we really want to say is that a certain `E`dge and `N`ode type come together | |
30 | to form each kind of `Graph`. We can do that with associated types: | |
31 | ||
32 | ```rust | |
33 | trait Graph { | |
34 | type N; | |
35 | type E; | |
36 | ||
37 | fn has_edge(&self, &Self::N, &Self::N) -> bool; | |
38 | fn edges(&self, &Self::N) -> Vec<Self::E>; | |
39 | // etc | |
40 | } | |
41 | ``` | |
42 | ||
43 | Now, our clients can be abstract over a given `Graph`: | |
44 | ||
45 | ```rust,ignore | |
62682a34 | 46 | fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> u32 { ... } |
c34b1796 AL |
47 | ``` |
48 | ||
49 | No need to deal with the `E`dge type here! | |
50 | ||
bd371182 | 51 | Let’s go over all this in more detail. |
c34b1796 AL |
52 | |
53 | ## Defining associated types | |
54 | ||
bd371182 | 55 | Let’s build that `Graph` trait. Here’s the definition: |
c34b1796 AL |
56 | |
57 | ```rust | |
58 | trait Graph { | |
59 | type N; | |
60 | type E; | |
61 | ||
62 | fn has_edge(&self, &Self::N, &Self::N) -> bool; | |
63 | fn edges(&self, &Self::N) -> Vec<Self::E>; | |
64 | } | |
65 | ``` | |
66 | ||
67 | Simple enough. Associated types use the `type` keyword, and go inside the body | |
68 | of the trait, with the functions. | |
69 | ||
70 | These `type` declarations can have all the same thing as functions do. For example, | |
71 | if we wanted our `N` type to implement `Display`, so we can print the nodes out, | |
72 | we could do this: | |
73 | ||
74 | ```rust | |
75 | use std::fmt; | |
76 | ||
77 | trait Graph { | |
78 | type N: fmt::Display; | |
79 | type E; | |
80 | ||
81 | fn has_edge(&self, &Self::N, &Self::N) -> bool; | |
82 | fn edges(&self, &Self::N) -> Vec<Self::E>; | |
83 | } | |
84 | ``` | |
85 | ||
86 | ## Implementing associated types | |
87 | ||
88 | Just like any trait, traits that use associated types use the `impl` keyword to | |
bd371182 | 89 | provide implementations. Here’s a simple implementation of Graph: |
c34b1796 AL |
90 | |
91 | ```rust | |
92 | # trait Graph { | |
93 | # type N; | |
94 | # type E; | |
95 | # fn has_edge(&self, &Self::N, &Self::N) -> bool; | |
96 | # fn edges(&self, &Self::N) -> Vec<Self::E>; | |
97 | # } | |
98 | struct Node; | |
99 | ||
100 | struct Edge; | |
101 | ||
102 | struct MyGraph; | |
103 | ||
104 | impl Graph for MyGraph { | |
105 | type N = Node; | |
106 | type E = Edge; | |
107 | ||
108 | fn has_edge(&self, n1: &Node, n2: &Node) -> bool { | |
109 | true | |
110 | } | |
111 | ||
112 | fn edges(&self, n: &Node) -> Vec<Edge> { | |
113 | Vec::new() | |
114 | } | |
115 | } | |
116 | ``` | |
117 | ||
118 | This silly implementation always returns `true` and an empty `Vec<Edge>`, but it | |
119 | gives you an idea of how to implement this kind of thing. We first need three | |
120 | `struct`s, one for the graph, one for the node, and one for the edge. If it made | |
bd371182 | 121 | more sense to use a different type, that would work as well, we’re just going to |
c34b1796 AL |
122 | use `struct`s for all three here. |
123 | ||
124 | Next is the `impl` line, which is just like implementing any other trait. | |
125 | ||
126 | From here, we use `=` to define our associated types. The name the trait uses | |
bd371182 | 127 | goes on the left of the `=`, and the concrete type we’re `impl`ementing this |
c34b1796 AL |
128 | for goes on the right. Finally, we use the concrete types in our function |
129 | declarations. | |
130 | ||
131 | ## Trait objects with associated types | |
132 | ||
133 | There’s one more bit of syntax we should talk about: trait objects. If you | |
134 | try to create a trait object from an associated type, like this: | |
135 | ||
136 | ```rust,ignore | |
137 | # trait Graph { | |
138 | # type N; | |
139 | # type E; | |
140 | # fn has_edge(&self, &Self::N, &Self::N) -> bool; | |
141 | # fn edges(&self, &Self::N) -> Vec<Self::E>; | |
142 | # } | |
143 | # struct Node; | |
144 | # struct Edge; | |
145 | # struct MyGraph; | |
146 | # impl Graph for MyGraph { | |
147 | # type N = Node; | |
148 | # type E = Edge; | |
149 | # fn has_edge(&self, n1: &Node, n2: &Node) -> bool { | |
150 | # true | |
151 | # } | |
152 | # fn edges(&self, n: &Node) -> Vec<Edge> { | |
153 | # Vec::new() | |
154 | # } | |
155 | # } | |
156 | let graph = MyGraph; | |
157 | let obj = Box::new(graph) as Box<Graph>; | |
158 | ``` | |
159 | ||
160 | You’ll get two errors: | |
161 | ||
162 | ```text | |
163 | error: the value of the associated type `E` (from the trait `main::Graph`) must | |
164 | be specified [E0191] | |
165 | let obj = Box::new(graph) as Box<Graph>; | |
166 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
167 | 24:44 error: the value of the associated type `N` (from the trait | |
168 | `main::Graph`) must be specified [E0191] | |
169 | let obj = Box::new(graph) as Box<Graph>; | |
170 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
171 | ``` | |
172 | ||
173 | We can’t create a trait object like this, because we don’t know the associated | |
174 | types. Instead, we can write this: | |
175 | ||
176 | ```rust | |
177 | # trait Graph { | |
178 | # type N; | |
179 | # type E; | |
180 | # fn has_edge(&self, &Self::N, &Self::N) -> bool; | |
181 | # fn edges(&self, &Self::N) -> Vec<Self::E>; | |
182 | # } | |
183 | # struct Node; | |
184 | # struct Edge; | |
185 | # struct MyGraph; | |
186 | # impl Graph for MyGraph { | |
187 | # type N = Node; | |
188 | # type E = Edge; | |
189 | # fn has_edge(&self, n1: &Node, n2: &Node) -> bool { | |
190 | # true | |
191 | # } | |
192 | # fn edges(&self, n: &Node) -> Vec<Edge> { | |
193 | # Vec::new() | |
194 | # } | |
195 | # } | |
196 | let graph = MyGraph; | |
197 | let obj = Box::new(graph) as Box<Graph<N=Node, E=Edge>>; | |
198 | ``` | |
199 | ||
200 | The `N=Node` syntax allows us to provide a concrete type, `Node`, for the `N` | |
9346a6ac | 201 | type parameter. Same with `E=Edge`. If we didn’t provide this constraint, we |
c34b1796 | 202 | couldn’t be sure which `impl` to match this trait object to. |