Skip to content

Commit cf7322d

Browse files
Merge pull request #1266 from rust-lang/ch17-second-page-review
Changes made in 2nd page review of ch17
2 parents 246e7e1 + 07b0ca8 commit cf7322d

6 files changed

Lines changed: 103 additions & 99 deletions

File tree

second-edition/dictionary.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ nocapture
287287
nomicon
288288
nondeterministic
289289
nonequality
290+
nongeneric
290291
NotFound
291292
null's
292293
OCaml
@@ -481,6 +482,7 @@ vers
481482
versa
482483
Versioning
483484
visualstudio
485+
Vlissides
484486
vtable
485487
wasn
486488
WeatherForecast

second-edition/src/SUMMARY.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@
9898
- [Shared State](ch16-03-shared-state.md)
9999
- [Extensible Concurrency: `Sync` and `Send`](ch16-04-extensible-concurrency-sync-and-send.md)
100100

101-
- [Is Rust an Object-Oriented Programming Language?](ch17-00-oop.md)
102-
- [What Does Object-Oriented Mean?](ch17-01-what-is-oo.md)
103-
- [Trait Objects for Using Values of Different Types](ch17-02-trait-objects.md)
104-
- [Object-Oriented Design Pattern Implementations](ch17-03-oo-design-patterns.md)
101+
- [Object Oriented Programming Features of Rust](ch17-00-oop.md)
102+
- [Characteristics of Object-Oriented Languages](ch17-01-what-is-oo.md)
103+
- [Using Trait Objects that Allow for Values of Different Types](ch17-02-trait-objects.md)
104+
- [Implementing an Object-Oriented Design Pattern](ch17-03-oo-design-patterns.md)
105105

106106
## Advanced Topics
107107

second-edition/src/ch17-00-oop.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# Is Rust an Object-Oriented Programming Language?
1+
# Object Oriented Programming Features of Rust
22

33
Object-oriented programming (OOP) is a way of modeling programs. Objects came
44
from Simula in the 1960s. Those objects influenced Alan Kay’s programming
5-
architecture where objects pass messages to each other. He coined the term
6-
object-oriented programming in 1967 to describe this architecture. Many
5+
architecture in which objects pass messages to each other. He coined the term
6+
*object-oriented programming* in 1967 to describe this architecture. Many
77
competing definitions describe what OOP is; some definitions would classify
8-
Rust as object oriented but other definitions would not. In this chapter, we’ll
9-
explore certain characteristics that are commonly considered object oriented
10-
and how those characteristics translate to idiomatic Rust. We’ll then show you
11-
how to implement an object-oriented design pattern in Rust and discuss the
12-
trade-offs of doing so versus implementing a solution using some of Rust’s
8+
Rust as object oriented, but other definitions would not. In this chapter,
9+
we’ll explore certain characteristics that are commonly considered object
10+
oriented and how those characteristics translate to idiomatic Rust. We’ll then
11+
show you how to implement an object-oriented design pattern in Rust and discuss
12+
the trade-offs of doing so versus implementing a solution using some of Rust’s
1313
strengths instead.

second-edition/src/ch17-01-what-is-oo.md

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
## What Does Object Oriented Mean?
1+
## Characteristics of Object-Oriented Languages
22

33
There is no consensus in the programming community about what features a
4-
language needs to be considered object oriented. Rust is influenced by many
5-
different programming paradigms, including OOP; for example, we explored the
6-
features that came from functional programming in Chapter 13. Arguably, OOP
7-
languages share certain common characteristics, namely objects, encapsulation,
8-
and inheritance. Let’s look at what each of those characteristics mean and
9-
whether Rust supports them.
4+
language must have to be considered object oriented. Rust is influenced by many
5+
programming paradigms, including OOP; for example, we explored the features
6+
that came from functional programming in Chapter 13. Arguably, OOP languages
7+
share certain common characteristics, namely objects, encapsulation, and
8+
inheritance. Let’s look at what each of those characteristics means and whether
9+
Rust supports it.
1010

1111
### Objects Contain Data and Behavior
1212

13-
The book *Design Patterns: Elements of Reusable Object-Oriented Software*,
14-
colloquially referred to as *The Gang of Four book*, is a catalog of
15-
object-oriented design patterns. It defines OOP this way:
13+
The book *Design Patterns: Elements of Reusable Object-Oriented Software* by
14+
Enoch Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley
15+
Professional, 1994) colloquially referred to as *The Gang of Four* book, is a
16+
catalog of object-oriented design patterns. It defines OOP this way:
1617

1718
> Object-oriented programs are made up of objects. An *object* packages both
1819
> data and the procedures that operate on that data. The procedures are
@@ -39,7 +40,7 @@ should be public, and by default everything else is private. For example, we
3940
can define a struct `AveragedCollection` that has a field containing a vector
4041
of `i32` values. The struct can also have a field that contains the average of
4142
the values in the vector, meaning the average doesn’t have to be computed
42-
on-demand whenever anyone needs it. In other words, `AveragedCollection` will
43+
on demand whenever anyone needs it. In other words, `AveragedCollection` will
4344
cache the calculated average for us. Listing 17-1 has the definition of the
4445
`AveragedCollection` struct:
4546

@@ -112,14 +113,15 @@ the `average` field might become out of sync when the `list` changes. The
112113
`average` method returns the value in the `average` field, allowing external
113114
code to read the `average` but not modify it.
114115

115-
Because we’ve encapsulated the implementation details of `AveragedCollection`,
116-
we can easily change aspects, such as the data structure, in the future. For
117-
instance, we could use a `HashSet` instead of a `Vec` for the `list` field. As
118-
long as the signatures of the `add`, `remove`, and `average` public methods
119-
stay the same, code using `AveragedCollection` wouldn’t need to change. If we
120-
made `list` public instead, this wouldn’t necessarily be the case: `HashSet`
121-
and `Vec` have different methods for adding and removing items, so the external
122-
code would likely have to change if it was modifying `list` directly.
116+
Because we’ve encapsulated the implementation details of the struct
117+
`AveragedCollection`, we can easily change aspects, such as the data structure,
118+
in the future. For instance, we could use a `HashSet` instead of a `Vec` for
119+
the `list` field. As long as the signatures of the `add`, `remove`, and
120+
`average` public methods stay the same, code using `AveragedCollection`
121+
wouldn’t need to change. If we made `list` public instead, this wouldn’t
122+
necessarily be the case: `HashSet` and `Vec` have different methods for adding
123+
and removing items, so the external code would likely have to change if it were
124+
modifying `list` directly.
123125

124126
If encapsulation is a required aspect for a language to be considered object
125127
oriented, then Rust meets that requirement. The option to use `pub` or not for
@@ -132,9 +134,9 @@ object’s definition, thus gaining the parent object’s data and behavior with
132134
you having to define them again.
133135

134136
If a language must have inheritance to be an object-oriented language, then
135-
Rust is not. There is no way to define a struct that inherits the parent
137+
Rust is not one. There is no way to define a struct that inherits the parent
136138
struct’s fields and method implementations. However, if you’re used to having
137-
inheritance in your programming toolbox, you can use other solutions in Rust
139+
inheritance in your programming toolbox, you can use other solutions in Rust,
138140
depending on your reason for reaching for inheritance in the first place.
139141

140142
You choose inheritance for two main reasons. One is for reuse of code: you can
@@ -167,12 +169,13 @@ each other at runtime if they share certain characteristics.
167169
168170
Inheritance has recently fallen out of favor as a programming design solution
169171
in many programming languages because it’s often at risk of sharing more code
170-
than needs be. Subclasses shouldn’t always share all characteristics of their
172+
than necessary. Subclasses shouldn’t always share all characteristics of their
171173
parent class but will do so with inheritance. This can make a program’s design
172-
less flexible and introduces the possibility of calling methods on subclasses
173-
that don’t make sense or that cause errors because the methods don’t apply to
174-
the subclass. Some languages will also only allow a subclass to inherit from
175-
one class, further restricting the flexibility of a program’s design.
174+
less flexible. It also introduces the possibility of calling methods on
175+
subclasses that don’t make sense or that cause errors because the methods don’t
176+
apply to the subclass. In addition, some languages will only allow a subclass
177+
to inherit from one class, further restricting the flexibility of a program’s
178+
design.
176179

177180
For these reasons, Rust takes a different approach, using trait objects instead
178181
of inheritance. Let’s look at how trait objects enable polymorphism in Rust.

second-edition/src/ch17-02-trait-objects.md

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
## Using Trait Objects that Allow for Values of Different Types
22

3-
In Chapter 8, we mentioned that one limitation of vectors is that they can only
4-
store elements of one type. We created a workaround in Listing 8-10 where we
5-
defined a `SpreadsheetCell` enum that had variants to hold integers, floats,
3+
In Chapter 8, we mentioned that one limitation of vectors is that they can
4+
store elements of only one type. We created a workaround in Listing 8-10 where
5+
we defined a `SpreadsheetCell` enum that had variants to hold integers, floats,
66
and text. This meant we could store different types of data in each cell and
77
still have a vector that represented a row of cells. This is a perfectly good
88
solution when our interchangeable items are a fixed set of types that we know
@@ -43,17 +43,17 @@ To implement the behavior we want `gui` to have, we’ll define a trait named
4343
takes a *trait object*. A trait object points to an instance of a type that
4444
implements the trait we specify. We create a trait object by specifying some
4545
sort of pointer, such as a `&` reference or a `Box<T>` smart pointer, and then
46-
specifying the relevant trait (we’ll talk about the reason trait objects must
47-
use a pointer in Chapter 19 in the section “Dynamically Sized Types & Sized”).
46+
specifying the relevant trait. (We’ll talk about the reason trait objects must
47+
use a pointer in Chapter 19 in the section “Dynamically Sized Types & Sized”.)
4848
We can use trait objects in place of a generic or concrete type. Wherever we
4949
use a trait object, Rust’s type system will ensure at compile time that any
5050
value used in that context will implement the trait object’s trait.
5151
Consequently, we don’t need to know all the possible types at compile time.
5252

53-
We’ve mentioned that in Rust we refrain from calling structs and enums
53+
We’ve mentioned that in Rust, we refrain from calling structs and enums
5454
“objects” to distinguish them from other languages’ objects. In a struct or
5555
enum, the data in the struct fields and the behavior in `impl` blocks are
56-
separated, whereas in other languages the data and behavior combined into one
56+
separated, whereas in other languages, the data and behavior combined into one
5757
concept is often labeled an object. However, trait objects *are* more like
5858
objects in other languages in the sense that they combine data and behavior.
5959
But trait objects differ from traditional objects in that we can’t add data to
@@ -77,7 +77,7 @@ pub trait Draw {
7777
This syntax should look familiar from our discussions on how to define traits
7878
in Chapter 10. Next comes some new syntax: Listing 17-4 defines a struct named
7979
`Screen` that holds a vector named `components`. This vector is of type
80-
`Box<Draw>`, which is a trait object: it’s a stand-in for any type inside a
80+
`Box<Draw>`, which is a trait object; it’s a stand-in for any type inside a
8181
`Box` that implements the `Draw` trait.
8282

8383
<span class="filename">Filename: src/lib.rs</span>
@@ -119,8 +119,8 @@ impl Screen {
119119
}
120120
```
121121

122-
<span class="caption">Listing 17-5: Implementing a `run` method on `Screen`
123-
that calls the `draw` method on each component</span>
122+
<span class="caption">Listing 17-5: A `run` method on `Screen` that calls the
123+
`draw` method on each component</span>
124124

125125
This works differently than defining a struct that uses a generic type
126126
parameter with trait bounds. A generic type parameter can only be substituted
@@ -186,7 +186,7 @@ pub struct Button {
186186

187187
impl Draw for Button {
188188
fn draw(&self) {
189-
// Code to actually draw a button
189+
// code to actually draw a button
190190
}
191191
}
192192
```
@@ -198,9 +198,9 @@ The `width`, `height`, and `label` fields on `Button` will differ from the
198198
fields on other components, such as a `TextField` type, that might have those
199199
fields plus a `placeholder` field instead. Each of the types we want to draw on
200200
the screen will implement the `Draw` trait but will use different code in the
201-
`draw` method to define how to draw that particular type, like `Button` has
202-
here (without the actual GUI code that is beyond the scope of this chapter).
203-
`Button`, for instance, might have an additional `impl` block containing
201+
`draw` method to define how to draw that particular type, as `Button` has here
202+
(without the actual GUI code, which is beyond the scope of this chapter). The
203+
`Button` type, for instance, might have an additional `impl` block containing
204204
methods related to what happens when a user clicks the button. These kinds of
205205
methods won’t apply to types like `TextField`.
206206

@@ -222,7 +222,7 @@ struct SelectBox {
222222
223223
impl Draw for SelectBox {
224224
fn draw(&self) {
225-
// Code to actually draw a select box
225+
// code to actually draw a select box
226226
}
227227
}
228228
```
@@ -284,10 +284,10 @@ It doesn’t check whether a component is an instance of a `Button` or a
284284
`Screen` to need values that we can call the `draw` method on.
285285

286286
The advantage of using trait objects and Rust’s type system to write code
287-
similar to code using duck typing is that we never have to check that a value
288-
implements a particular method at runtime or worry about getting errors if a
289-
value doesn’t implement a method, but we call it anyway. Rust won’t compile our
290-
code if the values don’t implement the traits that the trait objects need.
287+
similar to code using duck typing is that we never have to check whether a
288+
value implements a particular method at runtime or worry about getting errors
289+
if a value doesn’t implement a method but we call it anyway. Rust won’t compile
290+
our code if the values don’t implement the traits that the trait objects need.
291291

292292
For example, Listing 17-10 shows what happens if we try to create a `Screen`
293293
with a `String` as a component:
@@ -326,36 +326,36 @@ error[E0277]: the trait bound `std::string::String: gui::Draw` is not satisfied
326326
```
327327

328328
This error lets us know that either we’re passing something to `Screen` we
329-
didn’t mean to pass and we should pass a different type, or we should implement
329+
didn’t mean to pass and we should pass a different type or we should implement
330330
`Draw` on `String` so that `Screen` is able to call `draw` on it.
331331

332332
### Trait Objects Perform Dynamic Dispatch
333333

334334
Recall in the “Performance of Code Using Generics” section in Chapter 10 our
335335
discussion on the monomorphization process performed by the compiler when we
336-
use trait bounds on generics: the compiler generates non-generic
337-
implementations of functions and methods for each concrete type that we use in
338-
place of a generic type parameter. The code that results from monomorphization
339-
is doing *static dispatch*, which is when the compiler knows what method you’re
340-
calling at compile time. This is opposed to *dynamic dispatch*, which is when
341-
the compiler can’t tell at compile time which method you’re calling. In dynamic
336+
use trait bounds on generics: the compiler generates nongeneric implementations
337+
of functions and methods for each concrete type that we use in place of a
338+
generic type parameter. The code that results from monomorphization is doing
339+
*static dispatch*, which is when the compiler knows what method you’re calling
340+
at compile time. This is opposed to *dynamic dispatch*, which is when the
341+
compiler can’t tell at compile time which method you’re calling. In dynamic
342342
dispatch cases, the compiler emits code that at runtime will figure out which
343343
method to call.
344344

345345
When we use trait objects, Rust must use dynamic dispatch. The compiler doesn’t
346346
know all the types that might be used with the code that is using trait
347347
objects, so it doesn’t know which method implemented on which type to call.
348348
Instead, at runtime, Rust uses the pointers inside the trait object to know
349-
which specific method to call. There is a runtime cost when this lookup happens
350-
that doesn’t occur with static dispatch. Dynamic dispatch also prevents the
351-
compiler from choosing to inline a method’s code, which in turn prevents some
349+
which method to call. There is a runtime cost when this lookup happens that
350+
doesn’t occur with static dispatch. Dynamic dispatch also prevents the compiler
351+
from choosing to inline a method’s code, which in turn prevents some
352352
optimizations. However, we did get extra flexibility in the code that we wrote
353353
in Listing 17-5 and were able to support in Listing 17-9, so it’s a trade-off
354354
to consider.
355355

356356
### Object Safety Is Required for Trait Objects
357357

358-
You can only make *object safe* traits into trait objects. Some complex rules
358+
You can only make *object-safe* traits into trait objects. Some complex rules
359359
govern all the properties that make a trait object safe, but in practice, only
360360
two rules are relevant. A trait is object safe if all the methods defined in
361361
the trait have the following properties:
@@ -391,7 +391,7 @@ of `Vec`. The signature of `clone` needs to know what type will stand in for
391391
`Self`, because that’s the return type.
392392

393393
The compiler will indicate when you’re trying to do something that violates the
394-
rules of object safety in regards to trait objects. For example, let’s say we
394+
rules of object safety in regard to trait objects. For example, let’s say we
395395
tried to implement the `Screen` struct in Listing 17-4 to hold types that
396396
implement the `Clone` trait instead of the `Draw` trait, like this:
397397

0 commit comments

Comments
 (0)