]>
Commit | Line | Data |
---|---|---|
9fa01778 | 1 | ## Using Trait Objects That Allow for Values of Different Types |
13cf67c4 XL |
2 | |
3 | In Chapter 8, we mentioned that one limitation of vectors is that they can | |
923072b8 | 4 | store elements of only one type. We created a workaround in Listing 8-9 where |
13cf67c4 XL |
5 | we defined a `SpreadsheetCell` enum that had variants to hold integers, floats, |
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 | |
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 | |
23 | how the pieces would fit together. At the time of writing the library, we can’t | |
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. | |
29 | ||
30 | To do this in a language with inheritance, we might define a class named | |
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 | |
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 | |
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. | |
38 | ||
39 | ### Defining a Trait for Common Behavior | |
40 | ||
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 | |
9fa01778 | 43 | takes a *trait object*. A trait object points to both an instance of a type |
923072b8 FG |
44 | implementing our specified trait and a table used to look up trait methods on |
45 | that type at runtime. We create a trait object by specifying some sort of | |
46 | pointer, such as a `&` reference or a `Box<T>` smart pointer, then the `dyn` | |
47 | keyword, and then specifying the relevant trait. (We’ll talk about the reason | |
48 | trait objects must use a pointer in Chapter 19 in the section [“Dynamically | |
49 | Sized Types and the `Sized` Trait.”][dynamically-sized]<!-- ignore -->) We can | |
50 | use trait objects in place of a generic or concrete type. Wherever we use a | |
51 | trait object, Rust’s type system will ensure at compile time that any value | |
52 | used in that context will implement the trait object’s trait. Consequently, we | |
53 | don’t need to know all the possible types at compile time. | |
54 | ||
55 | We’ve mentioned that, in Rust, we refrain from calling structs and enums | |
13cf67c4 XL |
56 | “objects” to distinguish them from other languages’ objects. In a struct or |
57 | enum, the data in the struct fields and the behavior in `impl` blocks are | |
58 | separated, whereas in other languages, the data and behavior combined into one | |
59 | concept is often labeled an object. However, trait objects *are* more like | |
60 | objects in other languages in the sense that they combine data and behavior. | |
61 | But trait objects differ from traditional objects in that we can’t add data to | |
62 | a trait object. Trait objects aren’t as generally useful as objects in other | |
63 | languages: their specific purpose is to allow abstraction across common | |
64 | behavior. | |
65 | ||
66 | Listing 17-3 shows how to define a trait named `Draw` with one method named | |
67 | `draw`: | |
68 | ||
69 | <span class="filename">Filename: src/lib.rs</span> | |
70 | ||
5869c6ff | 71 | ```rust,noplayground |
74b04a01 | 72 | {{#rustdoc_include ../listings/ch17-oop/listing-17-03/src/lib.rs}} |
13cf67c4 XL |
73 | ``` |
74 | ||
75 | <span class="caption">Listing 17-3: Definition of the `Draw` trait</span> | |
76 | ||
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 | |
79 | `Screen` that holds a vector named `components`. This vector is of type | |
80 | `Box<dyn Draw>`, which is a trait object; it’s a stand-in for any type inside | |
81 | a `Box` that implements the `Draw` trait. | |
82 | ||
83 | <span class="filename">Filename: src/lib.rs</span> | |
84 | ||
5869c6ff | 85 | ```rust,noplayground |
74b04a01 | 86 | {{#rustdoc_include ../listings/ch17-oop/listing-17-04/src/lib.rs:here}} |
13cf67c4 XL |
87 | ``` |
88 | ||
89 | <span class="caption">Listing 17-4: Definition of the `Screen` struct with a | |
90 | `components` field holding a vector of trait objects that implement the `Draw` | |
91 | trait</span> | |
92 | ||
93 | On the `Screen` struct, we’ll define a method named `run` that will call the | |
94 | `draw` method on each of its `components`, as shown in Listing 17-5: | |
95 | ||
96 | <span class="filename">Filename: src/lib.rs</span> | |
97 | ||
5869c6ff | 98 | ```rust,noplayground |
74b04a01 | 99 | {{#rustdoc_include ../listings/ch17-oop/listing-17-05/src/lib.rs:here}} |
13cf67c4 XL |
100 | ``` |
101 | ||
102 | <span class="caption">Listing 17-5: A `run` method on `Screen` that calls the | |
103 | `draw` method on each component</span> | |
104 | ||
532ac7d7 | 105 | This works differently from defining a struct that uses a generic type |
13cf67c4 XL |
106 | parameter with trait bounds. A generic type parameter can only be substituted |
107 | with one concrete type at a time, whereas trait objects allow for multiple | |
108 | concrete types to fill in for the trait object at runtime. For example, we | |
109 | could have defined the `Screen` struct using a generic type and a trait bound | |
110 | as in Listing 17-6: | |
111 | ||
112 | <span class="filename">Filename: src/lib.rs</span> | |
113 | ||
5869c6ff | 114 | ```rust,noplayground |
74b04a01 | 115 | {{#rustdoc_include ../listings/ch17-oop/listing-17-06/src/lib.rs:here}} |
13cf67c4 XL |
116 | ``` |
117 | ||
118 | <span class="caption">Listing 17-6: An alternate implementation of the `Screen` | |
119 | struct and its `run` method using generics and trait bounds</span> | |
120 | ||
121 | This restricts us to a `Screen` instance that has a list of components all of | |
122 | type `Button` or all of type `TextField`. If you’ll only ever have homogeneous | |
123 | collections, using generics and trait bounds is preferable because the | |
124 | definitions will be monomorphized at compile time to use the concrete types. | |
125 | ||
126 | On the other hand, with the method using trait objects, one `Screen` instance | |
9fa01778 XL |
127 | can hold a `Vec<T>` that contains a `Box<Button>` as well as a |
128 | `Box<TextField>`. Let’s look at how this works, and then we’ll talk about the | |
129 | runtime performance implications. | |
13cf67c4 XL |
130 | |
131 | ### Implementing the Trait | |
132 | ||
133 | Now we’ll add some types that implement the `Draw` trait. We’ll provide the | |
134 | `Button` type. Again, actually implementing a GUI library is beyond the scope | |
135 | of this book, so the `draw` method won’t have any useful implementation in its | |
136 | body. To imagine what the implementation might look like, a `Button` struct | |
137 | might have fields for `width`, `height`, and `label`, as shown in Listing 17-7: | |
138 | ||
139 | <span class="filename">Filename: src/lib.rs</span> | |
140 | ||
fc512014 | 141 | ```rust,noplayground |
74b04a01 | 142 | {{#rustdoc_include ../listings/ch17-oop/listing-17-07/src/lib.rs:here}} |
13cf67c4 XL |
143 | ``` |
144 | ||
145 | <span class="caption">Listing 17-7: A `Button` struct that implements the | |
146 | `Draw` trait</span> | |
147 | ||
148 | The `width`, `height`, and `label` fields on `Button` will differ from the | |
923072b8 FG |
149 | fields on other components; for example, a `TextField` type might have those |
150 | same fields plus a `placeholder` field. Each of the types we want to draw on | |
13cf67c4 XL |
151 | the screen will implement the `Draw` trait but will use different code in the |
152 | `draw` method to define how to draw that particular type, as `Button` has here | |
923072b8 FG |
153 | (without the actual GUI code, as mentioned). The `Button` type, for instance, |
154 | might have an additional `impl` block containing methods related to what | |
155 | happens when a user clicks the button. These kinds of methods won’t apply to | |
156 | types like `TextField`. | |
13cf67c4 XL |
157 | |
158 | If someone using our library decides to implement a `SelectBox` struct that has | |
159 | `width`, `height`, and `options` fields, they implement the `Draw` trait on the | |
160 | `SelectBox` type as well, as shown in Listing 17-8: | |
161 | ||
162 | <span class="filename">Filename: src/main.rs</span> | |
163 | ||
164 | ```rust,ignore | |
74b04a01 | 165 | {{#rustdoc_include ../listings/ch17-oop/listing-17-08/src/main.rs:here}} |
13cf67c4 XL |
166 | ``` |
167 | ||
168 | <span class="caption">Listing 17-8: Another crate using `gui` and implementing | |
169 | the `Draw` trait on a `SelectBox` struct</span> | |
170 | ||
171 | Our library’s user can now write their `main` function to create a `Screen` | |
172 | instance. To the `Screen` instance, they can add a `SelectBox` and a `Button` | |
173 | by putting each in a `Box<T>` to become a trait object. They can then call the | |
174 | `run` method on the `Screen` instance, which will call `draw` on each of the | |
175 | components. Listing 17-9 shows this implementation: | |
176 | ||
177 | <span class="filename">Filename: src/main.rs</span> | |
178 | ||
179 | ```rust,ignore | |
74b04a01 | 180 | {{#rustdoc_include ../listings/ch17-oop/listing-17-09/src/main.rs:here}} |
13cf67c4 XL |
181 | ``` |
182 | ||
183 | <span class="caption">Listing 17-9: Using trait objects to store values of | |
184 | different types that implement the same trait</span> | |
185 | ||
186 | When we wrote the library, we didn’t know that someone might add the | |
187 | `SelectBox` type, but our `Screen` implementation was able to operate on the | |
9fa01778 | 188 | new type and draw it because `SelectBox` implements the `Draw` trait, which |
13cf67c4 XL |
189 | means it implements the `draw` method. |
190 | ||
191 | This concept—of being concerned only with the messages a value responds to | |
e74abb32 XL |
192 | rather than the value’s concrete type—is similar to the concept of *duck |
193 | typing* in dynamically typed languages: if it walks like a duck and quacks | |
194 | like a duck, then it must be a duck! In the implementation of `run` on `Screen` | |
195 | in Listing 17-5, `run` doesn’t need to know what the concrete type of each | |
196 | component is. It doesn’t check whether a component is an instance of a `Button` | |
197 | or a `SelectBox`, it just calls the `draw` method on the component. By | |
198 | specifying `Box<dyn Draw>` as the type of the values in the `components` | |
199 | vector, we’ve defined `Screen` to need values that we can call the `draw` | |
200 | method on. | |
13cf67c4 XL |
201 | |
202 | The advantage of using trait objects and Rust’s type system to write code | |
203 | similar to code using duck typing is that we never have to check whether a | |
204 | value implements a particular method at runtime or worry about getting errors | |
205 | if a value doesn’t implement a method but we call it anyway. Rust won’t compile | |
206 | our code if the values don’t implement the traits that the trait objects need. | |
207 | ||
208 | For example, Listing 17-10 shows what happens if we try to create a `Screen` | |
209 | with a `String` as a component: | |
210 | ||
211 | <span class="filename">Filename: src/main.rs</span> | |
212 | ||
213 | ```rust,ignore,does_not_compile | |
74b04a01 | 214 | {{#rustdoc_include ../listings/ch17-oop/listing-17-10/src/main.rs}} |
13cf67c4 XL |
215 | ``` |
216 | ||
217 | <span class="caption">Listing 17-10: Attempting to use a type that doesn’t | |
218 | implement the trait object’s trait</span> | |
219 | ||
220 | We’ll get this error because `String` doesn’t implement the `Draw` trait: | |
221 | ||
f035d41b | 222 | ```console |
74b04a01 | 223 | {{#include ../listings/ch17-oop/listing-17-10/output.txt}} |
13cf67c4 XL |
224 | ``` |
225 | ||
226 | This error lets us know that either we’re passing something to `Screen` we | |
923072b8 | 227 | didn’t mean to pass and so should pass a different type or we should implement |
13cf67c4 XL |
228 | `Draw` on `String` so that `Screen` is able to call `draw` on it. |
229 | ||
230 | ### Trait Objects Perform Dynamic Dispatch | |
231 | ||
dc9dc135 XL |
232 | Recall in the [“Performance of Code Using |
233 | Generics”][performance-of-code-using-generics]<!-- ignore --> section in | |
234 | Chapter 10 our discussion on the monomorphization process performed by the | |
235 | compiler when we use trait bounds on generics: the compiler generates | |
923072b8 FG |
236 | nongeneric implementations of functions and methods for each concrete type that |
237 | we use in place of a generic type parameter. The code that results from | |
dc9dc135 XL |
238 | monomorphization is doing *static dispatch*, which is when the compiler knows |
239 | what method you’re calling at compile time. This is opposed to *dynamic | |
240 | dispatch*, which is when the compiler can’t tell at compile time which method | |
241 | you’re calling. In dynamic dispatch cases, the compiler emits code that at | |
242 | runtime will figure out which method to call. | |
13cf67c4 XL |
243 | |
244 | When we use trait objects, Rust must use dynamic dispatch. The compiler doesn’t | |
923072b8 FG |
245 | know all the types that might be used with the code that’s using trait objects, |
246 | so it doesn’t know which method implemented on which type to call. Instead, at | |
247 | runtime, Rust uses the pointers inside the trait object to know which method to | |
248 | call. This lookup incurs a runtime cost that doesn’t occur with static | |
249 | dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a | |
250 | method’s code, which in turn prevents some optimizations. However, we did get | |
251 | extra flexibility in the code that we wrote in Listing 17-5 and were able to | |
252 | support in Listing 17-9, so it’s a trade-off to consider. | |
13cf67c4 | 253 | |
9fa01778 XL |
254 | [performance-of-code-using-generics]: |
255 | ch10-01-syntax.html#performance-of-code-using-generics | |
256 | [dynamically-sized]: ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait |