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