]>
Commit | Line | Data |
---|---|---|
abe05a73 XL |
1 | ## Using Trait Objects that Allow for Values of Different Types |
2 | ||
83c7162d XL |
3 | In Chapter 8, we mentioned that one limitation of vectors is that they can |
4 | store elements of only one type. We created a workaround in Listing 8-10 where | |
5 | we defined a `SpreadsheetCell` enum that had variants to hold integers, floats, | |
abe05a73 XL |
6 | and text. This meant we could store different types of data in each cell and |
7 | still have a vector that represented a row of cells. This is a perfectly good | |
8 | solution when our interchangeable items are a fixed set of types that we know | |
0531ce1d XL |
9 | when our code is compiled. |
10 | ||
11 | However, sometimes we want our library user to be able to extend the set of | |
12 | types that are valid in a particular situation. To show how we might achieve | |
13 | this, we’ll create an example graphical user interface (GUI) tool that iterates | |
14 | through a list of items, calling a `draw` method on each one to draw it to the | |
15 | screen—a common technique for GUI tools. We’ll create a library crate called | |
16 | `gui` that contains the structure of a GUI library. This crate might include | |
17 | some types for people to use, such as `Button` or `TextField`. In addition, | |
18 | `gui` users will want to create their own types that can be drawn: for | |
19 | instance, one programmer might add an `Image` and another might add a | |
20 | `SelectBox`. | |
21 | ||
22 | We won’t implement a fully fledged GUI library for this example but will show | |
abe05a73 | 23 | how the pieces would fit together. At the time of writing the library, we can’t |
0531ce1d XL |
24 | know and define all the types other programmers might want to create. But we do |
25 | know that `gui` needs to keep track of many values of different types, and it | |
26 | needs to call a `draw` method on each of these differently typed values. It | |
27 | doesn’t need to know exactly what will happen when we call the `draw` method, | |
28 | just that the value will have that method available for us to call. | |
abe05a73 XL |
29 | |
30 | To do this in a language with inheritance, we might define a class named | |
0531ce1d XL |
31 | `Component` that has a method named `draw` on it. The other classes, such as |
32 | `Button`, `Image`, and `SelectBox`, would inherit from `Component` and thus | |
abe05a73 XL |
33 | inherit the `draw` method. They could each override the `draw` method to define |
34 | their custom behavior, but the framework could treat all of the types as if | |
0531ce1d XL |
35 | they were `Component` instances and call `draw` on them. But because Rust |
36 | doesn’t have inheritance, we need another way to structure the `gui` library to | |
37 | allow users to extend it with new types. | |
abe05a73 XL |
38 | |
39 | ### Defining a Trait for Common Behavior | |
40 | ||
0531ce1d XL |
41 | To implement the behavior we want `gui` to have, we’ll define a trait named |
42 | `Draw` that will have one method named `draw`. Then we can define a vector that | |
43 | takes a *trait object*. A trait object points to an instance of a type that | |
44 | implements the trait we specify. We create a trait object by specifying some | |
45 | sort of pointer, such as a `&` reference or a `Box<T>` smart pointer, and then | |
83c7162d XL |
46 | specifying the relevant trait. (We’ll talk about the reason trait objects must |
47 | use a pointer in Chapter 19 in the section “Dynamically Sized Types & Sized”.) | |
0531ce1d XL |
48 | We can use trait objects in place of a generic or concrete type. Wherever we |
49 | use a trait object, Rust’s type system will ensure at compile time that any | |
50 | value used in that context will implement the trait object’s trait. | |
51 | Consequently, we don’t need to know all the possible types at compile time. | |
abe05a73 | 52 | |
83c7162d | 53 | We’ve mentioned that in Rust, we refrain from calling structs and enums |
abe05a73 | 54 | “objects” to distinguish them from other languages’ objects. In a struct or |
0531ce1d | 55 | enum, the data in the struct fields and the behavior in `impl` blocks are |
83c7162d | 56 | separated, whereas in other languages, the data and behavior combined into one |
0531ce1d XL |
57 | concept is often labeled an object. However, trait objects *are* more like |
58 | objects in other languages in the sense that they combine data and behavior. | |
59 | But trait objects differ from traditional objects in that we can’t add data to | |
60 | a trait object. Trait objects aren’t as generally useful as objects in other | |
61 | languages: their specific purpose is to allow abstraction across common | |
62 | behavior. | |
abe05a73 XL |
63 | |
64 | Listing 17-3 shows how to define a trait named `Draw` with one method named | |
65 | `draw`: | |
cc61c64b XL |
66 | |
67 | <span class="filename">Filename: src/lib.rs</span> | |
68 | ||
69 | ```rust | |
70 | pub trait Draw { | |
71 | fn draw(&self); | |
72 | } | |
73 | ``` | |
74 | ||
75 | <span class="caption">Listing 17-3: Definition of the `Draw` trait</span> | |
76 | ||
0531ce1d XL |
77 | This syntax should look familiar from our discussions on how to define traits |
78 | in Chapter 10. Next comes some new syntax: Listing 17-4 defines a struct named | |
abe05a73 | 79 | `Screen` that holds a vector named `components`. This vector is of type |
83c7162d | 80 | `Box<Draw>`, which is a trait object; it’s a stand-in for any type inside a |
abe05a73 | 81 | `Box` that implements the `Draw` trait. |
cc61c64b | 82 | |
cc61c64b XL |
83 | <span class="filename">Filename: src/lib.rs</span> |
84 | ||
85 | ```rust | |
86 | # pub trait Draw { | |
87 | # fn draw(&self); | |
88 | # } | |
89 | # | |
90 | pub struct Screen { | |
91 | pub components: Vec<Box<Draw>>, | |
92 | } | |
93 | ``` | |
94 | ||
95 | <span class="caption">Listing 17-4: Definition of the `Screen` struct with a | |
abe05a73 XL |
96 | `components` field holding a vector of trait objects that implement the `Draw` |
97 | trait</span> | |
cc61c64b | 98 | |
abe05a73 XL |
99 | On the `Screen` struct, we’ll define a method named `run` that will call the |
100 | `draw` method on each of its `components`, as shown in Listing 17-5: | |
cc61c64b XL |
101 | |
102 | <span class="filename">Filename: src/lib.rs</span> | |
103 | ||
104 | ```rust | |
105 | # pub trait Draw { | |
106 | # fn draw(&self); | |
107 | # } | |
108 | # | |
109 | # pub struct Screen { | |
110 | # pub components: Vec<Box<Draw>>, | |
111 | # } | |
112 | # | |
113 | impl Screen { | |
114 | pub fn run(&self) { | |
115 | for component in self.components.iter() { | |
116 | component.draw(); | |
117 | } | |
118 | } | |
119 | } | |
120 | ``` | |
121 | ||
83c7162d XL |
122 | <span class="caption">Listing 17-5: A `run` method on `Screen` that calls the |
123 | `draw` method on each component</span> | |
cc61c64b | 124 | |
0531ce1d XL |
125 | This works differently than defining a struct that uses a generic type |
126 | parameter with trait bounds. A generic type parameter can only be substituted | |
127 | with one concrete type at a time, whereas trait objects allow for multiple | |
128 | concrete types to fill in for the trait object at runtime. For example, we | |
129 | could have defined the `Screen` struct using a generic type and a trait bound | |
130 | as in Listing 17-6: | |
cc61c64b XL |
131 | |
132 | <span class="filename">Filename: src/lib.rs</span> | |
133 | ||
134 | ```rust | |
135 | # pub trait Draw { | |
136 | # fn draw(&self); | |
137 | # } | |
138 | # | |
139 | pub struct Screen<T: Draw> { | |
140 | pub components: Vec<T>, | |
141 | } | |
142 | ||
143 | impl<T> Screen<T> | |
144 | where T: Draw { | |
145 | pub fn run(&self) { | |
146 | for component in self.components.iter() { | |
147 | component.draw(); | |
148 | } | |
149 | } | |
150 | } | |
151 | ``` | |
152 | ||
153 | <span class="caption">Listing 17-6: An alternate implementation of the `Screen` | |
154 | struct and its `run` method using generics and trait bounds</span> | |
155 | ||
abe05a73 XL |
156 | This restricts us to a `Screen` instance that has a list of components all of |
157 | type `Button` or all of type `TextField`. If you’ll only ever have homogeneous | |
0531ce1d | 158 | collections, using generics and trait bounds is preferable because the |
abe05a73 | 159 | definitions will be monomorphized at compile time to use the concrete types. |
cc61c64b | 160 | |
0531ce1d | 161 | On the other hand, with the method using trait objects, one `Screen` instance |
94b46f34 XL |
162 | can hold a `Vec<T>` that contains a `Box<Button>` as well as a |
163 | `Box<TextField>`. Let’s look at how this works, and then we’ll talk about the | |
164 | runtime performance implications. | |
cc61c64b | 165 | |
abe05a73 | 166 | ### Implementing the Trait |
cc61c64b | 167 | |
0531ce1d XL |
168 | Now we’ll add some types that implement the `Draw` trait. We’ll provide the |
169 | `Button` type. Again, actually implementing a GUI library is beyond the scope | |
170 | of this book, so the `draw` method won’t have any useful implementation in its | |
171 | body. To imagine what the implementation might look like, a `Button` struct | |
172 | might have fields for `width`, `height`, and `label`, as shown in Listing 17-7: | |
cc61c64b XL |
173 | |
174 | <span class="filename">Filename: src/lib.rs</span> | |
175 | ||
176 | ```rust | |
177 | # pub trait Draw { | |
178 | # fn draw(&self); | |
179 | # } | |
180 | # | |
181 | pub struct Button { | |
182 | pub width: u32, | |
183 | pub height: u32, | |
184 | pub label: String, | |
185 | } | |
186 | ||
187 | impl Draw for Button { | |
188 | fn draw(&self) { | |
83c7162d | 189 | // code to actually draw a button |
cc61c64b XL |
190 | } |
191 | } | |
192 | ``` | |
193 | ||
194 | <span class="caption">Listing 17-7: A `Button` struct that implements the | |
195 | `Draw` trait</span> | |
196 | ||
abe05a73 | 197 | The `width`, `height`, and `label` fields on `Button` will differ from the |
0531ce1d XL |
198 | fields on other components, such as a `TextField` type, that might have those |
199 | fields plus a `placeholder` field instead. Each of the types we want to draw on | |
200 | the screen will implement the `Draw` trait but will use different code in the | |
83c7162d XL |
201 | `draw` method to define how to draw that particular type, as `Button` has here |
202 | (without the actual GUI code, which is beyond the scope of this chapter). The | |
203 | `Button` type, for instance, might have an additional `impl` block containing | |
0531ce1d XL |
204 | methods related to what happens when a user clicks the button. These kinds of |
205 | methods won’t apply to types like `TextField`. | |
206 | ||
207 | If someone using our library decides to implement a `SelectBox` struct that has | |
208 | `width`, `height`, and `options` fields, they implement the `Draw` trait on the | |
209 | `SelectBox` type as well, as shown in Listing 17-8: | |
cc61c64b XL |
210 | |
211 | <span class="filename">Filename: src/main.rs</span> | |
212 | ||
213 | ```rust,ignore | |
0531ce1d XL |
214 | extern crate gui; |
215 | use gui::Draw; | |
cc61c64b XL |
216 | |
217 | struct SelectBox { | |
218 | width: u32, | |
219 | height: u32, | |
220 | options: Vec<String>, | |
221 | } | |
222 | ||
223 | impl Draw for SelectBox { | |
224 | fn draw(&self) { | |
83c7162d | 225 | // code to actually draw a select box |
cc61c64b XL |
226 | } |
227 | } | |
228 | ``` | |
229 | ||
0531ce1d XL |
230 | <span class="caption">Listing 17-8: Another crate using `gui` and implementing |
231 | the `Draw` trait on a `SelectBox` struct</span> | |
cc61c64b | 232 | |
0531ce1d XL |
233 | Our library’s user can now write their `main` function to create a `Screen` |
234 | instance. To the `Screen` instance, they can add a `SelectBox` and a `Button` | |
235 | by putting each in a `Box<T>` to become a trait object. They can then call the | |
236 | `run` method on the `Screen` instance, which will call `draw` on each of the | |
cc61c64b XL |
237 | components. Listing 17-9 shows this implementation: |
238 | ||
239 | <span class="filename">Filename: src/main.rs</span> | |
240 | ||
241 | ```rust,ignore | |
0531ce1d | 242 | use gui::{Screen, Button}; |
cc61c64b XL |
243 | |
244 | fn main() { | |
245 | let screen = Screen { | |
246 | components: vec![ | |
247 | Box::new(SelectBox { | |
248 | width: 75, | |
249 | height: 10, | |
250 | options: vec![ | |
251 | String::from("Yes"), | |
252 | String::from("Maybe"), | |
253 | String::from("No") | |
254 | ], | |
255 | }), | |
256 | Box::new(Button { | |
257 | width: 50, | |
258 | height: 10, | |
259 | label: String::from("OK"), | |
260 | }), | |
261 | ], | |
262 | }; | |
263 | ||
264 | screen.run(); | |
265 | } | |
266 | ``` | |
267 | ||
268 | <span class="caption">Listing 17-9: Using trait objects to store values of | |
269 | different types that implement the same trait</span> | |
270 | ||
0531ce1d XL |
271 | When we wrote the library, we didn’t know that someone might add the |
272 | `SelectBox` type, but our `Screen` implementation was able to operate on the | |
273 | new type and draw it because `SelectBox` implements the `Draw` type, which | |
274 | means it implements the `draw` method. | |
275 | ||
276 | This concept—of being concerned only with the messages a value responds to | |
277 | rather than the value’s concrete type—is similar to the concept *duck typing* | |
278 | in dynamically typed languages: if it walks like a duck and quacks like a duck, | |
279 | then it must be a duck! In the implementation of `run` on `Screen` in Listing | |
280 | 17-5, `run` doesn’t need to know what the concrete type of each component is. | |
281 | It doesn’t check whether a component is an instance of a `Button` or a | |
282 | `SelectBox`, it just calls the `draw` method on the component. By specifying | |
283 | `Box<Draw>` as the type of the values in the `components` vector, we’ve defined | |
284 | `Screen` to need values that we can call the `draw` method on. | |
285 | ||
286 | The advantage of using trait objects and Rust’s type system to write code | |
83c7162d XL |
287 | similar to code using duck typing is that we never have to check whether a |
288 | value implements a particular method at runtime or worry about getting errors | |
289 | if a value doesn’t implement a method but we call it anyway. Rust won’t compile | |
290 | our code if the values don’t implement the traits that the trait objects need. | |
cc61c64b XL |
291 | |
292 | For example, Listing 17-10 shows what happens if we try to create a `Screen` | |
293 | with a `String` as a component: | |
294 | ||
295 | <span class="filename">Filename: src/main.rs</span> | |
296 | ||
297 | ```rust,ignore | |
0531ce1d XL |
298 | extern crate gui; |
299 | use gui::Screen; | |
cc61c64b XL |
300 | |
301 | fn main() { | |
302 | let screen = Screen { | |
303 | components: vec![ | |
304 | Box::new(String::from("Hi")), | |
305 | ], | |
306 | }; | |
307 | ||
308 | screen.run(); | |
309 | } | |
310 | ``` | |
311 | ||
3b2f2976 XL |
312 | <span class="caption">Listing 17-10: Attempting to use a type that doesn’t |
313 | implement the trait object’s trait</span> | |
cc61c64b | 314 | |
0531ce1d | 315 | We’ll get this error because `String` doesn’t implement the `Draw` trait: |
cc61c64b XL |
316 | |
317 | ```text | |
0531ce1d XL |
318 | error[E0277]: the trait bound `std::string::String: gui::Draw` is not satisfied |
319 | --> src/main.rs:7:13 | |
cc61c64b | 320 | | |
0531ce1d XL |
321 | 7 | Box::new(String::from("Hi")), |
322 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait gui::Draw is not | |
cc61c64b XL |
323 | implemented for `std::string::String` |
324 | | | |
0531ce1d | 325 | = note: required for the cast to the object type `gui::Draw` |
cc61c64b XL |
326 | ``` |
327 | ||
0531ce1d | 328 | This error lets us know that either we’re passing something to `Screen` we |
83c7162d | 329 | didn’t mean to pass and we should pass a different type or we should implement |
0531ce1d | 330 | `Draw` on `String` so that `Screen` is able to call `draw` on it. |
cc61c64b XL |
331 | |
332 | ### Trait Objects Perform Dynamic Dispatch | |
333 | ||
0531ce1d XL |
334 | Recall in the “Performance of Code Using Generics” section in Chapter 10 our |
335 | discussion on the monomorphization process performed by the compiler when we | |
83c7162d XL |
336 | use trait bounds on generics: the compiler generates nongeneric implementations |
337 | of functions and methods for each concrete type that we use in place of a | |
338 | generic type parameter. The code that results from monomorphization is doing | |
339 | *static dispatch*, which is when the compiler knows what method you’re calling | |
340 | at compile time. This is opposed to *dynamic dispatch*, which is when the | |
341 | compiler can’t tell at compile time which method you’re calling. In dynamic | |
0531ce1d XL |
342 | dispatch cases, the compiler emits code that at runtime will figure out which |
343 | method to call. | |
344 | ||
345 | When we use trait objects, Rust must use dynamic dispatch. The compiler doesn’t | |
346 | know all the types that might be used with the code that is using trait | |
abe05a73 | 347 | objects, so it doesn’t know which method implemented on which type to call. |
0531ce1d | 348 | Instead, at runtime, Rust uses the pointers inside the trait object to know |
83c7162d XL |
349 | which method to call. There is a runtime cost when this lookup happens that |
350 | doesn’t occur with static dispatch. Dynamic dispatch also prevents the compiler | |
351 | from choosing to inline a method’s code, which in turn prevents some | |
0531ce1d XL |
352 | optimizations. However, we did get extra flexibility in the code that we wrote |
353 | in Listing 17-5 and were able to support in Listing 17-9, so it’s a trade-off | |
354 | to consider. | |
355 | ||
356 | ### Object Safety Is Required for Trait Objects | |
357 | ||
83c7162d | 358 | You can only make *object-safe* traits into trait objects. Some complex rules |
0531ce1d XL |
359 | govern all the properties that make a trait object safe, but in practice, only |
360 | two rules are relevant. A trait is object safe if all the methods defined in | |
361 | the trait have the following properties: | |
362 | ||
363 | * The return type isn’t `Self`. | |
364 | * There are no generic type parameters. | |
365 | ||
366 | The `Self` keyword is an alias for the type we’re implementing the traits or | |
367 | methods on. Trait objects must be object safe because once you’ve used a trait | |
368 | object, Rust no longer knows the concrete type that’s implementing that trait. | |
369 | If a trait method returns the concrete `Self` type, but a trait object forgets | |
370 | the exact type that `Self` is, there is no way the method can use the original | |
371 | concrete type. The same is true of generic type parameters that are filled in | |
372 | with concrete type parameters when the trait is used: the concrete types become | |
373 | part of the type that implements the trait. When the type is forgotten through | |
374 | the use of a trait object, there is no way to know what types to fill in the | |
375 | generic type parameters with. | |
cc61c64b XL |
376 | |
377 | An example of a trait whose methods are not object safe is the standard | |
3b2f2976 | 378 | library’s `Clone` trait. The signature for the `clone` method in the `Clone` |
cc61c64b XL |
379 | trait looks like this: |
380 | ||
381 | ```rust | |
382 | pub trait Clone { | |
383 | fn clone(&self) -> Self; | |
384 | } | |
385 | ``` | |
386 | ||
0531ce1d XL |
387 | The `String` type implements the `Clone` trait, and when we call the `clone` |
388 | method on an instance of `String` we get back an instance of `String`. | |
94b46f34 XL |
389 | Similarly, if we call `clone` on an instance of `Vec<T>`, we get back an |
390 | instance of `Vec<T>`. The signature of `clone` needs to know what type will | |
391 | stand in for `Self`, because that’s the return type. | |
cc61c64b | 392 | |
0531ce1d | 393 | The compiler will indicate when you’re trying to do something that violates the |
83c7162d | 394 | rules of object safety in regard to trait objects. For example, let’s say we |
cc61c64b XL |
395 | tried to implement the `Screen` struct in Listing 17-4 to hold types that |
396 | implement the `Clone` trait instead of the `Draw` trait, like this: | |
397 | ||
398 | ```rust,ignore | |
399 | pub struct Screen { | |
400 | pub components: Vec<Box<Clone>>, | |
401 | } | |
402 | ``` | |
403 | ||
0531ce1d | 404 | We would get this error: |
cc61c64b XL |
405 | |
406 | ```text | |
407 | error[E0038]: the trait `std::clone::Clone` cannot be made into an object | |
0531ce1d | 408 | --> src/lib.rs:2:5 |
cc61c64b XL |
409 | | |
410 | 2 | pub components: Vec<Box<Clone>>, | |
411 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` cannot be | |
0531ce1d | 412 | made into an object |
cc61c64b XL |
413 | | |
414 | = note: the trait cannot require that `Self : Sized` | |
415 | ``` | |
416 | ||
0531ce1d XL |
417 | This error means you can’t use this trait as a trait object in this way. If |
418 | you’re interested in more details on object safety, see [Rust RFC 255]. | |
abe05a73 XL |
419 | |
420 | [Rust RFC 255]: https://github.com/rust-lang/rfcs/blob/master/text/0255-object-safety.md |