]>
Commit | Line | Data |
---|---|---|
cc61c64b XL |
1 | |
2 | [TOC] | |
3 | ||
4 | # Is Rust an Object-Oriented Programming Language? | |
5 | ||
6 | Object-Oriented Programming is a way of modeling programs that originated with | |
7 | Simula in the 1960s and became popular with C++ in the 1990s. There are many | |
abe05a73 XL |
8 | competing definitions for what counts as OOP, and under some definitions, Rust |
9 | is object-oriented; under other definitions, it is not. In this chapter, we’ll | |
cc61c64b | 10 | explore some characteristics that are commonly considered to be object-oriented |
abe05a73 XL |
11 | and how those characteristics translate to idiomatic Rust. We’ll then show you |
12 | how to implement an object-oriented design pattern in Rust and discuss the | |
13 | tradeoffs of doing so versus implementing a solution using some of Rust’s | |
14 | strengths instead. | |
cc61c64b XL |
15 | |
16 | ## What Does Object-Oriented Mean? | |
17 | ||
abe05a73 XL |
18 | There’s no consensus in the programming community about what features a |
19 | language needs in order to be called object-oriented. Rust is influenced by | |
20 | many different programming paradigms including OOP; we explored, for example, | |
21 | the features that came from functional programming in Chapter 13. Arguably, | |
22 | object-oriented programming languages do tend to share certain common | |
23 | characteristics, namely objects, encapsulation, and inheritance. Let’s take a | |
24 | look at what each of those mean and whether Rust supports them. | |
cc61c64b XL |
25 | |
26 | ### Objects Contain Data and Behavior | |
27 | ||
abe05a73 XL |
28 | <!-- Is there a reason we're using this book as the reference, is it generally |
29 | accepted as an authority? --> | |
30 | <!-- Yes, it is. For example, Martin Fowler (himself regarded as an authority) | |
31 | had this to say about it https://www.martinfowler.com/bliki/GangOfFour.html: | |
32 | > In my view the Gang of Four is the best book ever written on object-oriented | |
33 | > design - possibly of any style of design. | |
34 | /Carol --> | |
35 | ||
3b2f2976 XL |
36 | The book “Design Patterns: Elements of Reusable Object-Oriented Software,” |
37 | colloquially referred to as “The Gang of Four book,” is a catalog of | |
cc61c64b XL |
38 | object-oriented design patterns. It defines object-oriented programming in this |
39 | way: | |
40 | ||
41 | > Object-oriented programs are made up of objects. An *object* packages both | |
42 | > data and the procedures that operate on that data. The procedures are | |
43 | > typically called *methods* or *operations*. | |
44 | ||
45 | Under this definition, then, Rust is object-oriented: structs and enums have | |
46 | data and `impl` blocks provide methods on structs and enums. Even though | |
3b2f2976 | 47 | structs and enums with methods aren’t *called* objects, they provide the same |
abe05a73 | 48 | functionality, under the Gang of Four’s definition of objects. |
cc61c64b XL |
49 | |
50 | ### Encapsulation that Hides Implementation Details | |
51 | ||
52 | Another aspect commonly associated with object-oriented programming is the idea | |
abe05a73 XL |
53 | of *encapsulation*: that the implementation details of an object aren’t |
54 | accessible to code using that object. The only way to interact with an object | |
55 | therefore is through its public API; code using the object should not be able | |
56 | to reach into the object’s internals and change data or behavior directly. This | |
57 | enables the programmer to change and refactor an object’s internals without | |
cc61c64b XL |
58 | needing to change the code that uses the object. |
59 | ||
abe05a73 XL |
60 | We discussed an example of this in Chapter 7: We can use the `pub` keyword to |
61 | decide what modules, types, functions, and methods in our code should be | |
62 | public, and by default everything else is private. For example, we can define a | |
63 | struct `AveragedCollection` that has a field containing a vector of `i32` | |
64 | values. The struct can also have a field that contains the average of the | |
65 | values in the vector, meaning the average doesn’t have to be computed on-demand | |
66 | whenever anyone needs it. In other words, `AveragedCollection` will cache the | |
67 | calculated average for us. Listing 17-1 has the definition of the | |
68 | `AveragedCollection` struct: | |
cc61c64b XL |
69 | |
70 | Filename: src/lib.rs | |
71 | ||
72 | ``` | |
73 | pub struct AveragedCollection { | |
74 | list: Vec<i32>, | |
75 | average: f64, | |
76 | } | |
77 | ``` | |
78 | ||
79 | Listing 17-1: An `AveragedCollection` struct that maintains a list of integers | |
80 | and the average of the items in the collection. | |
81 | ||
abe05a73 XL |
82 | The struct itself is marked `pub` so that other code may use it, but the fields |
83 | within the struct remain private. This is important in this case because we | |
84 | want to ensure that whenever a value is added or removed from the list, the | |
85 | average is also updated. We do this by implementing `add`, `remove`, and | |
86 | `average` methods on the struct as shown in Listing 17-2: | |
cc61c64b XL |
87 | |
88 | Filename: src/lib.rs | |
89 | ||
90 | ``` | |
91 | impl AveragedCollection { | |
92 | pub fn add(&mut self, value: i32) { | |
93 | self.list.push(value); | |
94 | self.update_average(); | |
95 | } | |
96 | ||
97 | pub fn remove(&mut self) -> Option<i32> { | |
98 | let result = self.list.pop(); | |
99 | match result { | |
100 | Some(value) => { | |
101 | self.update_average(); | |
102 | Some(value) | |
103 | }, | |
104 | None => None, | |
105 | } | |
106 | } | |
107 | ||
108 | pub fn average(&self) -> f64 { | |
109 | self.average | |
110 | } | |
111 | ||
112 | fn update_average(&mut self) { | |
113 | let total: i32 = self.list.iter().sum(); | |
114 | self.average = total as f64 / self.list.len() as f64; | |
115 | } | |
116 | } | |
117 | ``` | |
118 | ||
abe05a73 XL |
119 | Listing 17-2: Implementations of the public methods `add`, `remove`, and |
120 | `average` on `AveragedCollection` | |
cc61c64b XL |
121 | |
122 | The public methods `add`, `remove`, and `average` are the only way to modify an | |
abe05a73 XL |
123 | instance of `AveragedCollection`. When an item is added to `list` using the |
124 | `add` method or removed using the `remove` method, the implementations of each | |
125 | call the private `update_average` method that takes care of updating the | |
126 | `average` field as well. | |
127 | ||
128 | We leave the `list` and `average` fields private so that there’s no way for | |
129 | external code to add or remove items to the `list` field directly, otherwise | |
130 | the `average` field might become out of sync when the `list` changes. The | |
131 | `average` method returns the value in the `average` field, allowing external | |
132 | code to read the `average` but not modify it. | |
cc61c64b | 133 | |
3b2f2976 | 134 | Because we’ve encapsulated the implementation details of `AveragedCollection`, |
abe05a73 XL |
135 | we can easily change aspects like the data structure in the future. For |
136 | instance, we could use a `HashSet` instead of a `Vec` for the `list` field. As | |
137 | long as the signatures of the `add`, `remove`, and `average` public methods | |
138 | stay the same, code using `AveragedCollection` wouldn’t need to change. If we | |
139 | made `list` public instead, this wouldn’t necessarily be the case: `HashSet` | |
140 | and `Vec` have different methods for adding and removing items, so the external | |
cc61c64b XL |
141 | code would likely have to change if it was modifying `list` directly. |
142 | ||
143 | If encapsulation is a required aspect for a language to be considered | |
abe05a73 XL |
144 | object-oriented, then Rust meets that requirement. The option to use `pub` or |
145 | not for different parts of code enables encapsulation of implementation details. | |
cc61c64b XL |
146 | |
147 | ### Inheritance as a Type System and as Code Sharing | |
148 | ||
abe05a73 XL |
149 | *Inheritance* is a mechanism whereby an object can inherit from another |
150 | object’s definition, thus gaining the parent object’s data and behavior without | |
151 | you having to define them again. | |
cc61c64b XL |
152 | |
153 | If a language must have inheritance to be an object-oriented language, then | |
abe05a73 XL |
154 | Rust is not. There is no way to define a struct that inherits the parent |
155 | struct’s fields and method implementations. However, if you’re used to having | |
156 | inheritance in your programming toolbox, there are other solutions in Rust | |
157 | depending on your reason for reaching for inheritance in the first place. | |
158 | ||
159 | There are two main reasons to choose inheritance. The first is for re-use of | |
160 | code: you can implement particular behavior for one type, and inheritance | |
161 | enables you to re-use that implementation for a different type. Rust code can | |
162 | be shared using default trait method implementations instead, which we saw in | |
163 | Listing 10-15 when we added a default implementation of the `summary` method on | |
164 | the `Summarizable` trait. Any type implementing the `Summarizable` trait would | |
165 | have the `summary` method available on it without any further code. This is | |
166 | similar to a parent class having an implementation of a method, and an | |
167 | inheriting child class then also having the implementation of the method. We | |
168 | can also choose to override the default implementation of the `summary` method | |
169 | when we implement the `Summarizable` trait, similar to a child class overriding | |
170 | the implementation of a method inherited from a parent class. | |
171 | ||
172 | The second reason to use inheritance relates to the type system: to enable a | |
173 | child type to be used in the same places as the parent type. This is also | |
174 | called *polymorphism*, which means that multiple objects can be substituted for | |
175 | each other at runtime if they share certain characteristics. | |
176 | ||
177 | <!-- What does it mean for objects to have the same shape? --> | |
178 | <!-- The use of "shape" in this context has to do with the roots of "morph" in | |
179 | "polymorphism", but it's not very well defined so I've reworded. /Carol --> | |
cc61c64b XL |
180 | |
181 | <!-- PROD: START BOX --> | |
182 | ||
abe05a73 XL |
183 | > Polymorphism |
184 | > | |
185 | > To many people, polymorphism is synonymous with inheritance. But it’s | |
186 | > actually a more general concept that refers to code that can work with data | |
187 | > of multiple types. For inheritance, those types are generally subclasses. | |
188 | > Rust instead uses generics to abstract over different possible types, and | |
189 | > trait bounds to impose constraints on what those types must provide. This is | |
190 | > sometimes called *bounded parametric polymorphism*. | |
cc61c64b XL |
191 | |
192 | <!-- PROD: END BOX --> | |
193 | ||
cc61c64b | 194 | Inheritance has recently fallen out of favor as a programming design solution |
abe05a73 XL |
195 | in many programming languages because it’s often at risk of sharing more code |
196 | than needs be. Subclasses shouldn’t always share all characteristics of their | |
197 | parent class, but will do so with inheritance. This can make a program’s design | |
198 | less flexible, and introduces the possibility of calling methods on subclasses | |
199 | that don’t make sense or that cause errors because the methods don’t actually | |
200 | apply to the subclass. Some languages will also only allow a subclass to | |
201 | inherit from one class, further restricting the flexibility of a program’s | |
202 | design. | |
203 | ||
204 | For these reasons, Rust chose to take a different approach, using trait objects | |
3b2f2976 | 205 | instead of inheritance. Let’s take a look at how trait objects enable |
cc61c64b XL |
206 | polymorphism in Rust. |
207 | ||
abe05a73 XL |
208 | ## Using Trait Objects that Allow for Values of Different Types |
209 | ||
210 | In Chapter 8, we mentioned that one limitation of vectors is that they can only | |
211 | store elements of one type. We created a workaround in Listing 8-10 where we | |
212 | defined a `SpreadsheetCell` enum that had variants to hold integers, floats, | |
213 | and text. This meant we could store different types of data in each cell and | |
214 | still have a vector that represented a row of cells. This is a perfectly good | |
215 | solution when our interchangeable items are a fixed set of types that we know | |
216 | when our code gets compiled. | |
217 | ||
218 | Sometimes, however, we want the user of our library to be able to extend the | |
219 | set of types that are valid in a particular situation. To show how we might | |
220 | achieve this, we’ll create an example Graphical User Interface tool that | |
221 | iterates through a list of items, calling a `draw` method on each one to drawn | |
222 | it to the screen; a common technique for GUI tools. We’re going to create a | |
223 | library crate containing the structure of a GUI library called `rust_gui`. This | |
224 | crate might include some types for people to use, such as `Button` or | |
225 | `TextField`. On top of these, users of `rust_gui` will want to create their own | |
226 | types that can be drawn on the screen: for instance, one programmer might add | |
227 | an `Image`, another might add a `SelectBox`. | |
228 | ||
229 | We won’t implement a fully-fledged GUI library for this example, but will show | |
230 | how the pieces would fit together. At the time of writing the library, we can’t | |
231 | know and define all the types other programmers will want to create. What we do | |
232 | know is that `rust_gui` needs to keep track of a bunch of values that are of | |
233 | different types, and it needs to be able to call a `draw` method on each of | |
234 | these differently-typed values. It doesn’t need to know exactly what will | |
235 | happen when we call the `draw` method, just that the value will have that | |
236 | method available for us to call. | |
237 | ||
238 | To do this in a language with inheritance, we might define a class named | |
239 | `Component` that has a method named `draw` on it. The other classes like | |
240 | `Button`, `Image`, and `SelectBox` would inherit from `Component` and thus | |
241 | inherit the `draw` method. They could each override the `draw` method to define | |
242 | their custom behavior, but the framework could treat all of the types as if | |
243 | they were `Component` instances and call `draw` on them. But Rust doesn’t have | |
244 | inheritance, so we need another way. | |
245 | ||
246 | ### Defining a Trait for Common Behavior | |
247 | ||
248 | To implement the behavior we want `rust_gui` to have, we’ll define a trait | |
249 | named `Draw` that will have one method named `draw`. Then we can define a | |
250 | vector that takes a *trait object*. A trait object points to an instance of a | |
251 | type that implements the trait we specify. We create a trait object by | |
252 | specifying some sort of pointer, such as a `&` reference or a `Box<T>` smart | |
253 | pointer, and then specifying the relevant trait (we’ll talk about the reason | |
254 | trait objects have to use a pointer in Chapter 19 in the section on Dynamically | |
255 | Sized Types). We can use trait objects in place of a generic or concrete type. | |
256 | Wherever we use a trait object, Rust’s type system will ensure at compile-time | |
257 | that any value used in that context will implement the trait object’s trait. | |
258 | This way we don’t need to know all the possible types at compile time. | |
259 | ||
260 | <!-- What will the trait object do in this case? I've taken this last part of | |
261 | the line from below, but I'm not 100% on that --> | |
262 | <!-- I've moved up more and reworded a bit, hope that clarifies /Carol --> | |
263 | ||
264 | We’ve mentioned that in Rust we refrain from calling structs and enums | |
265 | “objects” to distinguish them from other languages’ objects. In a struct or | |
266 | enum, the data in the struct fields and the behavior in `impl` blocks is | |
267 | separated, whereas in other languages the data and behavior combined into one | |
268 | concept is often labeled an object. Trait objects, though, *are* more like | |
269 | objects in other languages, in the sense that they combine both data and | |
270 | behavior. However, trait objects differ from traditional objects in that we | |
271 | can’t add data to a trait object. Trait objects aren’t as generally useful as | |
272 | objects in other languages: their specific purpose is to allow abstraction | |
273 | across common behavior. | |
274 | ||
275 | Listing 17-3 shows how to define a trait named `Draw` with one method named | |
276 | `draw`: | |
cc61c64b XL |
277 | |
278 | Filename: src/lib.rs | |
279 | ||
280 | ``` | |
281 | pub trait Draw { | |
282 | fn draw(&self); | |
283 | } | |
284 | ``` | |
285 | ||
286 | Listing 17-3: Definition of the `Draw` trait | |
287 | ||
abe05a73 XL |
288 | This should look familiar from our discussions on how to define traits in |
289 | Chapter 10. Next comes something new: Listing 17-4 defines a struct named | |
290 | `Screen` that holds a vector named `components`. This vector is of type | |
291 | `Box<Draw>`, which is a trait object: it’s a stand-in for any type inside a | |
292 | `Box` that implements the `Draw` trait. | |
293 | ||
294 | <!-- Would it be useful to let the reader know why we need a box here, or will | |
295 | that be clear at this point? --> | |
296 | <!-- We get into this in chapter 19; I've added a reference to the start of | |
297 | this section where we talk about needing a `&` or a `Box` to be a trait object. | |
298 | /Carol --> | |
cc61c64b XL |
299 | |
300 | Filename: src/lib.rs | |
301 | ||
302 | ``` | |
303 | pub struct Screen { | |
304 | pub components: Vec<Box<Draw>>, | |
305 | } | |
306 | ``` | |
307 | ||
abe05a73 XL |
308 | Listing 17-4: Definition of the `Screen` struct with a `components` field |
309 | holding a vector of trait objects that implement the `Draw` trait | |
cc61c64b | 310 | |
abe05a73 XL |
311 | On the `Screen` struct, we’ll define a method named `run` that will call the |
312 | `draw` method on each of its `components`, as shown in Listing 17-5: | |
cc61c64b XL |
313 | |
314 | Filename: src/lib.rs | |
315 | ||
316 | ``` | |
317 | impl Screen { | |
318 | pub fn run(&self) { | |
319 | for component in self.components.iter() { | |
320 | component.draw(); | |
321 | } | |
322 | } | |
323 | } | |
324 | ``` | |
325 | ||
326 | Listing 17-5: Implementing a `run` method on `Screen` that calls the `draw` | |
327 | method on each component | |
328 | ||
abe05a73 | 329 | This works differently to defining a struct that uses a generic type parameter |
cc61c64b XL |
330 | with trait bounds. A generic type parameter can only be substituted with one |
331 | concrete type at a time, while trait objects allow for multiple concrete types | |
332 | to fill in for the trait object at runtime. For example, we could have defined | |
333 | the `Screen` struct using a generic type and a trait bound as in Listing 17-6: | |
334 | ||
335 | Filename: src/lib.rs | |
336 | ||
337 | ``` | |
338 | pub struct Screen<T: Draw> { | |
339 | pub components: Vec<T>, | |
340 | } | |
341 | ||
342 | impl<T> Screen<T> | |
343 | where T: Draw { | |
344 | pub fn run(&self) { | |
345 | for component in self.components.iter() { | |
346 | component.draw(); | |
347 | } | |
348 | } | |
349 | } | |
350 | ``` | |
351 | ||
352 | Listing 17-6: An alternate implementation of the `Screen` struct and its `run` | |
353 | method using generics and trait bounds | |
354 | ||
abe05a73 XL |
355 | This restricts us to a `Screen` instance that has a list of components all of |
356 | type `Button` or all of type `TextField`. If you’ll only ever have homogeneous | |
357 | collections, using generics and trait bounds is preferable since the | |
358 | definitions will be monomorphized at compile time to use the concrete types. | |
cc61c64b | 359 | |
abe05a73 XL |
360 | With the the method using trait objects, on the other hand, one `Screen` |
361 | instance can hold a `Vec` that contains a `Box<Button>` as well as a | |
362 | `Box<TextField>`. Let’s see how that works, and then talk about the runtime | |
363 | performance implications. | |
cc61c64b | 364 | |
abe05a73 | 365 | ### Implementing the Trait |
cc61c64b | 366 | |
abe05a73 XL |
367 | Now we’ll add some types that implement the `Draw` trait. We’re going to |
368 | provide the `Button` type. Again, actually implementing a GUI library is out of | |
3b2f2976 | 369 | scope of this book, so the `draw` method won’t have any useful implementation |
cc61c64b XL |
370 | in its body. To imagine what the implementation might look like, a `Button` |
371 | struct might have fields for `width`, `height`, and `label`, as shown in | |
372 | Listing 17-7: | |
373 | ||
374 | Filename: src/lib.rs | |
375 | ||
376 | ``` | |
377 | pub struct Button { | |
378 | pub width: u32, | |
379 | pub height: u32, | |
380 | pub label: String, | |
381 | } | |
382 | ||
383 | impl Draw for Button { | |
384 | fn draw(&self) { | |
385 | // Code to actually draw a button | |
386 | } | |
387 | } | |
388 | ``` | |
389 | ||
390 | Listing 17-7: A `Button` struct that implements the `Draw` trait | |
391 | ||
abe05a73 XL |
392 | The `width`, `height`, and `label` fields on `Button` will differ from the |
393 | fields on other components, such as a `TextField` type that might have those | |
394 | plus a `placeholder` field instead. Each of the types we want to draw on the | |
395 | screen will implement the `Draw` trait, with different code in the `draw` | |
396 | method to define how to draw that particular type, like `Button` has here | |
397 | (without the actual GUI code that’s out of scope of this chapter). `Button`, | |
398 | for instance, might have an additional `impl` block containing methods related | |
399 | to what happens if the button is clicked. These kinds of methods won’t apply to | |
400 | types like `TextField`. | |
cc61c64b XL |
401 | |
402 | Someone using our library has decided to implement a `SelectBox` struct that | |
403 | has `width`, `height`, and `options` fields. They implement the `Draw` trait on | |
404 | the `SelectBox` type as well, as shown in Listing 17-8: | |
405 | ||
406 | Filename: src/main.rs | |
407 | ||
408 | ``` | |
409 | extern crate rust_gui; | |
410 | use rust_gui::Draw; | |
411 | ||
412 | struct SelectBox { | |
413 | width: u32, | |
414 | height: u32, | |
415 | options: Vec<String>, | |
416 | } | |
417 | ||
418 | impl Draw for SelectBox { | |
419 | fn draw(&self) { | |
420 | // Code to actually draw a select box | |
421 | } | |
422 | } | |
423 | ``` | |
424 | ||
425 | Listing 17-8: Another crate using `rust_gui` and implementing the `Draw` trait | |
426 | on a `SelectBox` struct | |
427 | ||
428 | The user of our library can now write their `main` function to create a | |
abe05a73 | 429 | `Screen` instance. To this they can add a `SelectBox` and a `Button` by putting |
cc61c64b XL |
430 | each in a `Box<T>` to become a trait object. They can then call the `run` |
431 | method on the `Screen` instance, which will call `draw` on each of the | |
432 | components. Listing 17-9 shows this implementation: | |
433 | ||
434 | Filename: src/main.rs | |
435 | ||
436 | ``` | |
437 | use rust_gui::{Screen, Button}; | |
438 | ||
439 | fn main() { | |
440 | let screen = Screen { | |
441 | components: vec![ | |
442 | Box::new(SelectBox { | |
443 | width: 75, | |
444 | height: 10, | |
445 | options: vec![ | |
446 | String::from("Yes"), | |
447 | String::from("Maybe"), | |
448 | String::from("No") | |
449 | ], | |
450 | }), | |
451 | Box::new(Button { | |
452 | width: 50, | |
453 | height: 10, | |
454 | label: String::from("OK"), | |
455 | }), | |
456 | ], | |
457 | }; | |
458 | ||
459 | screen.run(); | |
460 | } | |
461 | ``` | |
462 | ||
463 | Listing 17-9: Using trait objects to store values of different types that | |
464 | implement the same trait | |
465 | ||
abe05a73 XL |
466 | When we wrote the library, we didn’t know that someone would add the |
467 | `SelectBox` type someday, but our `Screen` implementation was able to operate | |
468 | on the new type and draw it because `SelectBox` implements the `Draw` type, | |
469 | which means it implements the `draw` method. | |
470 | ||
471 | This concept---of being concerned only with the messages a value responds to, | |
472 | rather than the value’s concrete type---is similar to a concept in dynamically | |
473 | typed languages called *duck typing*: if it walks like a duck, and quacks like | |
474 | a duck, then it must be a duck! In the implementation of `run` on `Screen` in | |
475 | Listing 17-5, `run` doesn’t need to know what the concrete type of each | |
476 | component is. It doesn’t check to see if a component is an instance of a | |
477 | `Button` or a `SelectBox`, it just calls the `draw` method on the component. By | |
478 | specifying `Box<Draw>` as the type of the values in the `components` vector, | |
479 | we’ve defined `Screen` to need values that we can call the `draw` method on. | |
480 | ||
481 | <!-- I may be slow on the uptake here, but it seems like we're saying that | |
482 | responsibility for how the type trait object behaves with the draw method is | |
483 | called on it belongs to the trait object, and not to the draw method itself. Is | |
484 | that an accurate summary? I want to make sure I'm clearly following the | |
485 | argument! --> | |
486 | <!-- Each type (like `Button` or `SelectBox`) that implements the `Draw` trait | |
487 | can customize what happens in the body of the `draw` method. The trait object | |
488 | is just responsible for making sure that the only things that are usable in | |
489 | that context are things that implement the `Draw` trait. Does this clear it up | |
490 | at all? Is there something we should clarify in the text? /Carol --> | |
491 | ||
492 | The advantage of using trait objects and Rust’s type system to do something | |
493 | similar to duck typing is that we never have to check that a value implements a | |
494 | particular method at runtime or worry about getting errors if a value doesn’t | |
495 | implement a method but we call it anyway. Rust won’t compile our code if the | |
496 | values don’t implement the traits that the trait objects need. | |
cc61c64b XL |
497 | |
498 | For example, Listing 17-10 shows what happens if we try to create a `Screen` | |
499 | with a `String` as a component: | |
500 | ||
501 | Filename: src/main.rs | |
502 | ||
503 | ``` | |
504 | extern crate rust_gui; | |
505 | use rust_gui::Draw; | |
506 | ||
507 | fn main() { | |
508 | let screen = Screen { | |
509 | components: vec![ | |
510 | Box::new(String::from("Hi")), | |
511 | ], | |
512 | }; | |
513 | ||
514 | screen.run(); | |
515 | } | |
516 | ``` | |
517 | ||
3b2f2976 XL |
518 | Listing 17-10: Attempting to use a type that doesn’t implement the trait |
519 | object’s trait | |
cc61c64b | 520 | |
3b2f2976 | 521 | We’ll get this error because `String` doesn’t implement the `Draw` trait: |
cc61c64b XL |
522 | |
523 | ``` | |
524 | error[E0277]: the trait bound `std::string::String: Draw` is not satisfied | |
525 | --> | |
526 | | | |
527 | 4 | Box::new(String::from("Hi")), | |
528 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not | |
529 | implemented for `std::string::String` | |
530 | | | |
531 | = note: required for the cast to the object type `Draw` | |
532 | ``` | |
533 | ||
abe05a73 XL |
534 | This lets us know that either we’re passing something to `Screen` we didn’t |
535 | mean to pass, and we should pass a different type, or implement `Draw` on | |
cc61c64b XL |
536 | `String` so that `Screen` is able to call `draw` on it. |
537 | ||
538 | ### Trait Objects Perform Dynamic Dispatch | |
539 | ||
abe05a73 XL |
540 | Recall from Chapter 10 our discussion on the monomorphization process performed |
541 | by the compiler when we use trait bounds on generics: the compiler generates | |
cc61c64b XL |
542 | non-generic implementations of functions and methods for each concrete type |
543 | that we use in place of a generic type parameter. The code that results from | |
abe05a73 XL |
544 | monomorphization is doing *static dispatch*. Static dispatch is when the |
545 | compiler knows what method you’re calling at compile time. This is opposed to | |
546 | *dynamic dispatch*, when the compiler can’t tell at compile time which method | |
547 | you’re calling. In these cases, the compiler emits code that will figure out at | |
548 | runtime which method to call. | |
549 | ||
550 | <!--I'm struggling to follow the static dispatch definition, can you expand | |
551 | that a little? Which part of that is the static dispatch, pre-determining the | |
552 | code called with a method and storing it? --> | |
553 | <!-- Yes, in a way. We've expanded and moved the definitions of static and | |
554 | dynamic dispatch together to better contrast, hopefully this helps? /Carol --> | |
555 | ||
556 | When we use trait objects, Rust has to use dynamic dispatch. The compiler | |
557 | doesn’t know all the types that might be used with the code using trait | |
558 | objects, so it doesn’t know which method implemented on which type to call. | |
559 | Instead, Rust uses the pointers inside of the trait object at runtime to know | |
560 | which specific method to call. There’s a runtime cost when this lookup happens, | |
561 | compared to static dispatch. Dynamic dispatch also prevents the compiler from | |
562 | choosing to inline a method’s code which in turn prevents some optimizations. | |
563 | We did get extra flexibility in the code that we wrote and were able to | |
564 | support, though, so it’s a tradeoff to consider. | |
cc61c64b XL |
565 | |
566 | ### Object Safety is Required for Trait Objects | |
567 | ||
568 | <!-- Liz: we're conflicted on including this section. Not being able to use a | |
569 | trait as a trait object because of object safety is something that | |
570 | beginner/intermediate Rust developers run into sometimes, but explaining it | |
571 | fully is long and complicated. Should we just cut this whole section? Leave it | |
572 | (and finish the explanation of how to fix the error at the end)? Shorten it to | |
573 | a quick caveat, that just says something like "Some traits can't be trait | |
574 | objects. Clone is an example of one. You'll get errors that will let you know | |
575 | if a trait can't be a trait object, look up object safety if you're interested | |
576 | in the details"? Thanks! /Carol --> | |
abe05a73 XL |
577 | <!-- That sounds like a good solution, since the compiler will warn them in any |
578 | case. I read through, editing a little, and I agree we could afford to cut it, | |
579 | I'm not sure it brings practical skills to the user --> | |
580 | <!-- Ok, I've cut section way down to the practical pieces, but still explained | |
581 | a little bit /Carol --> | |
582 | ||
583 | Only *object safe* traits can be made into trait objects. There are some | |
584 | complex rules around all the properties that make a trait object safe, but in | |
585 | practice, there are only two rules that are relevant. A trait is object safe if | |
586 | all of the methods defined in the trait have the following properties: | |
587 | ||
588 | - The return type isn’t `Self` | |
589 | - There aren’t any generic type parameters | |
590 | ||
591 | The `Self` keyword is an alias for the type we’re implementing traits or | |
592 | methods on. Object safety is required for trait objects because once you have a | |
593 | trait object, you no longer know what the concrete type implementing that trait | |
594 | is. If a trait method returns the concrete `Self` type, but a trait object | |
595 | forgets the exact type that it is, there’s no way that the method can use the | |
596 | original concrete type that it’s forgotten. Same with generic type parameters | |
597 | that are filled in with concrete type parameters when the trait is used: the | |
598 | concrete types become part of the type that implements the trait. When the type | |
599 | is erased by the use of a trait object, there’s no way to know what types to | |
600 | fill in the generic type parameters with. | |
cc61c64b XL |
601 | |
602 | An example of a trait whose methods are not object safe is the standard | |
3b2f2976 | 603 | library’s `Clone` trait. The signature for the `clone` method in the `Clone` |
cc61c64b XL |
604 | trait looks like this: |
605 | ||
606 | ``` | |
607 | pub trait Clone { | |
608 | fn clone(&self) -> Self; | |
609 | } | |
610 | ``` | |
611 | ||
612 | `String` implements the `Clone` trait, and when we call the `clone` method on | |
613 | an instance of `String` we get back an instance of `String`. Similarly, if we | |
614 | call `clone` on an instance of `Vec`, we get back an instance of `Vec`. The | |
615 | signature of `clone` needs to know what type will stand in for `Self`, since | |
3b2f2976 | 616 | that’s the return type. |
cc61c64b | 617 | |
3b2f2976 | 618 | The compiler will tell you if you’re trying to do something that violates the |
cc61c64b XL |
619 | rules of object safety in regards to trait objects. For example, if we had |
620 | tried to implement the `Screen` struct in Listing 17-4 to hold types that | |
621 | implement the `Clone` trait instead of the `Draw` trait, like this: | |
622 | ||
623 | ``` | |
624 | pub struct Screen { | |
625 | pub components: Vec<Box<Clone>>, | |
626 | } | |
627 | ``` | |
628 | ||
3b2f2976 | 629 | We’ll get this error: |
cc61c64b XL |
630 | |
631 | ``` | |
632 | error[E0038]: the trait `std::clone::Clone` cannot be made into an object | |
633 | --> | |
634 | | | |
635 | 2 | pub components: Vec<Box<Clone>>, | |
636 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` cannot be | |
637 | made into an object | |
638 | | | |
639 | = note: the trait cannot require that `Self : Sized` | |
640 | ``` | |
641 | ||
abe05a73 XL |
642 | This means you can’t use this trait as a trait object in this way. If you’re |
643 | interested in more details on object safety, see Rust RFC 255 at | |
644 | *https://github.com/rust-lang/rfcs/blob/master/text/0255-object-safety.md*. | |
645 | ||
646 | ## Implementing an Object-Oriented Design Pattern | |
647 | ||
648 | The *state pattern* is an object-oriented design pattern. The crux of the | |
649 | pattern is that a value has some internal state, represented by a set of *state | |
650 | objects*, and the value’s behavior changes based on the internal state. The | |
651 | state objects share functionality--in Rust, of course, we use structs and | |
652 | traits rather than objects and inheritance. Each state object representing the | |
653 | state is responsible for its own behavior and for governing when it should | |
654 | change into another state. The value that holds a state object knows nothing | |
655 | about the different behavior of the states or when to transition between states. | |
656 | ||
657 | <!-- Below -- requirements for what, for what we need the value for? --> | |
658 | <!-- I've clarified /Carol --> | |
659 | ||
660 | Using the state pattern means when the business requirements of the program | |
661 | change, we won’t need to change the code of the value holding the state or the | |
662 | code that uses the value. We’ll only need to update the code inside one of the | |
663 | state objects to change its rules, or perhaps add more state objects. Let’s | |
664 | look at an example of the state design pattern and how to use it in Rust. | |
665 | ||
666 | To explore this idea, we’ll implement a blog post workflow in an incremental | |
667 | way. The blog’s final functionality will look like this: | |
cc61c64b XL |
668 | |
669 | 1. A blog post starts as an empty draft. | |
abe05a73 | 670 | 2. Once the draft is done, a review of the post is requested. |
cc61c64b | 671 | 3. Once the post is approved, it gets published. |
abe05a73 XL |
672 | 4. Only published blog posts return content to print, so unapproved posts can’t |
673 | accidentally be published. | |
cc61c64b XL |
674 | |
675 | Any other changes attempted on a post should have no effect. For example, if we | |
3b2f2976 | 676 | try to approve a draft blog post before we’ve requested a review, the post |
cc61c64b XL |
677 | should stay an unpublished draft. |
678 | ||
679 | Listing 17-11 shows this workflow in code form. This is an example usage of the | |
3b2f2976 | 680 | API we’re going to implement in a library crate named `blog`: |
cc61c64b XL |
681 | |
682 | Filename: src/main.rs | |
683 | ||
684 | ``` | |
685 | extern crate blog; | |
686 | use blog::Post; | |
687 | ||
688 | fn main() { | |
689 | let mut post = Post::new(); | |
690 | ||
691 | post.add_text("I ate a salad for lunch today"); | |
692 | assert_eq!("", post.content()); | |
693 | ||
694 | post.request_review(); | |
695 | assert_eq!("", post.content()); | |
696 | ||
697 | post.approve(); | |
698 | assert_eq!("I ate a salad for lunch today", post.content()); | |
699 | } | |
700 | ``` | |
701 | ||
702 | Listing 17-11: Code that demonstrates the desired behavior we want our `blog` | |
703 | crate to have | |
704 | ||
abe05a73 XL |
705 | We want to allow the user to create a new draft blog post with `Post::new`. |
706 | Then, we want to allow text to be added to the blog post while it’s in the | |
707 | draft state. If we try to get the post’s content immediately, before | |
708 | approval, nothing should happen because the post is still a draft. We’ve added | |
709 | an `assert_eq!` here for demonstration purposes. An excellent unit test for | |
710 | this would be to assert that a draft blog post returns an empty string from the | |
711 | `content` method, but we’re not going to write tests for this example. | |
712 | ||
713 | Next, we want to enable a request for a review of the post, and we want | |
714 | `content` to return an empty string while waiting for the review. Lastly, when | |
715 | the post receives approval, it should get published, meaning the text of the | |
716 | post will be returned when `content` is called. | |
717 | ||
718 | <!-- Below -- so this is where we'll implement the state pattern? If so, can | |
719 | you make that explicit, just to be clear! I've added some text to the second | |
720 | line, not sure if that's accurate though --> | |
721 | <!-- Yes, the state pattern will be implemented within the `Post` type. I've | |
722 | tweaked the wording a bit but you've pretty much got it! /Carol--> | |
cc61c64b | 723 | |
3b2f2976 | 724 | Notice that the only type we’re interacting with from the crate is the `Post` |
abe05a73 XL |
725 | type. This type will use the state pattern and will hold a value that will be |
726 | one of three state objects representing the various states a post can be | |
727 | in---draft, waiting for review, or published. Changing from one state to | |
728 | another will be managed internally within the `Post` type. The states change in | |
729 | response to the methods users of our library call on the `Post` instance, but | |
730 | they don’t have to manage the state changes directly. This also means users | |
731 | can’t make a mistake with the states, like publishing a post before it is | |
732 | reviewed. | |
cc61c64b XL |
733 | |
734 | ### Defining `Post` and Creating a New Instance in the Draft State | |
735 | ||
abe05a73 XL |
736 | Let’s get started on the implementation of the library! We know we need a |
737 | public `Post` struct that holds some content, so let’s start with the | |
cc61c64b | 738 | definition of the struct and an associated public `new` function to create an |
abe05a73 XL |
739 | instance of `Post`, as shown in Listing 17-12. We’ll also make a private |
740 | `State` trait. Then `Post` will hold a trait object of `Box<State>` inside an | |
741 | `Option` in a private field named `state`. We’ll see why the `Option` is | |
742 | necessary in a bit. | |
743 | ||
744 | The `State` trait defines the behavior shared by different post states, and the | |
745 | `Draft`, `PendingReview`, and `Published` states will all implement the `State` | |
746 | trait. For now, the trait does not have any methods, and we’re going to start | |
747 | by defining just the `Draft` state since that’s the state we want a post to | |
748 | start in: | |
cc61c64b XL |
749 | |
750 | Filename: src/lib.rs | |
751 | ||
752 | ``` | |
753 | pub struct Post { | |
754 | state: Option<Box<State>>, | |
755 | content: String, | |
756 | } | |
757 | ||
758 | impl Post { | |
759 | pub fn new() -> Post { | |
760 | Post { | |
761 | state: Some(Box::new(Draft {})), | |
762 | content: String::new(), | |
763 | } | |
764 | } | |
765 | } | |
766 | ||
767 | trait State {} | |
768 | ||
769 | struct Draft {} | |
770 | ||
771 | impl State for Draft {} | |
772 | ``` | |
773 | ||
774 | Listing 17-12: Definition of a `Post` struct and a `new` function that creates | |
abe05a73 | 775 | a new `Post` instance, a `State` trait, and a `Draft` struct |
cc61c64b | 776 | |
abe05a73 XL |
777 | When we create a new `Post`, we set its `state` field to a `Some` value that |
778 | holds a `Box`. This `Box` points to a new instance of the `Draft` struct. This | |
779 | ensures whenever we create a new instance of `Post`, it’ll start out as a | |
780 | draft. Because the `state` field of `Post` is private, there’s no way to create | |
781 | a `Post` in any other state! | |
cc61c64b XL |
782 | |
783 | ### Storing the Text of the Post Content | |
784 | ||
785 | In the `Post::new` function, we set the `content` field to a new, empty | |
abe05a73 XL |
786 | `String`. Listing 17-11 showed that we want to be able to call a method named |
787 | `add_text` and pass it a `&str` that’s then added to the text content of the | |
788 | blog post. We implement this as a method rather than exposing the `content` | |
789 | field as `pub`. This means we can implement a method later that will control | |
790 | how the `content` field’s data is read. The `add_text` method is pretty | |
791 | straightforward, so let’s add the implementation in Listing 17-13 to the `impl | |
792 | Post` block: | |
cc61c64b XL |
793 | |
794 | Filename: src/lib.rs | |
795 | ||
796 | ``` | |
797 | impl Post { | |
798 | // ...snip... | |
799 | pub fn add_text(&mut self, text: &str) { | |
800 | self.content.push_str(text); | |
801 | } | |
802 | } | |
803 | ``` | |
804 | ||
abe05a73 XL |
805 | Listing 17-13: Implementing the `add_text` method to add text to a post’s |
806 | `content` | |
cc61c64b | 807 | |
3b2f2976 XL |
808 | `add_text` takes a mutable reference to `self`, since we’re changing the `Post` |
809 | instance that we’re calling `add_text` on. We then call `push_str` on the | |
cc61c64b | 810 | `String` in `content` and pass the `text` argument to add to the saved |
abe05a73 XL |
811 | `content`. This behavior doesn’t depend on the state the post is in so it’s not |
812 | part of the state pattern. The `add_text` method doesn’t interact with the | |
813 | `state` field at all, but it is part of the behavior we want to support. | |
814 | ||
815 | ### Ensuring the Content of a Draft Post is Empty | |
816 | ||
817 | Even after we’ve called `add_text` and added some content to our post, we still | |
818 | want the `content` method to return an empty string slice since the post is | |
819 | still in the draft state, as shown on line 8 of Listing 17-11. For now, let’s | |
820 | implement the `content` method with the simplest thing that will fulfill this | |
821 | requirement: always returning an empty string slice. We’re going to change this | |
822 | later once we implement the ability to change a post’s state so it can be | |
823 | published. So far, though, posts can only be in the draft state, so the post | |
cc61c64b XL |
824 | content should always be empty. Listing 17-14 shows this placeholder |
825 | implementation: | |
826 | ||
827 | Filename: src/lib.rs | |
828 | ||
829 | ``` | |
830 | impl Post { | |
831 | // ...snip... | |
832 | pub fn content(&self) -> &str { | |
833 | "" | |
834 | } | |
835 | } | |
836 | ``` | |
837 | ||
abe05a73 XL |
838 | Listing 17-14: Adding a placeholder implementation for the `content` method on |
839 | `Post` that always returns an empty string slice | |
cc61c64b XL |
840 | |
841 | With this added `content` method, everything in Listing 17-11 up to line 8 | |
842 | works as we intend. | |
843 | ||
844 | ### Requesting a Review of the Post Changes its State | |
845 | ||
abe05a73 XL |
846 | Next up we need to add functionality to request a review of a post, which |
847 | should change its state from `Draft` to `PendingReview`. We want to give `Post` | |
848 | a public method named `request_review` that will take a mutable reference to | |
849 | `self`. Then we’re going to call an internal `request_review` method on the | |
850 | current state of `Post`, and this second `request_review` method will consume | |
851 | the current state and return a new state. Listing 17-15 shows this code: | |
852 | ||
853 | <!-- NOTE TO DE/AU: We might want to move this explanation to after the code if | |
854 | you want to add wingdings, we can see once we transfer it to Word --> | |
855 | <!-- I decided to move some of this explanation after the code for this reason | |
856 | and because we got some questions about this example that I wanted to expand | |
857 | upon /Carol --> | |
cc61c64b XL |
858 | |
859 | Filename: src/lib.rs | |
860 | ||
861 | ``` | |
862 | impl Post { | |
863 | // ...snip... | |
864 | pub fn request_review(&mut self) { | |
865 | if let Some(s) = self.state.take() { | |
866 | self.state = Some(s.request_review()) | |
867 | } | |
868 | } | |
869 | } | |
870 | ||
871 | trait State { | |
872 | fn request_review(self: Box<Self>) -> Box<State>; | |
873 | } | |
874 | ||
875 | struct Draft {} | |
876 | ||
877 | impl State for Draft { | |
878 | fn request_review(self: Box<Self>) -> Box<State> { | |
879 | Box::new(PendingReview {}) | |
880 | } | |
881 | } | |
882 | ||
883 | struct PendingReview {} | |
884 | ||
885 | impl State for PendingReview { | |
886 | fn request_review(self: Box<Self>) -> Box<State> { | |
887 | self | |
888 | } | |
889 | } | |
890 | ``` | |
891 | ||
892 | Listing 17-15: Implementing `request_review` methods on `Post` and the `State` | |
893 | trait | |
894 | ||
3b2f2976 | 895 | We’ve added the `request_review` method to the `State` trait; all types that |
cc61c64b XL |
896 | implement the trait will now need to implement the `request_review` method. |
897 | Note that rather than having `self`, `&self`, or `&mut self` as the first | |
898 | parameter of the method, we have `self: Box<Self>`. This syntax means the | |
899 | method is only valid when called on a `Box` holding the type. This syntax takes | |
abe05a73 XL |
900 | ownership of `Box<Self>`, invalidating the old state so that the state value of |
901 | the `Post` can transform itself into a new state. | |
902 | ||
903 | <!-- Above -- so Post can transform, or so Draft can transform? --> | |
904 | <!-- Technically it's so the Draft value can transform into another value, | |
905 | which changes the state of Post-- I've tried to clarify. /Carol --> | |
906 | ||
907 | To consume the old state, the `request_review` method needs to take ownership | |
908 | of the state value. This is where the `Option` in the `state` field of `Post` | |
909 | comes in: we call the `take` method to take the `Some` value out of the `state` | |
910 | field and leave a `None` in its place, since Rust doesn’t let us have | |
911 | unpopulated fields in structs. This lets us move the `state` value out of | |
912 | `Post` rather than borrowing it. Then we’ll set the post’s `state` value to the | |
913 | result of this operation. | |
914 | ||
915 | We need to set `state` to `None` temporarily, rather than code like `self.state | |
916 | = self.state.request_review();` that would set the `state` field directly, to | |
917 | get ownership of the `state` value. This ensures `Post` can’t use the old | |
918 | `state` value after we’ve transformed it into a new state. | |
919 | ||
920 | The `request_review` method on `Draft` needs to return a new, boxed instance of | |
921 | a new `PendingReview` struct, which represents the state when a post is waiting | |
922 | for a review. The `PendingReview` struct also implements the `request_review` | |
923 | method, but doesn’t do any transformations. Rather, it returns itself, since | |
924 | when we request a review on a post already in the `PendingReview` state, it | |
925 | should stay in the `PendingReview` state. | |
cc61c64b XL |
926 | |
927 | Now we can start seeing the advantages of the state pattern: the | |
abe05a73 XL |
928 | `request_review` method on `Post` is the same no matter its `state` value. Each |
929 | state is responsible for its own rules. | |
cc61c64b | 930 | |
3b2f2976 | 931 | We’re going to leave the `content` method on `Post` as it is, returning an |
abe05a73 XL |
932 | empty string slice. We can now have a `Post` in the `PendingReview` state as |
933 | well as the `Draft` state, but we want the same behavior in the `PendingReview` | |
cc61c64b XL |
934 | state. Listing 17-11 now works up until line 11! |
935 | ||
abe05a73 | 936 | ### Adding the `approve` Method that Changes the Behavior of `content` |
cc61c64b | 937 | |
abe05a73 XL |
938 | The `approve` method will be similar to the `request_review` method: it will |
939 | set `state` to the value that the current state says it should have when that | |
940 | state is approved, shown in Listing 17-16. | |
cc61c64b XL |
941 | |
942 | Filename: src/lib.rs | |
943 | ||
944 | ``` | |
945 | impl Post { | |
946 | // ...snip... | |
947 | pub fn approve(&mut self) { | |
948 | if let Some(s) = self.state.take() { | |
949 | self.state = Some(s.approve()) | |
950 | } | |
951 | } | |
952 | } | |
953 | ||
954 | trait State { | |
955 | fn request_review(self: Box<Self>) -> Box<State>; | |
956 | fn approve(self: Box<Self>) -> Box<State>; | |
957 | } | |
958 | ||
959 | struct Draft {} | |
960 | ||
961 | impl State for Draft { | |
962 | // ...snip... | |
963 | fn approve(self: Box<Self>) -> Box<State> { | |
964 | self | |
965 | } | |
966 | } | |
967 | ||
968 | struct PendingReview {} | |
969 | ||
970 | impl State for PendingReview { | |
971 | // ...snip... | |
972 | fn approve(self: Box<Self>) -> Box<State> { | |
973 | Box::new(Published {}) | |
974 | } | |
975 | } | |
976 | ||
977 | struct Published {} | |
978 | ||
979 | impl State for Published { | |
980 | fn request_review(self: Box<Self>) -> Box<State> { | |
981 | self | |
982 | } | |
983 | ||
984 | fn approve(self: Box<Self>) -> Box<State> { | |
985 | self | |
986 | } | |
987 | } | |
988 | ``` | |
989 | ||
990 | Listing 17-16: Implementing the `approve` method on `Post` and the `State` trait | |
991 | ||
abe05a73 XL |
992 | We add the `approve` method to the `State` trait, and add a new struct that |
993 | implements `State`, the `Published` state. | |
994 | ||
995 | Similar to `request_review`, if we call the `approve` method on a `Draft`, it | |
cc61c64b XL |
996 | will have no effect since it will return `self`. When we call `approve` on |
997 | `PendingReview`, it returns a new, boxed instance of the `Published` struct. | |
998 | The `Published` struct implements the `State` trait, and for both the | |
abe05a73 | 999 | `request_review` method and the `approve` method, it returns itself, since the |
cc61c64b XL |
1000 | post should stay in the `Published` state in those cases. |
1001 | ||
abe05a73 XL |
1002 | Now to update the `content` method on `Post`: if the state is `Published` we |
1003 | want to return the value in the post’s `content` field; otherwise we want to | |
1004 | return an empty string slice, as shown in Listing 17-17: | |
cc61c64b XL |
1005 | |
1006 | Filename: src/lib.rs | |
1007 | ||
1008 | ``` | |
1009 | impl Post { | |
1010 | // ...snip... | |
1011 | pub fn content(&self) -> &str { | |
1012 | self.state.as_ref().unwrap().content(&self) | |
1013 | } | |
1014 | // ...snip... | |
1015 | } | |
1016 | ``` | |
1017 | ||
1018 | Listing 17-17: Updating the `content` method on `Post` to delegate to a | |
1019 | `content` method on `State` | |
1020 | ||
abe05a73 XL |
1021 | Because the goal is to keep all these rules inside the structs that implement |
1022 | `State`, we call a `content` method on the value in `state` and pass the post | |
1023 | instance (that is, `self`) as an argument. Then we return the value that’s | |
1024 | returned from using the `content` method on the `state` value. | |
1025 | ||
1026 | We call the `as_ref` method on the `Option` because we want a reference to the | |
1027 | value inside the `Option` rather than ownership of it. Because `state` is an | |
1028 | `Option<Box<State>>`, calling `as_ref` returns an `Option<&Box<State>>`. If we | |
1029 | didn’t call `as_ref`, we’d get an error because we can’t move `state` out of | |
1030 | the borrowed `&self` of the function parameter. | |
1031 | ||
1032 | We’re then calling the `unwrap` method, which we know will never panic, because | |
1033 | we know the methods on `Post` ensure that `state` will always contain a `Some` | |
1034 | value when those methods are done. This is one of the cases we talked about in | |
1035 | Chapter 12 when we know that a `None` value is never possible, even though the | |
1036 | compiler isn’t able to understand that. | |
1037 | ||
1038 | So then we have a `&Box<State>`, and when we call the `content` on it, deref | |
1039 | coercion will take effect on the `&` and the `Box` so that the `content` method | |
1040 | will ultimately be called on the type that implements the `State` trait. | |
cc61c64b | 1041 | |
abe05a73 XL |
1042 | That means we need to add `content` to the `State` trait definition, and that’s |
1043 | where We’ll put the logic for what content to return depending on which state | |
1044 | we have, as shown in Listing 17-18: | |
cc61c64b XL |
1045 | |
1046 | Filename: src/lib.rs | |
1047 | ||
1048 | ``` | |
1049 | trait State { | |
1050 | // ...snip... | |
1051 | fn content<'a>(&self, post: &'a Post) -> &'a str { | |
1052 | "" | |
1053 | } | |
1054 | } | |
1055 | ||
1056 | // ...snip... | |
1057 | struct Published {} | |
1058 | ||
1059 | impl State for Published { | |
1060 | // ...snip... | |
1061 | fn content<'a>(&self, post: &'a Post) -> &'a str { | |
1062 | &post.content | |
1063 | } | |
1064 | } | |
1065 | ``` | |
1066 | ||
1067 | Listing 17-18: Adding the `content` method to the `State` trait | |
1068 | ||
abe05a73 XL |
1069 | We add a default implementation for the `content` method that returns an empty |
1070 | string slice. That means we don’t need to implement `content` on the `Draft` | |
1071 | and `PendingReview` structs. The `Published` struct will override the `content` | |
1072 | method and return the value in `post.content`. | |
1073 | ||
cc61c64b | 1074 | Note that we need lifetime annotations on this method, like we discussed in |
abe05a73 XL |
1075 | Chapter 10. We’re taking a reference to a `post` as an argument, and returning |
1076 | a reference to part of that `post`, so the lifetime of the returned reference | |
1077 | is related to the lifetime of the `post` argument. | |
1078 | ||
1079 | <!-- Is this it finished, without the touch up we make to get rid of the empty | |
1080 | string? That's pretty awesome coding, maybe give it some ceremony here. Does | |
1081 | all of 17-11 now work? --> | |
1082 | <!-- Yep! Good point, so added! /Carol --> | |
1083 | ||
1084 | And we’re done-- all of Listing 17-11 now works! We’ve implemented the state | |
1085 | pattern with the rules of the blog post workflow. The logic around the rules | |
1086 | lives in the state objects rather than scattered throughout `Post`. | |
cc61c64b XL |
1087 | |
1088 | ### Tradeoffs of the State Pattern | |
1089 | ||
3b2f2976 | 1090 | We’ve shown that Rust is capable of implementing the object-oriented state |
abe05a73 XL |
1091 | pattern to encapsulate the different kinds of behavior a post should have in |
1092 | each state. The methods on `Post` know nothing about the different kinds of | |
1093 | behavior. The way this code is organized, we only have to look in one place to | |
1094 | know the different ways a published post can behave: the implementation of the | |
1095 | `State` trait on the `Published` struct. | |
1096 | ||
1097 | If we were to create an alternative implementation that didn’t use the state | |
1098 | pattern we might use `match` statements in the methods on `Post`, or even in | |
1099 | the `main` code that checks the state of the post and changes behavior in those | |
1100 | places instead. That would mean we’d have to look in a lot of places to | |
1101 | understand all the implications of a post being in the published state! This | |
1102 | would only increase the more states we added: each of those `match` statements | |
1103 | would need another arm. | |
1104 | ||
1105 | With the state pattern, the `Post` methods and the places we use `Post` don’t | |
1106 | need `match` statements, and to add a new state we would only need to add a new | |
1107 | `struct` and implement the trait methods on that one struct. | |
1108 | ||
1109 | This implementation is easy to extend to add more functionality. To see the | |
1110 | simplicity of maintaining code that uses this patterns, try out a few of these | |
1111 | suggestions: | |
1112 | ||
1113 | - Allow users to add text content only when a post is in the `Draft` state | |
3b2f2976 | 1114 | - Add a `reject` method that changes the post’s state from `PendingReview` back |
cc61c64b | 1115 | to `Draft` |
abe05a73 XL |
1116 | - Require two calls to `approve` before the state can be changed to `Published` |
1117 | ||
1118 | One downside of the state pattern is that, because the states implement the | |
1119 | transitions between states, some of the states are coupled to each other. If we | |
1120 | add another state between `PendingReview` and `Published`, such as `Scheduled`, | |
1121 | we would have to change the code in `PendingReview` to transition to | |
1122 | `Scheduled` instead. It would be less work if `PendingReview` wouldn’t need to | |
1123 | change with the addition of a new state, but that would mean switching to | |
cc61c64b XL |
1124 | another design pattern. |
1125 | ||
abe05a73 XL |
1126 | Another downside is that we find ourselves with a few bits of duplicated logic. |
1127 | To eliminate this, we might try to make default implementations for the | |
1128 | `request_review` and `approve` methods on the `State` trait that return `self`, | |
1129 | but this would violate object safety, since the trait doesn’t know what the | |
1130 | concrete `self` will be exactly. We want to be able to use `State` as a trait | |
1131 | object, so we need its methods to be object safe. | |
1132 | ||
1133 | The other duplication is the similar implementations of the `request_review` | |
1134 | and `approve` methods on `Post`. Both methods delegate to the implementation of | |
1135 | the same method on the value in the `state` field of `Option`, and set the new | |
1136 | value of the `state` field to the result. If we had a lot of methods on `Post` | |
1137 | that followed this pattern, we might consider defining a macro to eliminate the | |
1138 | repetition (see Appendix E on macros). | |
1139 | ||
1140 | By implementing this pattern exactly as it’s defined for object-oriented | |
1141 | languages, we’re not taking full advantage of Rust’s strengths as much as we | |
1142 | could. Let’s take a look at some changes we can make to this code that can make | |
1143 | invalid states and transitions into compile time errors. | |
cc61c64b XL |
1144 | |
1145 | #### Encoding States and Behavior as Types | |
1146 | ||
abe05a73 XL |
1147 | We’re going to show how to rethink the state pattern to get a different set of |
1148 | tradeoffs. Rather than encapsulating the states and transitions completely so | |
1149 | that outside code has no knowledge of them, we’re going to encode the states | |
1150 | into different types. Like this, Rust’s type checking system will make attempts | |
1151 | to use draft posts where only published posts are allowed into a compiler error. | |
cc61c64b | 1152 | |
3b2f2976 | 1153 | Let’s consider the first part of `main` from Listing 17-11: |
cc61c64b XL |
1154 | |
1155 | Filename: src/main.rs | |
1156 | ||
1157 | ``` | |
1158 | fn main() { | |
1159 | let mut post = Post::new(); | |
1160 | ||
1161 | post.add_text("I ate a salad for lunch today"); | |
1162 | assert_eq!("", post.content()); | |
1163 | } | |
1164 | ``` | |
1165 | ||
abe05a73 XL |
1166 | We still enable the creation of new posts in the draft state using `Post::new`, |
1167 | and the ability to add text to the post’s content. But instead of having a | |
1168 | `content` method on a draft post that returns an empty string, we’ll make it so | |
1169 | that draft posts don’t have the `content` method at all. That way, if we try to | |
1170 | get a draft post’s content, we’ll get a compiler error telling us the method | |
1171 | doesn’t exist. This will make it impossible for us to accidentally display | |
1172 | draft post content in production, since that code won’t even compile. Listing | |
1173 | 17-19 shows the definition of a `Post` struct, a `DraftPost` struct, and | |
1174 | methods on each: | |
cc61c64b XL |
1175 | |
1176 | Filename: src/lib.rs | |
1177 | ||
1178 | ``` | |
1179 | pub struct Post { | |
1180 | content: String, | |
1181 | } | |
1182 | ||
1183 | pub struct DraftPost { | |
1184 | content: String, | |
1185 | } | |
1186 | ||
1187 | impl Post { | |
1188 | pub fn new() -> DraftPost { | |
1189 | DraftPost { | |
1190 | content: String::new(), | |
1191 | } | |
1192 | } | |
1193 | ||
1194 | pub fn content(&self) -> &str { | |
1195 | &self.content | |
1196 | } | |
1197 | } | |
1198 | ||
1199 | impl DraftPost { | |
1200 | pub fn add_text(&mut self, text: &str) { | |
1201 | self.content.push_str(text); | |
1202 | } | |
1203 | } | |
1204 | ``` | |
1205 | ||
1206 | Listing 17-19: A `Post` with a `content` method and a `DraftPost` without a | |
1207 | `content` method | |
1208 | ||
1209 | Both the `Post` and `DraftPost` structs have a private `content` field that | |
1210 | stores the blog post text. The structs no longer have the `state` field since | |
3b2f2976 | 1211 | we’re moving the encoding of the state to the types of the structs. `Post` will |
cc61c64b XL |
1212 | represent a published post, and it has a `content` method that returns the |
1213 | `content`. | |
1214 | ||
1215 | We still have a `Post::new` function, but instead of returning an instance of | |
abe05a73 XL |
1216 | `Post`, it returns an instance of `DraftPost`. Because `content` is private, |
1217 | and there aren’t any functions that return `Post`, it’s not possible to create | |
1218 | an instance of `Post` right now. | |
1219 | ||
1220 | `DraftPost` has an `add_text` method so we can add text to `content` as before, | |
1221 | but note that `DraftPost` does not have a `content` method defined! So now the | |
1222 | program ensures all posts start as draft posts, and draft posts don’t have | |
1223 | their content available for display. Any attempt to get around these | |
1224 | constraints will result in a compiler error. | |
cc61c64b XL |
1225 | |
1226 | #### Implementing Transitions as Transformations into Different Types | |
1227 | ||
abe05a73 | 1228 | So how do we get a published post then? We want to enforce the rule that a |
cc61c64b | 1229 | draft post has to be reviewed and approved before it can be published. A post |
3b2f2976 | 1230 | in the pending review state should still not display any content. Let’s |
cc61c64b XL |
1231 | implement these constraints by adding another struct, `PendingReviewPost`, |
1232 | defining the `request_review` method on `DraftPost` to return a | |
1233 | `PendingReviewPost`, and defining an `approve` method on `PendingReviewPost` to | |
1234 | return a `Post` as shown in Listing 17-20: | |
1235 | ||
1236 | Filename: src/lib.rs | |
1237 | ||
1238 | ``` | |
1239 | impl DraftPost { | |
1240 | // ...snip... | |
1241 | ||
1242 | pub fn request_review(self) -> PendingReviewPost { | |
1243 | PendingReviewPost { | |
1244 | content: self.content, | |
1245 | } | |
1246 | } | |
1247 | } | |
1248 | ||
1249 | pub struct PendingReviewPost { | |
1250 | content: String, | |
1251 | } | |
1252 | ||
1253 | impl PendingReviewPost { | |
1254 | pub fn approve(self) -> Post { | |
1255 | Post { | |
1256 | content: self.content, | |
1257 | } | |
1258 | } | |
1259 | } | |
1260 | ``` | |
1261 | ||
abe05a73 XL |
1262 | Listing 17-20: A `PendingReviewPost` that gets created by calling |
1263 | `request_review` on `DraftPost`, and an `approve` method that turns a | |
cc61c64b XL |
1264 | `PendingReviewPost` into a published `Post` |
1265 | ||
1266 | The `request_review` and `approve` methods take ownership of `self`, thus | |
1267 | consuming the `DraftPost` and `PendingReviewPost` instances and transforming | |
1268 | them into a `PendingReviewPost` and a published `Post`, respectively. This way, | |
3b2f2976 XL |
1269 | we won’t have any `DraftPost` instances lingering around after we’ve called |
1270 | `request_review` on them, and so forth. `PendingReviewPost` doesn’t have a | |
abe05a73 XL |
1271 | `content` method defined on it, so attempting to read its content results in a |
1272 | compiler error, as with `DraftPost`. Because the only way to get a published | |
cc61c64b XL |
1273 | `Post` instance that does have a `content` method defined is to call the |
1274 | `approve` method on a `PendingReviewPost`, and the only way to get a | |
1275 | `PendingReviewPost` is to call the `request_review` method on a `DraftPost`, | |
3b2f2976 | 1276 | we’ve now encoded the blog post workflow into the type system. |
cc61c64b | 1277 | |
abe05a73 XL |
1278 | This does mean we have to make some small changes to `main`. The |
1279 | `request_review` and `approve` methods return new instances rather than | |
1280 | modifying the struct they’re called on, so we need to add more `let post = ` | |
1281 | shadowing assignments to save the returned instances. We also can’t have the | |
1282 | assertions about the draft and pending review post’s contents being empty | |
1283 | strings, nor do we need them: we can’t compile code that tries to use the | |
1284 | content of posts in those states any longer. The updated code in `main` is | |
1285 | shown in Listing 17-21: | |
cc61c64b XL |
1286 | |
1287 | Filename: src/main.rs | |
1288 | ||
1289 | ``` | |
1290 | extern crate blog; | |
1291 | use blog::Post; | |
1292 | ||
1293 | fn main() { | |
1294 | let mut post = Post::new(); | |
1295 | ||
1296 | post.add_text("I ate a salad for lunch today"); | |
1297 | ||
1298 | let post = post.request_review(); | |
1299 | ||
1300 | let post = post.approve(); | |
1301 | ||
1302 | assert_eq!("I ate a salad for lunch today", post.content()); | |
1303 | } | |
1304 | ``` | |
1305 | ||
1306 | Listing 17-21: Modifications to `main` to use the new implementation of the | |
1307 | blog post workflow | |
1308 | ||
abe05a73 XL |
1309 | These changes we need to make to `main` to reassign `post` means this |
1310 | implementation doesn’t quite follow the object-oriented state pattern anymore: | |
1311 | the transformations between the states are no longer encapsulated entirely | |
1312 | within the `Post` implementation. However, our gain is that invalid states are | |
1313 | now impossible because of the type system and the type checking that happens at | |
1314 | compile time! This ensures that certain bugs, such as the content of an | |
1315 | unpublished post being displayed, will be discovered before they make it to | |
1316 | production. | |
1317 | ||
1318 | Try the tasks suggested for additional requirements that we mentioned at the | |
1319 | start of this section on this code, to see how working with this version of the | |
1320 | code feels. | |
1321 | ||
1322 | We’ve seen that even though Rust is capable of implementing object-oriented | |
1323 | design patterns, other patterns like encoding state into the type system are | |
1324 | also available in Rust. These patterns have different tradeoffs. While you may | |
1325 | be very familiar with object-oriented patterns, rethinking the problem in order | |
1326 | to take advantage of Rust’s features can provide benefits like preventing some | |
1327 | bugs at compile-time. Object-oriented patterns won’t always be the best | |
1328 | solution in Rust, because of the features like ownership that object-oriented | |
1329 | languages don’t have. | |
cc61c64b XL |
1330 | |
1331 | ## Summary | |
1332 | ||
1333 | No matter whether you think Rust is an object-oriented language or not after | |
3b2f2976 | 1334 | reading this chapter, you’ve now seen that trait objects are a way to get some |
cc61c64b XL |
1335 | object-oriented features in Rust. Dynamic dispatch can give your code some |
1336 | flexibility in exchange for a bit of runtime performance. This flexibility can | |
1337 | be used to implement object-oriented patterns that can help with the | |
abe05a73 XL |
1338 | maintainability of your code. Rust also has other different features, like |
1339 | ownership, that object-oriented languages don’t have. An object-oriented | |
1340 | pattern won’t always be the best way to take advantage of Rust’s strengths, but | |
1341 | is an available option. | |
cc61c64b | 1342 | |
3b2f2976 XL |
1343 | Next, let’s look at another feature of Rust that enables lots of flexibility: |
1344 | patterns. We’ve looked at them briefly throughout the book, but haven’t seen | |
1345 | everything they’re capable of yet. Let’s go! |