Propagate nostarch changes to src

This commit is contained in:
Carol (Nichols || Goulding)
2017-10-18 15:39:10 -04:00
parent 3c871f6172
commit 28d0efb644
4 changed files with 535 additions and 509 deletions

View File

@@ -2,7 +2,10 @@
Object-Oriented Programming is a way of modeling programs that originated with
Simula in the 1960s and became popular with C++ in the 1990s. There are many
competing definitions for what OOP is: under some definitions, Rust is
object-oriented; under other definitions, Rust is not. In this chapter, well
competing definitions for what counts as OOP, and under some definitions, Rust
is object-oriented; under other definitions, it is not. In this chapter, well
explore some characteristics that are commonly considered to be object-oriented
and how those characteristics translate to idiomatic Rust.
and how those characteristics translate to idiomatic Rust. Well then show you
how to implement an object-oriented design pattern in Rust and discuss the
tradeoffs of doing so versus implementing a solution using some of Rusts
strengths instead.

View File

@@ -1,15 +1,23 @@
## What Does Object-Oriented Mean?
There isnt consensus in the programming community about the features a
language needs to have in order to be called object-oriented. Rust is
influenced by many different programming paradigms; we explored the features it
has that come from functional programming in Chapter 13. Some of the
characteristics that object-oriented programming languages tend to share are
objects, encapsulation, and inheritance. Lets take a look at what each of
those mean and whether Rust supports them.
Theres no consensus in the programming community about what features a
language needs in order to be called object-oriented. Rust is influenced by
many different programming paradigms including OOP; we explored, for example,
the features that came from functional programming in Chapter 13. Arguably,
object-oriented programming languages do tend to share certain common
characteristics, namely objects, encapsulation, and inheritance. Lets take a
look at what each of those mean and whether Rust supports them.
### Objects Contain Data and Behavior
<!-- Is there a reason we're using this book as the reference, is it generally
accepted as an authority? -->
<!-- Yes, it is. For example, Martin Fowler (himself regarded as an authority)
had this to say about it https://www.martinfowler.com/bliki/GangOfFour.html:
> In my view the Gang of Four is the best book ever written on object-oriented
> design - possibly of any style of design.
/Carol -->
The book “Design Patterns: Elements of Reusable Object-Oriented Software,”
colloquially referred to as “The Gang of Four book,” is a catalog of
object-oriented design patterns. It defines object-oriented programming in this
@@ -22,27 +30,27 @@ way:
Under this definition, then, Rust is object-oriented: structs and enums have
data and `impl` blocks provide methods on structs and enums. Even though
structs and enums with methods arent *called* objects, they provide the same
functionality that objects do, using the Gang of Fours definition of objects.
functionality, under the Gang of Fours definition of objects.
### Encapsulation that Hides Implementation Details
Another aspect commonly associated with object-oriented programming is the idea
of *encapsulation*: the implementation details of an object arent accessible
to code using that object. The only way to interact with an object is through
the public API the object offers; code using the object should not be able to
reach into the objects internals and change data or behavior directly.
Encapsulation enables changing and refactoring an objects internals without
of *encapsulation*: that the implementation details of an object arent
accessible to code using that object. The only way to interact with an object
therefore is through its public API; code using the object should not be able
to reach into the objects internals and change data or behavior directly. This
enables the programmer to change and refactor an objects internals without
needing to change the code that uses the object.
As we discussed in Chapter 7, we can use the `pub` keyword to decide what
modules, types, functions, and methods in our code should be public, and by
default, everything is private. For example, we can define a struct
`AveragedCollection` that has a field containing a vector of `i32` values. The
struct can also have a field that knows the average of the values in the vector
so that whenever anyone wants to know the average of the values that the struct
has in its vector, we dont have to compute it on-demand. `AveragedCollection`
will cache the calculated average for us. Listing 17-1 has the definition of
the `AveragedCollection` struct:
We discussed an example of this in Chapter 7: We can use the `pub` keyword to
decide what modules, types, functions, and methods in our code should be
public, and by default everything else is private. For example, we can define a
struct `AveragedCollection` that has a field containing a vector of `i32`
values. The struct can also have a field that contains the average of the
values in the vector, meaning the average doesnt have to be computed on-demand
whenever anyone needs it. In other words, `AveragedCollection` will cache the
calculated average for us. Listing 17-1 has the definition of the
`AveragedCollection` struct:
<span class="filename">Filename: src/lib.rs</span>
@@ -57,11 +65,11 @@ pub struct AveragedCollection {
maintains a list of integers and the average of the items in the
collection.</span>
Note that the struct itself is marked `pub` so that other code may use this
struct, but the fields within the struct remain private. This is important in
this case because we want to ensure that whenever a value is added or removed
from the list, we also update the average. We do this by implementing `add`,
`remove`, and `average` methods on the struct as shown in Listing 17-2:
The struct itself is marked `pub` so that other code may use it, but the fields
within the struct remain private. This is important in this case because we
want to ensure that whenever a value is added or removed from the list, the
average is also updated. We do this by implementing `add`, `remove`, and
`average` methods on the struct as shown in Listing 17-2:
<span class="filename">Filename: src/lib.rs</span>
@@ -102,90 +110,88 @@ impl AveragedCollection {
`add`, `remove`, and `average` on `AveragedCollection`</span>
The public methods `add`, `remove`, and `average` are the only way to modify an
instance of a `AveragedCollection`. When an item is added to `list` using the
`add` method or removed using the `remove` method, the implementations of those
methods call the private `update_average` method that takes care of updating
the `average` field as well. Because the `list` and `average` fields are
private, theres no way for external code to add or remove items to the `list`
field directly, which could cause the `average` field to get out of sync. The
`average` method returns the value in the `average` field, which allows
external code to read the `average` but not modify it.
instance of `AveragedCollection`. When an item is added to `list` using the
`add` method or removed using the `remove` method, the implementations of each
call the private `update_average` method that takes care of updating the
`average` field as well.
We leave the `list` and `average` fields private so that theres no way for
external code to add or remove items to the `list` field directly, otherwise
the `average` field might become out of sync. The `average` method returns the
value in the `average` field, allowing external code to read the `average` but
not modify it.
Because weve encapsulated the implementation details of `AveragedCollection`,
we can easily change aspects like the data structure in the future. For
instance, we could use a `HashSet` instead of a `Vec` for the `list` field. As
long as the signatures of the `add`, `remove`, and `average` public methods
stay the same, code using `AveragedCollection` wouldnt need to change. This
wouldnt necessarily be the case if we exposed `list` to external code:
`HashSet` and `Vec` have different methods for adding and removing items, so
the external code would likely have to change if it was modifying `list`
directly.
stay the same, code using `AveragedCollection` wouldnt need to change. If we
made `list` public instead, this wouldnt necessarily be the case: `HashSet`
and `Vec` have different methods for adding and removing items, so the external
code would likely have to change if it was modifying `list` directly.
If encapsulation is a required aspect for a language to be considered
object-oriented, then Rust meets that requirement. Using `pub` or not for
different parts of code enables encapsulation of implementation details.
object-oriented, then Rust meets that requirement. The option to use `pub` or
not for different parts of code enables encapsulation of implementation details.
### Inheritance as a Type System and as Code Sharing
*Inheritance* is a mechanism that some programming languages provide whereby an
object can be defined to inherit from another objects definition, thus gaining
the parent objects data and behavior without having to define those again.
Inheritance is a characteristic that is part of some peoples definitions of
what an OOP language is.
*Inheritance* is a mechanism whereby an object can inherit from another
objects definition, thus gaining the parent objects data and behavior without
you having to define them again.
If a language must have inheritance to be an object-oriented language, then
Rust is not object-oriented. There is not a way to define a struct that
inherits from another struct in order to gain the parent structs fields and
method implementations. However, if youre used to having inheritance in your
programming toolbox, there are other solutions in Rust depending on the reason
you want to use inheritance.
Rust is not. There is no way to define a struct that inherits the parent
structs fields and method implementations. However, if youre used to having
inheritance in your programming toolbox, there are other solutions in Rust
depending on your reasons for using inheritance.
There are two main reasons to reach for inheritance. The first is to be able to
re-use code: once a particular behavior is implemented for one type,
inheritance can enable re-using that implementation for a different type. Rust
code can be shared using default trait method implementations instead, which we
saw in Listing 10-15 when we added a default implementation of the `summary`
method on the `Summarizable` trait. Any type implementing the `Summarizable`
trait would have the `summary` method available on it without any further code.
This is similar to a parent class having an implementation of a method, and a
child class inheriting from the parent class also having the implementation of
the method due to the inheritance. We can also choose to override the default
implementation of the `summary` method when we implement the `Summarizable`
trait, which is similar to a child class overriding the implementation of a
method inherited from a parent class.
There are two main reasons to reach for inheritance. The first is for re-use of
code: you can implement particular behavior for one type, and inheritance
enables you to re-use that implementation for a different type. Rust code can
be shared using default trait method implementations instead, which we saw in
Listing 10-15 when we added a default implementation of the `summary` method on
the `Summarizable` trait. Any type implementing the `Summarizable` trait would
have the `summary` method available on it without any further code. This is
similar to a parent class having an implementation of a method, and an
inheriting child class then also having the implementation of the method. We
can also choose to override the default implementation of the `summary` method
when we implement the `Summarizable` trait, similar to a child class overriding
the implementation of a method inherited from a parent class.
The second reason to use inheritance is with the type system: to express that a
child type can be used in the same places that the parent type can be used.
This is also called *polymorphism*, which means that multiple objects can be
substituted for each other at runtime if they have the same shape.
The second reason to use inheritance relates to the type system: to enable a
child type to be used in the same places as the parent type. This is also
called *polymorphism*, which means that multiple objects can be substituted for
each other at runtime if they share certain characteristics.
<!-- What does it mean for objects to have the same shape? -->
<!-- The use of "shape" in this context has to do with the roots of "morph" in
"polymorphism", but it's not very well defined so I've reworded. /Carol -->
<!-- PROD: START BOX -->
> While many people use “polymorphism” to describe inheritance, its actually
> a specific kind of polymorphism, called “sub-type polymorphism.” There are
> other forms as well; a generic parameter with a trait bound in Rust is
> also polymorphism, more specifically “parametric polymorphism.” The exact
> details between the different kinds of polymorphism arent crucial here,
> so dont worry too much about the details: just know that Rust has multiple
> Polymorphism
>
> Many people use “polymorphism” to describe inheritance itself, but what were
> discussing here is actually a specific kind of polymorphism, called “sub-type
> polymorphism.” There are other forms as well; a generic parameter with a
> trait bound in Rust is “parametric polymorphism,” for example. The exact
> details of the different kinds of polymorphism arent crucial here, so dont
> worry too much about them: just know that Rust has multiple
> polymorphism-related features, unlike many OOP languages.
<!-- PROD: END BOX -->
To support this sort of pattern, Rust has *trait objects* so that we can
specify that we would like values of any type, as long as the values implement
a particular trait.
Inheritance has recently fallen out of favor as a programming design solution
in many programming languages. Using inheritance to re-use some code can
require more code to be shared than you actually need. Subclasses shouldnt
always share all characteristics of their parent class, but inheritance means
the subclass gets all of its parents data and behavior. This can make a
programs design less flexible, and creates the possibility of calling methods
on subclasses that dont make sense or cause errors since the methods dont
apply to the subclass but must be inherited from the parent class. In addition,
some languages only allow a subclass to inherit from one class, further
restricting the flexibility of a programs design.
in many programming languages because its often at risk of sharing more code
than needs be. Subclasses shouldnt always share all characteristics of their
parent class, but will do so with inheritance. This can make a programs design
less flexible, and introduces the possibility of calling methods on subclasses
that dont make sense or that cause errors because the methods dont actually
apply to the subclass. Some languages will also only allow a subclass to
inherit from one class, further restricting the flexibility of a programs
design.
For these reasons, Rust chose to take a different approach with trait objects
For these reasons, Rust chose to take a different approach, using trait objects
instead of inheritance. Lets take a look at how trait objects enable
polymorphism in Rust.

View File

@@ -1,70 +1,71 @@
## Trait Objects for Using Values of Different Types
## Using Trait Objects that Allow for Values of Different Types
In Chapter 8, we said that a limitation of vectors is that vectors can only
store elements of one type. We had an example in Listing 8-1 where we defined a
`SpreadsheetCell` enum that had variants to hold integers, floats, and text so
that we could store different types of data in each cell and still have a
vector represent a row of cells. This works for cases in which the kinds of
things we want to be able to treat interchangeably are a fixed set of types that
we know when our code gets compiled.
In Chapter 8, we mentioned that one limitation of vectors is that they can only
store elements of one type. We created a workaround in Listing 8-10 where we
defined a `SpreadsheetCell` enum that had variants to hold integers, floats,
and text. This meant we could store different types of data in each cell and
still have a vector that represented a row of cells. This is a perfectly good
solution when our interchangeable items are a fixed set of types that we know
when our code gets compiled.
<!-- The code example I want to reference did not have a listing number; it's
the one with SpreadsheetCell. I will go back and add Listing 8-1 next time I
get Chapter 8 for editing. /Carol -->
Sometimes, however, we want the user of our library to be able to extend the
set of types that are valid in a particular situation. To show how we might
achieve this, well create an example Graphical User Interface tool that
iterates through a list of items, calling a `draw` method on each one to drawn
it to the screen; a common technique for GUI tools. Were going to create a
library crate containing the structure of a GUI library called `rust_gui`. This
crate might include some types for people to use, such as `Button` or
`TextField`. On top of these, users of `rust_gui` will want to create their own
types that can be drawn on the screen: for instance, one programmer might add
an `Image`, another might add a `SelectBox`.
Sometimes we want the set of types that we use to be extensible by the
programmers who use our library. For example, many Graphical User Interface
tools have a concept of a list of items that get drawn on the screen by
iterating through the list and calling a `draw` method on each of the items.
Were going to create a library crate containing the structure of a GUI library
called `rust_gui`. Our GUI library could include some types for people to use,
such as `Button` or `TextField`. Programmers that use `rust_gui` will want to
create more types that can be drawn on the screen: one programmer might add an
`Image`, while another might add a `SelectBox`. Were not going to implement a
fully-fledged GUI library in this chapter, but we will show how the pieces
would fit together.
We wont implement a fully-fledged GUI library for this example, but will show
how the pieces would fit together. At the time of writing the library, we cant
know and define all the types other programmers will want to create. What we do
know is that `rust_gui` needs to keep track of a bunch of values that are of
different types, and it needs to be able to call a `draw` method on each of
these differently-typed values. It doesnt need to know exactly what will
happen when we call the `draw` method, just that the value will have that
method available for us to call.
When were writing the `rust_gui` library, we dont know all the types that
other programmers will want to create, so we cant define an `enum` containing
all the types. What we do know is that `rust_gui` needs to be able to keep
track of a bunch of values of all these different types, and it needs to be
able to call a `draw` method on each of these values. Our GUI library doesnt
need to know what will happen exactly when we call the `draw` method, just that
the value will have that method available for us to call.
To do this in a language with inheritance, we might define a class named
`Component` that has a method named `draw` on it. The other classes like
`Button`, `Image`, and `SelectBox` would inherit from `Component` and thus
inherit the `draw` method. They could each override the `draw` method to define
their custom behavior, but the framework could treat all of the types as if
they were `Component` instances and call `draw` on them. But Rust doesnt have
inheritance, so we need another way.
In a language with inheritance, we might define a class named `Component` that
has a method named `draw` on it. The other classes like `Button`, `Image`, and
`SelectBox` would inherit from `Component` and thus inherit the `draw` method.
They could each override the `draw` method to define their custom behavior, but
the framework could treat all of the types as if they were `Component`
instances and call `draw` on them.
### Defining a Trait for Common Behavior
### Defining a Trait for the Common Behavior
To implement the behavior we want `rust_gui` to have, well define a trait
named `Draw` that will have one method named `draw`. Then we can define a
vector that takes a *trait object*---this is a trait behind some sort of
pointer, such as a `&` reference or a `Box<T>` smart pointer (well talk about
the reason trait objects have to be behind a pointer in Chapter 19 in the
section on Dynamically Sized Types). We can use trait objects in place of a
generic or concrete type. Wherever we use a trait object, Rusts type system
will ensure at compile-time that any value used in that context will implement
the trait objects trait. This way we dont need to know all the possible types
at compile time
In Rust, though, we can define a trait that well name `Draw` and that will
have one method named `draw`. Then we can define a vector that takes a *trait
object*, which is a trait behind some sort of pointer, such as a `&` reference
or a `Box<T>` smart pointer. Well talk about the reason trait objects have to
be behind a pointer in Chapter 19.
<!-- What will the trait object do in this case? I've taken this last part of
the line from below, but I'm not 100% on that -->
<!-- I've moved up more and reworded a bit, hope that clarifies /Carol -->
We mentioned that we dont call structs and enums “objects” to distinguish
structs and enums from other languages objects. The data in the struct or enum
fields and the behavior in `impl` blocks is separated, as opposed to other
languages that have data and behavior combined into one concept called an
object. Trait objects *are* more like objects in other languages, in the sense
that they combine the data made up of the pointer to a concrete object with the
behavior of the methods defined in the trait. However, trait objects are
different from objects in other languages because we cant add data to a trait
object. Trait objects arent as generally useful as objects in other languages:
their purpose is to allow abstraction across common behavior.
Weve mentioned that in Rust we refrain from calling structs and enums
“objects” to distinguish them from other languages objects. In a struct or
enum, the data in the struct fields and the behavior in `impl` blocks is
separated, whereas in other languages the data and behavior combined into one
concept is often labeled an object. Trait objects, though, *are* more like
objects in other languages, in the sense that they combine both data and
behavior. However, trait objects differ from traditional objects in that we
cant add data to a trait object. Trait objects arent as generally useful as
objects in other languages: their specific purpose is to allow abstraction
across common behavior.
A trait defines behavior that we need in a given situation. We can then use a
trait as a trait object in places where we would use a concrete type or a
generic type. Rusts type system will ensure that any value we substitute in
for the trait object will implement the methods of the trait. Then we dont
need to know all the possible types at compile time, and we can treat all the
instances the same way. Listing 17-3 shows how to define a trait named `Draw`
with one method named `draw`:
Listing 17-3 shows how to define a trait named `Draw` with one method named
`draw`:
<span class="filename">Filename: src/lib.rs</span>
@@ -76,13 +77,17 @@ pub trait Draw {
<span class="caption">Listing 17-3: Definition of the `Draw` trait</span>
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
This should look familiar from our discussions on how to define traits in
Chapter 10. Next comes something new: Listing 17-4 defines a struct named
`Screen` that holds a vector named `components`. This vector is of type
`Box<Draw>`, which is a trait object: its a stand-in for any type inside a
`Box` that implements the `Draw` trait.
This should look familiar since we talked about how to define traits in
Chapter 10. Next comes something new: Listing 17-4 has the definition of a
struct named `Screen` that holds a vector named `components` that are of type
`Box<Draw>`. That `Box<Draw>` is a trait object: its a stand-in for any type
inside a `Box` that implements the `Draw` trait.
<!-- Would it be useful to let the reader know why we need a box here, or will
that be clear at this point? -->
<!-- We get into this in chapter 19; I've added a reference to the start of
this section where we talk about needing a `&` or a `Box` to be a trait object.
/Carol -->
<span class="filename">Filename: src/lib.rs</span>
@@ -97,11 +102,11 @@ pub struct Screen {
```
<span class="caption">Listing 17-4: Definition of the `Screen` struct with a
`components` field that holds a vector of trait objects that implement the
`Draw` trait</span>
`components` field holding a vector of trait objects that implement the `Draw`
trait</span>
On the `Screen` struct, well define a method named `run`, which will call the
`draw` method on each of its `components` as shown in Listing 17-5:
On the `Screen` struct, well define a method named `run` that will call the
`draw` method on each of its `components`, as shown in Listing 17-5:
<span class="filename">Filename: src/lib.rs</span>
@@ -126,7 +131,7 @@ impl Screen {
<span class="caption">Listing 17-5: Implementing a `run` method on `Screen`
that calls the `draw` method on each component</span>
This is different than defining a struct that uses a generic type parameter
This works differently to defining a struct that uses a generic type parameter
with trait bounds. A generic type parameter can only be substituted with one
concrete type at a time, while trait objects allow for multiple concrete types
to fill in for the trait object at runtime. For example, we could have defined
@@ -156,20 +161,20 @@ impl<T> Screen<T>
<span class="caption">Listing 17-6: An alternate implementation of the `Screen`
struct and its `run` method using generics and trait bounds</span>
This only lets us have a `Screen` instance that has a list of components that
are all of type `Button` or all of type `TextField`. If youll only ever have
homogeneous collections, using generics and trait bounds is preferable since
the definitions will be monomorphized at compile time to use the concrete types.
This restricts us to a `Screen` instance that has a list of components all of
type `Button` or all of type `TextField`. If youll only ever have homogeneous
collections, using generics and trait bounds is preferable since the
definitions will be monomorphized at compile time to use the concrete types.
With the definition of `Screen` that holds a component list of trait objects in
`Vec<Box<Draw>>` instead, one `Screen` instance can hold a `Vec` that contains
a `Box<Button>` as well as a `Box<TextField>`. Lets see how that works, and
then talk about the runtime performance implications.
With the the method using trait objects, on the other hand, one `Screen`
instance can hold a `Vec` that contains a `Box<Button>` as well as a
`Box<TextField>`. Lets see how that works, and then talk about the runtime
performance implications.
### Implementations of the Trait from Us or Library Users
### Implementing the Trait
Now to add some types that implement the `Draw` trait. Were going to provide
the `Button` type, and again, actually implementing a GUI library is out of
Now well add some types that implement the `Draw` trait. Were going to
provide the `Button` type. Again, actually implementing a GUI library is out of
scope of this book, so the `draw` method wont have any useful implementation
in its body. To imagine what the implementation might look like, a `Button`
struct might have fields for `width`, `height`, and `label`, as shown in
@@ -198,15 +203,15 @@ impl Draw for Button {
<span class="caption">Listing 17-7: A `Button` struct that implements the
`Draw` trait</span>
The `width`, `height`, and `label` fields on `Button` will differ from other
components, such as a `TextField` type that might have `width`, `height`,
`label`, and `placeholder` fields instead. Each of the types that we want to be
able to draw on the screen will implement the `Draw` trait with different code
in the `draw` method that defines how to draw that type like `Button` has here
(without any actual GUI code thats out of scope of this chapter). In addition
to implementing the `Draw` trait, `Button` might also have another `impl` block
containing methods having to do with what happens if the button is clicked.
These kinds of methods wont apply to types like `TextField`.
The `width`, `height`, and `label` fields on `Button` will differ from the
fields on other components, such as a `TextField` type that might have those
plus a `placeholder` field instead. Each of the types we want to draw on the
screen will implement the `Draw` trait, with different code in the `draw`
method to define how to draw that particular type, like `Button` has here
(without the actual GUI code thats out of scope of this chapter). `Button`,
for instance, might have an additional `impl` block containing methods related
to what happens if the button is clicked. These kinds of methods wont apply to
types like `TextField`.
Someone using our library has decided to implement a `SelectBox` struct that
has `width`, `height`, and `options` fields. They implement the `Draw` trait on
@@ -235,7 +240,7 @@ impl Draw for SelectBox {
implementing the `Draw` trait on a `SelectBox` struct</span>
The user of our library can now write their `main` function to create a
`Screen` instance and add a `SelectBox` and a `Button` to the screen by putting
`Screen` instance. To this they can add a `SelectBox` and a `Button` by putting
each in a `Box<T>` to become a trait object. They can then call the `run`
method on the `Screen` instance, which will call `draw` on each of the
components. Listing 17-9 shows this implementation:
@@ -272,26 +277,37 @@ fn main() {
<span class="caption">Listing 17-9: Using trait objects to store values of
different types that implement the same trait</span>
Even though we didnt know that someone would add the `SelectBox` type someday,
our `Screen` implementation was able to operate on the `SelectBox` and draw it
because `SelectBox` implements the `Draw` type, which means it implements the
`draw` method.
When we wrote the library, we didnt know that someone would add the
`SelectBox` type someday, but our `Screen` implementation was able to operate
on the new type and draw it because `SelectBox` implements the `Draw` type,
which means it implements the `draw` method.
Only being concerned with the messages a value responds to, rather than the
values concrete type, is similar to a concept called *duck typing* in
dynamically typed languages: if it walks like a duck, and quacks like a duck,
then it must be a duck! In the implementation of `run` on `Screen` in Listing
17-5, `run` doesnt need to know what the concrete type of each component is.
It doesnt check to see if a component is an instance of a `Button` or a
`SelectBox`, it just calls the `draw` method on the component. By specifying
`Box<Draw>` as the type of the values in the `components` vector, weve defined
that `Screen` needs values that we can call the `draw` method on.
This concept---of being concerned only with the messages a value responds to,
rather than the values concrete type---is similar to a concept in dynamically
typed languages called *duck typing*: if it walks like a duck, and quacks like
a duck, then it must be a duck! In the implementation of `run` on `Screen` in
Listing 17-5, `run` doesnt need to know what the concrete type of each
component is. It doesnt check to see if a component is an instance of a
`Button` or a `SelectBox`, it just calls the `draw` method on the component. By
specifying `Box<Draw>` as the type of the values in the `components` vector,
weve defined `Screen` to need values that we can call the `draw` method on.
The advantage with using trait objects and Rusts type system to do duck typing
is that we never have to check that a value implements a particular method at
<!-- I may be slow on the uptake here, but it seems like we're saying that
responsibility for how the type trait object behaves with the draw method is
called on it belongs to the trait object, and not to the draw method itself. Is
that an accurate summary? I want to make sure I'm clearly following the
argument! -->
<!-- Each type (like `Button` or `SelectBox`) that implements the `Draw` trait
can customize what happens in the body of the `draw` method. The trait object
is just responsible for making sure that the only things that are usable in
that context are things that implement the `Draw` trait. Does this clear it up
at all? Is there something we should clarify in the text? /Carol -->
The advantage of using trait objects and Rusts type system for duck typing is
that we never have to check that a value implements a particular method at
runtime or worry about getting errors if a value doesnt implement a method but
we call it. Rust wont compile our code if the values dont implement the
traits that the trait objects need.
we call it anyway. Rust wont compile our code if the values dont implement
the traits that the trait objects need.
For example, Listing 17-10 shows what happens if we try to create a `Screen`
with a `String` as a component:
@@ -329,29 +345,37 @@ error[E0277]: the trait bound `std::string::String: Draw` is not satisfied
= note: required for the cast to the object type `Draw`
```
This lets us know that either were passing something we didnt mean to pass to
`Screen` and we should pass a different type, or we should implement `Draw` on
This lets us know that either were passing something to `Screen` we didnt
mean to pass, and we should pass a different type, or implement `Draw` on
`String` so that `Screen` is able to call `draw` on it.
### Trait Objects Perform Dynamic Dispatch
Recall in Chapter 10 when we discussed the process of monomorphization that the
compiler performs when we use trait bounds on generics: the compiler generates
Recall from Chapter 10 our discussion on the monomorphization process performed
by the compiler when we use trait bounds on generics: the compiler generates
non-generic implementations of functions and methods for each concrete type
that we use in place of a generic type parameter. The code that results from
monomorphization is doing *static dispatch*: when the method is called, the
code that goes with that method call has been determined at compile time, and
looking up that code is very fast.
monomorphization is doing *static dispatch*. Static dispatch is when the
compiler knows what method youre calling at compile time. This is opposed to
*dynamic dispatch*, when the compiler cant tell at compile time which method
youre calling. In these cases, the compiler emits code that will figure out at
runtime which method to call.
When we use trait objects, the compiler cant perform monomorphization because
we dont know all the types that might be used with the code. Instead, Rust
keeps track of the code that might be used when a method is called and figures
out at runtime which code needs to be used for a particular method call. This
is known as *dynamic dispatch*, and theres a runtime cost when this lookup
happens. Dynamic dispatch also prevents the compiler from choosing to inline a
methods code, which prevents some optimizations. We did get extra flexibility
in the code that we wrote and were able to support, though, so its a tradeoff
to consider.
<!--I'm struggling to follow the static dispatch definition, can you expand
that a little? Which part of that is the static dispatch, pre-determining the
code called with a method and storing it? -->
<!-- Yes, in a way. We've expanded and moved the definitions of static and
dynamic dispatch together to better contrast, hopefully this helps? /Carol -->
When we use trait objects, Rust has to use dynamic dispatch. The compiler
doesnt know all the types that might be used with the code using trait
objects, so it doesnt know which method implemented on which type to call.
Instead, Rust uses the pointers inside of the trait object at runtime to know
which specific method to call. Theres a runtime cost when this lookup happens,
compared to static dispatch. Dynamic dispatch also prevents the compiler from
choosing to inline a methods code, which prevents some optimizations. By using
this method, though, we did get extra flexibility in the code that we wrote and
were able to support, so its a tradeoff to consider.
### Object Safety is Required for Trait Objects
@@ -364,66 +388,30 @@ a quick caveat, that just says something like "Some traits can't be trait
objects. Clone is an example of one. You'll get errors that will let you know
if a trait can't be a trait object, look up object safety if you're interested
in the details"? Thanks! /Carol -->
<!-- That sounds like a good solution, since the compiler will warn them in any
case. I read through, editing a little, and I agree we could afford to cut it,
I'm not sure it brings practical skils to the user -->
<!-- Ok, I've cut section way down to the practical pieces, but still explained
a little bit /Carol -->
Not all traits can be made into trait objects; only *object safe* traits can. A
trait is object safe as long as both of the following are true:
Only *object safe* traits can be made into trait objects. There are some
complex rules around all the properties that make a trait object safe, but in
practice, there are only two rules that are relevant. A trait is object safe if
all of the methods defined in the trait have the following properties:
* The trait does not require `Self` to be `Sized`
* All of the traits methods are object safe.
- The return type isnt `Self`
- There arent any generic type parameters
`Self` is a keyword that is an alias for the type that were implementing
traits or methods on. `Sized` is a marker trait like the `Send` and `Sync`
traits that we talked about in Chapter 16. `Sized` is automatically implemented
on types that have a known size at compile time, such as `i32` and references.
Types that do not have a known size include slices (`[T]`) and trait objects.
`Sized` is an implicit trait bound on all generic type parameters by default.
Most useful operations in Rust require a type to be `Sized`, so making `Sized`
a default requirement on trait bounds means we dont have to write `T: Sized`
with most every use of generics. If we want to be able to use a trait on
slices, however, we need to opt out of the `Sized` trait bound, and we can do
that by specifying `T: ?Sized` as a trait bound.
Traits have a default bound of `Self: ?Sized`, which means that they can be
implemented on types that may or may not be `Sized`. If we create a trait `Foo`
that opts out of the `Self: ?Sized` bound, that would look like the following:
```rust
trait Foo: Sized {
fn some_method(&self);
}
```
The trait `Sized` is now a *supertrait* of trait `Foo`, which means trait `Foo`
requires types that implement `Foo` (that is, `Self`) to be `Sized`. Were
going to talk about supertraits in more detail in Chapter 19.
`Foo` requires `Self` to be `Sized`, and therefore is not allowed to be used in
a trait object like `Box<Foo>`. This is because it would be impossible to implement
the trait `Foo` for a trait object like `Box<Foo>`: trait objects arent sized,
but `Foo` requires `Self` to be `Sized`. A type cant be both sized and unsized
at the same time!
For the second object safety requirement that says all of a traits methods
must be object safe, a method is object safe if either:
* It requires `Self` to be `Sized` or
* It meets all three of the following:
* It must not have any generic type parameters
* Its first argument must be of type `Self` or a type that dereferences to
the Self type (that is, it must be a method rather than an associated
function and have `self`, `&self`, or `&mut self` as the first argument)
* It must not use `Self` anywhere else in the signature except for the
first argument
Those rules are a bit formal, but think of it this way: if your method requires
the concrete `Self` type somewhere in its signature, but an object forgets the
exact type that it is, theres no way that the method can use the original
concrete type that its forgotten. Same with generic type parameters that are
filled in with concrete type parameters when the trait is used: the concrete
types become part of the type that implements the trait. When the type is
erased by the use of a trait object, theres no way to know what types to fill
in the generic type parameters with.
The `Self` keyword is an alias for the type were implementing traits or
methods on. Object safety is required for trait objects because once you have a
trait object, you no longer know what the concrete type implementing that trait
is. If a trait method returns the concrete `Self` type, but a trait object
forgets the exact type that it is, theres no way that the method can use the
original concrete type that its forgotten. Same with generic type parameters
that are filled in with concrete type parameters when the trait is used: the
concrete types become part of the type that implements the trait. When the type
is erased by the use of a trait object, theres no way to know what types to
fill in the generic type parameters with.
An example of a trait whose methods are not object safe is the standard
librarys `Clone` trait. The signature for the `clone` method in the `Clone`
@@ -441,11 +429,6 @@ call `clone` on an instance of `Vec`, we get back an instance of `Vec`. The
signature of `clone` needs to know what type will stand in for `Self`, since
thats the return type.
If we try to implement `Clone` on a trait like the `Draw` trait from Listing
17-3, we wouldnt know whether `Self` would end up being a `Button`, a
`SelectBox`, or some other type that will implement the `Draw` trait in the
future.
The compiler will tell you if youre trying to do something that violates the
rules of object safety in regards to trait objects. For example, if we had
tried to implement the `Screen` struct in Listing 17-4 to hold types that
@@ -470,8 +453,7 @@ error[E0038]: the trait `std::clone::Clone` cannot be made into an object
= note: the trait cannot require that `Self : Sized`
```
<!-- If we are including this section, we would explain how to fix this
problem. It involves adding another trait and implementing Clone manually for
that trait. Because this section is getting long, I stopped because it feels
like we're off in the weeds with an esoteric detail that not everyone will need
to know about. /Carol -->
This means you cant use this trait as a trait object in this way. If youre
interested in more details on object safety, see [Rust RFC 255].
[Rust RFC 255]: https://github.com/rust-lang/rfcs/blob/master/text/0255-object-safety.md

View File

@@ -1,27 +1,31 @@
## Object-Oriented Design Pattern Implementation
## Implementing an Object-Oriented Design Pattern
Lets look at an example of the state design pattern and how to use it in Rust.
The *state pattern* is when a value has some internal state, and the values
behavior changes based on the internal state. The internal state is represented
by a set of objects that inherit shared functionality (well use structs and
traits since Rust doesnt have objects and inheritance). Each state object is
responsible for its own behavior and the rules for when it should change into
another state. The value that holds one of these state objects doesnt know
anything about the different behavior of the states or when to transition
between states. In the future when requirements change, we wont need to change
the code of the value holding the state or the code that uses the value. Well
only need to update the code inside one of the state objects to change its
rules, or perhaps add more state objects.
The *state pattern* is an object-oriented design pattern. The crux of the
pattern is that a value has some internal state, represented by a set of *state
objects*, and the values behavior changes based on the internal state. The
state objects share functionality--in Rust, of course, we use structs and
traits rather than objects and inheritance. Each state object representing the
state is responsible for its own behavior and for governing when it should
change into another state. The value that holds a state object knows nothing
about the different behavior of the states or when to transition between states.
In order to explore this idea, were going to implement a blog post workflow in
an incremental way. The workflow that we want our blog posts to follow, once
were done with the implementation, is:
<!-- Below -- requirements for what, for what we need the value for? -->
<!-- I've clarified /Carol -->
Using the state pattern means when the business requirements of the program
change, we wont need to change the code of the value holding the state or the
code that uses the value. Well only need to update the code inside one of the
state objects to change its rules, or perhaps add more state objects. Lets
look at an example of the state design pattern and how to use it in Rust.
To explore this idea, well implement a blog post workflow in an incremental
way. The blogs final functionality will look like this:
1. A blog post starts as an empty draft.
2. Once the draft is done, we request a review of the post.
2. Once the draft is done, a review of the post is requested.
3. Once the post is approved, it gets published.
4. Only published blog posts return content to print so that we cant
accidentally print the text of a post that hasnt been approved.
4. Only published blog posts return content to print, so unapproved posts cant
accidentally get published.
Any other changes attempted on a post should have no effect. For example, if we
try to approve a draft blog post before weve requested a review, the post
@@ -53,39 +57,50 @@ fn main() {
<span class="caption">Listing 17-11: Code that demonstrates the desired
behavior we want our `blog` crate to have</span>
We want to be able to create a new draft blog post with `Post::new`. Then, we
want to add some text to the blog post while were in the draft state. If we
try to print out the posts content immediately, though, we shouldnt get any
text, since the post is still a draft. Weve added an `assert_eq!` here for
demonstration purposes. Asserting that a draft blog post returns an empty
string from the `content` method would make an excellent unit test in our
library, but were not going to write tests for this example.
We want to allow the user to create a new draft blog post with `Post::new`.
Then, we want to allow text to be added to the blog post while its in the
draft state. If we try to get the posts content immediately, before
approval, nothing should happen because the post is still a draft. Weve added
an `assert_eq!` here for demonstration purposes. An excellent unit test for
this would be to assert that a draft blog post returns an empty string from the
`content` method, but were not going to write tests for this example.
Next, we want to be able to request a review of our post, and `content` should
still return an empty string while waiting for a review. Lastly, when we
approve the blog post, it should get published, which means the text we added
will be returned when we call `content`.
Next, we want to enable a request for a review of the post, and we want
`content` to return an empty string while waiting for the review. Lastly, when
the post receives approval, it should get published, meaning the text of the
post will be returned when `content` is called.
<!-- Below -- so this is where we'll implement the state pattern? If so, can
you make that explicit, just to be clear! I've added some text to the second
line, not sure if that's accurate though -->
<!-- Yes, the state pattern will be implemented within the `Post` type. I've
tweaked the wording a bit but you've pretty much got it! /Carol-->
Notice that the only type were interacting with from the crate is the `Post`
type. The various states a post can be in (draft, waiting for review,
published) are managed internally to the `Post` type. The states change due to
the methods we call on the `Post` instance, but we dont have to manage the
state changes directly. This also means we wont make a mistake with the
states, like forgetting to request a review before publishing.
type. This type will use the state pattern and will hold a value that will be
one of three state objects representing the various states a post can be
in---draft, waiting for review, or published. Changing from one state to
another will be managed internally within the `Post` type. The states change in
response to the methods users of our library call on the `Post` instance, but
they dont have to manage the state changes directly. This also means users
cant make a mistake with the states, like publishing a post before it is
reviewed.
### Defining `Post` and Creating a New Instance in the Draft State
Lets get started on the implementation of the library! We know we want to have
a public `Post` struct that holds some content, so lets start with the
Lets get started on the implementation of the library! We know we need a
public `Post` struct that holds some content, so lets start with the
definition of the struct and an associated public `new` function to create an
instance of `Post` as shown in Listing 17-12. Were also going to have a
private trait `State`. `Post` will hold a trait object of `Box<State>` inside
an `Option` in a private field named `state`. Well see why the `Option` is
necessary in a bit. The `State` trait defines all the behavior different post
states share, and the `Draft`, `PendingReview`, and `Published` states will all
implement the `State` trait. For now, the trait does not have any methods, and
were going to start by defining just the `Draft` state since thats the state
we want to start in:
instance of `Post`, as shown in Listing 17-12. Well also make a private
`State` trait. Then `Post` will hold a trait object of `Box<State>` inside an
`Option` in a private field named `state`. Well see why the `Option` is
necessary in a bit.
The `State` trait defines the behavior shared by different post states, and the
`Draft`, `PendingReview`, and `Published` states will all implement the `State`
trait. For now, the trait does not have any methods, and were going to start
by defining just the `Draft` state since thats the state we want a post to
start in:
<span class="filename">Filename: src/lib.rs</span>
@@ -113,24 +128,24 @@ impl State for Draft {}
<span class="caption">Listing 17-12: Definition of a `Post` struct and a `new`
function that creates a new `Post` instance, a `State` trait, and a `Draft`
struct that implements `State`</span>
struct</span>
When we create a new `Post`, we set its `state` field to a `Some` value holding
a `Box` pointing to a new instance of the `Draft` struct. This ensures whenever
we create a new instance of `Post`, itll start out as a draft. Because the
`state` field of `Post` is private, theres no way to create a `Post` in any
other state!
When we create a new `Post`, we set its `state` field to a `Some` value that
holds a `Box`. This `Box` points to a new instance of the `Draft` struct. This
ensures whenever we create a new instance of `Post`, itll start out as a
draft. Because the `state` field of `Post` is private, theres no way to create
a `Post` in any other state!
### Storing the Text of the Post Content
In the `Post::new` function, we set the `content` field to a new, empty
`String`. In Listing 17-11, we showed that we want to be able to call a method
named `add_text` and pass a `&str` to it to add that text to the content of the
blog post. Were choosing to implement this as a method rather than exposing
the `content` field as `pub` because we want to be able to control how the
`content` fields data is read by implementing a method later. The `add_text`
method is pretty straightforward though, lets add the implementation in
Listing 17-13 to the `impl Post` block:
`String`. Listing 17-11 showed that we want to be able to call a method named
`add_text` and pass it a `&str` thats then added to the text content of the
blog post. We implement this as a method rather than exposing the `content`
field as `pub`. This means we can implement a method later that will control
how the `content` fields data is read. The `add_text` method is pretty
straightforward, so lets add the implementation in Listing 17-13 to the `impl
Post` block:
<span class="filename">Filename: src/lib.rs</span>
@@ -153,20 +168,19 @@ text to a posts `content`</span>
`add_text` takes a mutable reference to `self`, since were changing the `Post`
instance that were calling `add_text` on. We then call `push_str` on the
`String` in `content` and pass the `text` argument to add to the saved
`content`. This isnt part of the state pattern since its behavior doesnt
depend on the state that the post is in. The `add_text` method doesnt interact
with the `state` field at all, but it is part of the behavior we want to
support.
`content`. This behavior doesnt depend on the state the post is in so its not
part of the state pattern. The `add_text` method doesnt interact with the
`state` field at all, but it is part of the behavior we want to support.
### Content of a Draft Post is Empty
### Ensuring the Content of a Draft Post is Empty
After weve called `add_text` and added some content to our post, we still want
the `content` method to return an empty string slice since the post is still in
the draft state, as shown on line 8 of Listing 17-11. For now, lets implement
the `content` method with the simplest thing that will fulfill this requirement:
always returning an empty string slice. Were going to change this later once
we implement the ability to change a posts state to be published. With what we
have so far, though, posts can only be in the draft state, which means the post
Even after weve called `add_text` and added some content to our post, we still
want the `content` method to return an empty string slice since the post is
still in the draft state, as shown on line 8 of Listing 17-11. For now, lets
implement the `content` method with the simplest thing that will fulfill this
requirement: always returning an empty string slice. Were going to change this
later once we implement the ability to change a posts state so it can be
published. So far, though, posts can only be in the draft state, so the post
content should always be empty. Listing 17-14 shows this placeholder
implementation:
@@ -193,17 +207,20 @@ works as we intend.
### Requesting a Review of the Post Changes its State
Next up is requesting a review of a post, which should change its state from
`Draft` to `PendingReview`. We want `Post` to have a public method named
`request_review` that will take a mutable reference to `self`. Then were going
to call an internal `request_review` method on the state that were holding, and
this second `request_review` method will consume the current state and return a
new state. In order to be able to consume the old state, the second `request_review`
method needs to take ownership of the state value. This is where the `Option` comes
in: were going to `take` the `Some` value out of the `state` field and leave a
`None` in its place since Rust doesnt let us have unpopulated fields in
structs. Then well set the posts `state` value to the result of this
operation. Listing 17-15 shows this code:
Next up we need to add functionality to request a review of a post, which
should change its state from `Draft` to `PendingReview`. We want to give `Post`
a public method named `request_review` that will take a mutable reference to
`self`. Then were going to call an internal `request_review` method on the
current state of `Post`, and this second `request_review` method will consume
the current state and return a new state. To consume the old state, the second
`request_review` method needs to take ownership of the state value. This is
where the `Option` comes in: were going to take the `Some` value out of the
`state` field and leave a `None` in its place, since Rust doesnt let us have
unpopulated fields in structs. Then well set the posts `state` value to the
result of this operation. Listing 17-15 shows this code:
<!-- NOTE TO DE/AU: We might want to move this explanation to after the code if
you want to add wingdings, we can see once we transfer it to Word -->
<span class="filename">Filename: src/lib.rs</span>
@@ -251,33 +268,34 @@ implement the trait will now need to implement the `request_review` method.
Note that rather than having `self`, `&self`, or `&mut self` as the first
parameter of the method, we have `self: Box<Self>`. This syntax means the
method is only valid when called on a `Box` holding the type. This syntax takes
ownership of `Box<Self>`, which is what we want because were transforming the
old state into a new state, and we want the old state to no longer be valid.
ownership of `Box<Self>`, invalidating the old state so that the state value of
the `Post` can transform itself into a new state.
The implementation for the `request_review` method on `Draft` is to return a
new, boxed instance of the `PendingReview` struct, which is a new type weve
introduced that represents the state when a post is waiting for a review. The
`PendingReview` struct also implements the `request_review` method, but it
doesnt do any transformations. It returns itself since requesting a review on
a post thats already in the `PendingReview` state should stay in the
`PendingReview` state.
<!-- Above -- so Post can transform, or so Draft can transform? -->
<!-- Technically it's so the Draft value can transform into another value,
which changes the state of Post-- I've tried to clarify. /Carol -->
The `request_review` method on `Draft` needs to return a new, boxed instance of
a new `PendingReview` struct, which represents the state when a post is waiting
for a review. The `PendingReview` struct also implements the `request_review`
method, but doesnt do any transformations. Rather, it returns itself, since
when we request a review on a post already in the `PendingReview` state, it
should stay in the `PendingReview` state.
Now we can start seeing the advantages of the state pattern: the
`request_review` method on `Post` is the same no matter what its `state` value
is. Each state is responsible for its own rules.
`request_review` method on `Post` is the same no matter its `state` value. Each
state is responsible for its own rules.
Were going to leave the `content` method on `Post` as it is, returning an
empty string slice. We can now have a `Post` in the `PendingReview` state, not
just the `Draft` state, but we want the same behavior in the `PendingReview`
empty string slice. We can now have a `Post` in the `PendingReview` state as
well as the `Draft` state, but we want the same behavior in the `PendingReview`
state. Listing 17-11 now works up until line 11!
### Approving a Post Changes the Behavior of `content`
### Adding the `approve` Method that Changes the Behavior of `content`
The `approve` method on `Post` will be similar to that of the `request_review`
method: it will set the `state` to the value that the current state says it
should have when that state is approved. Well need to add the `approve` method
to the `State` trait, and well add a new struct that implements `State`, the
`Published` state. Listing 17-16 shows the new code:
The `approve` method will be similar to the `request_review` method: it will
set `state` to the value that the current state says it should have when that
state is approved, shown in Listing 17-16.
<span class="filename">Filename: src/lib.rs</span>
@@ -343,20 +361,19 @@ impl State for Published {
<span class="caption">Listing 17-16: Implementing the `approve` method on
`Post` and the `State` trait</span>
Similarly to `request_review`, if we call the `approve` method on a `Draft`, it
We add the `approve` method to the `State` trait, and add a new struct that
implements `State`, the `Published` state.
Similar to `request_review`, if we call the `approve` method on a `Draft`, it
will have no effect since it will return `self`. When we call `approve` on
`PendingReview`, it returns a new, boxed instance of the `Published` struct.
The `Published` struct implements the `State` trait, and for both the
`request_review` method and the `approve` method, it returns itself since the
`request_review` method and the `approve` method, it returns itself, since the
post should stay in the `Published` state in those cases.
Now for updating the `content` method on `Post`: we want to return the value in
the posts `content` field if its state is `Published`, otherwise we want to
return an empty string slice. Because the goal is to keep all the rules like
this in the structs that implement `State`, were going to call a `content`
method on the value in `state` and pass the post instance (that is, `self`) as
an argument. Then well return the value returned from the `content` method on
the `state` value as shown in Listing 17-17:
Now to update the `content` method on `Post`: if the state is `Published` we
want to return the value in the posts `content` field; otherwise we want to
return an empty string slice, as shown in Listing 17-17:
<span class="filename">Filename: src/lib.rs</span>
@@ -381,19 +398,21 @@ impl Post {
<span class="caption">Listing 17-17: Updating the `content` method on `Post` to
delegate to a `content` method on `State`</span>
Were calling the `as_ref` method on the `Option` because we want a reference
to the value inside the `Option`. Were then calling the `unwrap` method, which
we know will never panic because all the methods on `Post` ensure that the
`state` value will have a `Some` value in it when those methods are done. This
is one of the cases we talked about in Chapter 12 where we know that a `None`
value is never possible even though the compiler isnt able to understand that.
Because the goal is to keep all these rules inside the structs that implement
`State`, we call a `content` method on the value in `state` and pass the post
instance (that is, `self`) as an argument. Then we return the value thats
returned from using the `content` method on the `state` value.
The `content` method on the `State` trait is where the logic for what content
to return will be. Were going to add a default implementation for the
`content` method that returns an empty string slice. That lets us not need to
implement `content` on the `Draft` and `PendingReview` structs. The `Published`
struct will override the `content` method and will return the value in
`post.content`, as shown in Listing 17-18:
We call the `as_ref` method on the `Option` so we get a reference to the value
inside the `Option` rather than ownership of it. Were then calling the
`unwrap` method, which we know will never panic, because we know the methods on
`Post` ensure that `state` will always contain a `Some` value when those
methods are done. This is one of the cases we talked about in Chapter 12 when
we know that a `None` value is never possible, even though the compiler isnt
able to understand that.
Well put the logic for the content to return in the `content` method on the
`State` trait, as shown in Listing 17-18:
<span class="filename">Filename: src/lib.rs</span>
@@ -422,78 +441,89 @@ impl State for Published {
<span class="caption">Listing 17-18: Adding the `content` method to the `State`
trait</span>
We add a default implementation for the `content` method that returns an empty
string slice. That means we dont need to implement `content` on the `Draft`
and `PendingReview` structs. The `Published` struct will override the `content`
method and return the value in `post.content`.
Note that we need lifetime annotations on this method, like we discussed in
Chapter 10. Were taking a reference to a `post` as an argument, and were
returning a reference to a part of that `post`, so the lifetime of the returned
reference is related to the lifetime of the `post` argument.
Chapter 10. Were taking a reference to a `post` as an argument, and returning
a reference to part of that `post`, so the lifetime of the returned reference
is related to the lifetime of the `post` argument.
<!-- Is this it finished, without the touch up we make to get rid of the empty
string? That's pretty awesome coding, maybe give it some ceremony here. Does
all of 17-11 now work? -->
<!-- Yep! Good point, so added! /Carol -->
And were done-- all of Listing 17-11 now works! Weve implemented the state
pattern with the rules of the blog post workflow. The logic around the rules
lives in the state objects rather than scattered throughout `Post`.
### Tradeoffs of the State Pattern
Weve shown that Rust is capable of implementing the object-oriented state
pattern in order to encapsulate the different kinds of behavior that a post
should have that depends on the state that the post is in. The methods on
`Post` dont know anything about the different kinds of behavior. The way this
code is organized, we have one place to look in order to find out all the
different ways that a published post behaves: the implementation of the `State`
trait on the `Published` struct.
pattern to encapsulate the different kinds of behavior a post should have in
each state. The methods on `Post` know nothing about the different kinds of
behavior. The way this code is organized, we only have to look in one place to
know the different ways a published post can behave: the implementation of the
`State` trait on the `Published` struct.
An alternative implementation that didnt use the state pattern might have
`match` statements in the methods on `Post` or even in the code that uses
`Post` (`main` in our case) that checks what the state of the post is and
changes behavior in those places instead. That would mean wed have a lot of
places to look in order to understand all the implications of a post being in
the published state! This would get worse the more states we added: each of
those `match` statements would need another arm. With the state pattern, the
`Post` methods and the places we use `Post` dont need `match` statements and
adding a new state only involves adding a new `struct` and implementing the
trait methods on that one struct.
If we were to create an alternative implementation that didnt use the state
pattern we might use `match` statements in the methods on `Post`, or even in
the `main` code that checks the state of the post and changes behavior in those
places instead. That would mean wed have to look in a lot of places to
understand all the implications of a post being in the published state! This
would only increase the more states we added: each of those `match` statements
would need another arm.
This implementation is easy to extend to add more functionality. Here are some
changes you can try making to the code in this section to see for yourself what
its like to maintain code using this pattern over time:
With the state pattern, the `Post` methods and the places we use `Post` dont
need `match` statements, and to add a new state we would only need to add a new
`struct` and implement the trait methods on that one struct.
- Only allow adding text content when a post is in the `Draft` state
This implementation is easy to extend to add more functionality. To see the
simplicity of mantaining code that uses this patterns, try out a few of these
suggestions:
- Allow users to add text content only when a post is in the `Draft` state
- Add a `reject` method that changes the posts state from `PendingReview` back
to `Draft`
- Require two calls to `approve` before changing the state to `Published`
- Require two calls to `approve` before the state can be changed to `Published`
A downside of the state pattern is that since the states implement the
transitions between the states, some of the states are coupled to each other.
If we add another state between `PendingReview` and `Published`, such as
`Scheduled`, we would have to change the code in `PendingReview` to transition
to `Scheduled` instead. It would be nicer if `PendingReview` wouldnt need to
change because of the addition of a new state, but that would mean switching to
One downside of the state pattern is that, because the states implement the
transitions between states, some of the states are coupled to each other. If we
add another state between `PendingReview` and `Published`, such as `Scheduled`,
we would have to change the code in `PendingReview` to transition to
`Scheduled` instead. It would be less work if `PendingReview` wouldnt need to
change with the addition of a new state, but that would mean switching to
another design pattern.
There are a few bits of duplicated logic that are a downside of this
implementation in Rust. It would be nice if we could make default
implementations for the `request_review` and `approve` methods on the `State`
trait that return `self`, but this would violate object safety since the trait
doesnt know what the concrete `self` will be exactly. We want to be able to
use `State` as a trait object, so we need its methods to be object safe.
Another downside is that we find ourselves with a few bits of duplicated logic.
To eliminate this, we might try to make default implementations for the
`request_review` and `approve` methods on the `State` trait that return `self`,
but this would violate object safety, since the trait doesnt know what the
concrete `self` will be exactly. We want to be able to use `State` as a trait
object, so we need its methods to be object safe.
The other duplication that would be nice to get rid of is the similar
implementations of the `request_review` and `approve` methods on `Post`. They
both delegate to the implementation of the same method on the value in the
`Option` in the `state` field, and set the new value of the `state` field to
the result. If we had a lot of methods on `Post` that followed this pattern, we
might consider defining a macro to eliminate the repetition (see Appendix E on
macros).
The other duplication is the similar implementations of the `request_review`
and `approve` methods on `Post`. Both methods delegate to the implementation of
the same method on the value in the `state` field of `Option`, and set the new
value of the `state` field to the result. If we had a lot of methods on `Post`
that followed this pattern, we might consider defining a macro to eliminate the
repetition (see Appendix E on macros).
A downside of implementing this object-oriented pattern exactly as its defined
for object-oriented languages is that were not taking advantage of Rusts
strengths as much as we could be. Lets take a look at some changes we can make
to this code that can make invalid states and transitions into compile time
errors.
By implementing this pattern exactly as its defined for object-oriented
languages, were not taking full advantage of Rusts strengths as much as we
could. Lets take a look at some changes we can make to this code that can make
invalid states and transitions into compile time errors.
#### Encoding States and Behavior as Types
Were going to show how to rethink the state pattern a bit in order to get a
different set of tradeoffs. Rather than encapsulating the states and
transitions completely so that outside code has no knowledge of them, were
going to encode the states into different types. When the states are types,
Rusts type checking will make any attempt to use a draft post where we should
only use published posts into a compiler error.
Were going to show how to rethink the state pattern to get a different set of
tradeoffs. Rather than encapsulating the states and transitions completely so
that outside code has no knowledge of them, were going to encode the states
into different types. Like this, Rusts type checking system will make attempts
to use draft posts where only published posts are allowed into a compiler error.
Lets consider the first part of `main` from Listing 17-11:
@@ -508,15 +538,15 @@ fn main() {
}
```
We still want to create a new post in the draft state using `Post::new`, and we
still want to be able to add text to the posts content. But instead of having
a `content` method on a draft post that returns an empty string, were going to
make it so that draft posts dont have the `content` method at all. That way,
if we try to get a draft posts content, well get a compiler error that the
method doesnt exist. This will make it impossible for us to accidentally
display draft post content in production, since that code wont even compile.
Listing 17-19 shows the definition of a `Post` struct, a `DraftPost` struct,
and methods on each:
We still enable the creation of new posts in the draft state using `Post::new`,
and the ability to add text to the posts content. But instead of having a
`content` method on a draft post that returns an empty string, well make it so
that draft posts dont have the `content` method at all. That way, if we try to
get a draft posts content, well get a compiler error telling us the method
doesnt exist. This will make it impossible for us to accidentally display
draft post content in production, since that code wont even compile. Listing
17-19 shows the definition of a `Post` struct, a `DraftPost` struct, and
methods on each:
<span class="filename">Filename: src/lib.rs</span>
@@ -551,23 +581,26 @@ impl DraftPost {
<span class="caption">Listing 17-19: A `Post` with a `content` method and a
`DraftPost` without a `content` method</span>
Both the `Post` and `DraftPost` structs have a private `content` field that stores the
blog post text. The structs no longer have the `state` field since were moving
the encoding of the state to the types of the structs. `Post` will represent a
published post, and it has a `content` method that returns the `content`.
Both the `Post` and `DraftPost` structs have a private `content` field that
stores the blog post text. The structs no longer have the `state` field since
were moving the encoding of the state to the types of the structs. `Post` will
represent a published post, and it has a `content` method that returns the
`content`.
We still have a `Post::new` function, but instead of returning an instance of
`Post`, it returns an instance of `DraftPost`. Its not possible to create an
instance of `Post` right now since `content` is private and there arent any
functions that return `Post`. `DraftPost` has an `add_text` method defined on
it so that we can add text to `content` as before, but note that `DraftPost`
does not have a `content` method defined! So weve enforced that all posts
start as draft posts, and draft posts dont have their content available for
display. Any attempt to get around these constraints will be a compiler error.
`Post`, it returns an instance of `DraftPost`. Because `content` is private,
and there arent any functions that return `Post`, its not possible to create
an instance of `Post` right now.
`DraftPost` has an `add_text` method so we can add text to `content` as before,
but note that `DraftPost` does not have a `content` method defined! So now the
program ensures all posts start as draft posts, and draft posts dont have
their content available for display. Any attempt to get around these
constraints will result in a compiler error.
#### Implementing Transitions as Transformations into Different Types
So how do we get a published post then? The rule we want to enforce is that a
So how do we get a published post then? We want to enforce the rule that a
draft post has to be reviewed and approved before it can be published. A post
in the pending review state should still not display any content. Lets
implement these constraints by adding another struct, `PendingReviewPost`,
@@ -618,21 +651,21 @@ consuming the `DraftPost` and `PendingReviewPost` instances and transforming
them into a `PendingReviewPost` and a published `Post`, respectively. This way,
we wont have any `DraftPost` instances lingering around after weve called
`request_review` on them, and so forth. `PendingReviewPost` doesnt have a
`content` method defined on it, so attempting to read its content is a compiler
error like it is with `DraftPost`. Because the only way to get a published
`content` method defined on it, so attempting to read its content results in a
compiler error, as with `DraftPost`. Because the only way to get a published
`Post` instance that does have a `content` method defined is to call the
`approve` method on a `PendingReviewPost`, and the only way to get a
`PendingReviewPost` is to call the `request_review` method on a `DraftPost`,
weve now encoded the blog post workflow into the type system.
This does mean we have to make some small changes to `main`. Because
`request_review` and `approve` return new instances rather than modifying the
struct theyre called on, we need to add more `let post = ` shadowing
assignments to save the returned instances. We also cant have the assertions
about the draft and pending review posts contents being empty string anymore,
nor do we need them: we cant compile code that tries to use the content of
posts in those states any longer. The updated code in `main` is shown in
Listing 17-21:
This does mean we have to make some small changes to `main`. The
`request_review` and `approve` methods return new instances rather than
modifying the struct theyre called on, so we need to add more `let post = `
shadowing assignments to save the returned instances. We also cant have the
assertions about the draft and pending review posts contents being empty
strings, nor do we need them: we cant compile code that tries to use the
content of posts in those states any longer. The updated code in `main` is
shown in Listing 17-21:
<span class="filename">Filename: src/main.rs</span>
@@ -656,26 +689,27 @@ fn main() {
<span class="caption">Listing 17-21: Modifications to `main` to use the new
implementation of the blog post workflow</span>
Having to change `main` to reassign `post` is what makes this implementation
not quite following the object-oriented state pattern anymore: the
transformations between the states are no longer encapsulated entirely within
the `Post` implementation. However, weve gained the property of having invalid
states be impossible because of the type system and type checking that happens
at compile time! This ensures that certain bugs, such as displaying the content
of an unpublished post, will be discovered before they make it to production.
These changes we need to make to `main` to reassign `post` means this
implementation doesnt quite follow the object-oriented state pattern anymore:
the transformations between the states are no longer encapsulated entirely
within the `Post` implementation. However, our gain is that invalid states are
now impossible because of the type system and the type checking that happens at
compile time! This ensures that certain bugs, such as the content of an
unpublished post being displayed, will be discovered before they make it to
production.
Try the tasks suggested that add additional requirements that we mentioned at
the start of this section to see how working with this version of the code
feels.
Try the tasks suggested for additional requirements that we mentioned at the
start of this section on this code, to see how working with this version of the
code feels.
Even though Rust is capable of implementing object-oriented design patterns,
there are other patterns like encoding state into the type system that are
available in Rust. These patterns have different tradeoffs than the
object-oriented patterns do. While you may be very familiar with
object-oriented patterns, rethinking the problem in order to take advantage of
Rusts features can give benefits like preventing some bugs at compile-time.
Object-oriented patterns wont always be the best solution in Rust, since Rust
has features like ownership that object-oriented languages dont have.
Weve seen that even though Rust is capable of implementing object-oriented
design patterns, other patterns like encoding state into the type system are
also available in Rust. These patterns have different tradeoffs. While you may
be very familiar with object-oriented patterns, rethinking the problem in order
to take advantage of Rusts features can provide benefits like preventing some
bugs at compile-time. Object-oriented patterns wont always be the best
solution in Rust, because of the features like ownership that object-oriented
languages dont have.
## Summary
@@ -684,9 +718,10 @@ reading this chapter, youve now seen that trait objects are a way to get some
object-oriented features in Rust. Dynamic dispatch can give your code some
flexibility in exchange for a bit of runtime performance. This flexibility can
be used to implement object-oriented patterns that can help with the
maintainability of your code. Rust also has different features, like ownership,
than object-oriented languages. An object-oriented pattern wont always be the
best way to take advantage of Rusts strengths.
maintainability of your code. Rust also has other different features, like
ownership, that object-oriented languages dont have. An object-oriented
pattern wont always be the best way to take advantage of Rusts strengths, but
is an available option.
Next, lets look at another feature of Rust that enables lots of flexibility:
patterns. Weve looked at them briefly throughout the book, but havent seen