Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0d32bbe
Chapter 15: Smart pointers
steveklabnik Jan 17, 2017
4ad2690
more work on smart pointers
steveklabnik Feb 14, 2017
ac61cbd
finish off draft
steveklabnik Feb 22, 2017
01e407a
First editing pass
carols10cents Feb 23, 2017
092ff98
Address most comments, make more notes
carols10cents Feb 23, 2017
54e807c
A more thorough editing pass
carols10cents Feb 23, 2017
12e12c8
More edits
carols10cents Feb 25, 2017
b07ce30
All but the summary
carols10cents Feb 26, 2017
3af06ba
Whew. Finish editing.
carols10cents Feb 27, 2017
5af5c49
Split into sections
carols10cents Feb 27, 2017
8a39ec9
Connect Drop a bit more to Rc
carols10cents Feb 27, 2017
d2e6325
Multithreaded, one word, is in No Starch's style guide
carols10cents Feb 27, 2017
8d784f7
Finish a sentence
carols10cents Feb 27, 2017
334c10e
Wrap
carols10cents Feb 27, 2017
a251d43
Take awesome suggestions from @matthewjasper
carols10cents Feb 27, 2017
3595078
Start (but not complete) swapping out the weak Weak<T> ex
carols10cents Feb 28, 2017
61d552e
Clarify why we can't call Drop::drop
carols10cents Feb 28, 2017
77f697b
Make section titles consistent and easier to scan
carols10cents Mar 1, 2017
13b10e7
Rejigger intro
carols10cents Mar 1, 2017
aa03fa1
More rejiggering because of @ScottAbbey's excellent comments <3
carols10cents Mar 1, 2017
6672fd6
Another attempt at the Weak<T> example
carols10cents Mar 1, 2017
bf02924
Set up example about borrowing rules on refcell better
carols10cents Mar 1, 2017
b460d00
Remove unnecessary sentence
carols10cents Mar 1, 2017
5c6c1ab
Fix capitalization
carols10cents Mar 1, 2017
e4658ca
Remove File since it doesn't implement Deref
carols10cents Mar 1, 2017
c981fc0
Remove sentence about box being on the stack; not quite true
carols10cents Mar 1, 2017
84255f9
Address most of aturon's comments
carols10cents Mar 1, 2017
52d109f
Try to make examples a bit clearer
carols10cents Mar 1, 2017
90c380c
Try to clarify that RefCell isn't really a smart pointer
carols10cents Mar 2, 2017
72b4ceb
More RefCell clarification
carols10cents Mar 2, 2017
d7ade00
Clarify another instance of immutable data
carols10cents Mar 2, 2017
6646733
Use a sillier example than the non-functioning WebSocket
carols10cents Mar 2, 2017
90ba6d3
Incorporate the Weak example
carols10cents Mar 2, 2017
542013a
See if we can get away with not editing chapter 4
carols10cents Mar 2, 2017
b13b292
Fixing spelling and tests
carols10cents Mar 2, 2017
0e27654
Infinite type illustration
carols10cents Mar 2, 2017
a17e7ca
Add a diagram for Cons with Box
carols10cents Mar 2, 2017
07310e5
Add more figures
carols10cents Mar 2, 2017
87609b1
graphviz is terrible
carols10cents Mar 3, 2017
5f6113d
wrong way arrow
carols10cents Mar 3, 2017
3bfc407
halp can't get arrow pointing the way i want
carols10cents Mar 3, 2017
364ff83
more diagrams
carols10cents Mar 3, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions src/ch15-00-smart-pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ though we didn't call them that by name at the time. For example, in a certain
sense, `String` and `Vec<T>` from Chapter 8 are both smart pointers. They own
some memory and allow you to manipulate it, and have metadata (like their
capacity) and extra capabilities or guarantees (`String` data will always be
valid UTF-8). Another good example is `File`, which we used for our I/O project
in Chapter 12: it owns and manages a file handle that the operating system
gives us, and allows us to access the data in the file. The characteristics
that distinguish a smart pointer from an ordinary struct are that smart
pointers implement the `Deref` and `Drop` traits, and in this chapter we'll be
discussing both of those traits and why they're important to smart pointers.
valid UTF-8). The characteristics that distinguish a smart pointer from an
ordinary struct are that smart pointers implement the `Deref` and `Drop`
traits, and in this chapter we'll be discussing both of those traits and why
they're important to smart pointers.

Given that the smart pointer pattern is a general design pattern used
frequently in Rust, this chapter won't cover every smart pointer that exists.
Expand Down
90 changes: 50 additions & 40 deletions src/ch15-01-box.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
## `Box<T>` Points to Data on the Heap and Has a Known Size

The most straightforward smart pointer is a *box*, whose type is written
`Box<T>`. Boxes allow you to put a single value on the heap, and the pointer to
that value lives on the stack. (We talked about the stack vs. the heap in
Chapter 4.) Listing 15-1 shows how to use a box to store an `i32` on the heap:
`Box<T>`. Boxes allow you to put a single value on the heap (we talked about
the stack vs. the heap in Chapter 4). Listing 15-1 shows how to use a box to
store an `i32` on the heap:

<figure>
<span class="filename">Filename: src/main.rs</span>
Expand Down Expand Up @@ -38,25 +38,36 @@ A cons list is a list where each item contains a value and the next item until
the end of the list, which is signified by a value called `Nil`. Note that we
aren't introducing the idea of "nil" or "null" that we discussed in Chapter 6,
this is just a regular enum variant name we're using because it's the canonical
name to use when describing the cons list data structure.
name to use when describing the cons list data structure. Cons lists aren't
used very often in Rust, `Vec<T>` is a better choice most of the time, but
implementing this data structure is useful as an example.

Here's our first try at defining a cons list as an enum; note that this won't
compile quite yet:

<figure>
<span class="filename">Filename: src/main.rs</span>

```rust,ignore
enum List<T> {
Cons(T, List<T>),
enum List {
Cons(i32, List),
Nil,
}
```

<figcaption>

Listing 15-2: An enum definition for a cons list data structure
Listing 15-2: The first attempt of defining an enum to represent a cons list
data structure of `i32` values

</figcaption>
</figure>

We're choosing to implement a cons list that only holds `i32` values, but we
could have chosen to implement it using generics as we discussed in Chapter 10
to define a cons list concept independent of the type of value stored in the
cons list.

Using a cons list to store the list `1, 2, 3` would look like this:

```rust,ignore
Expand All @@ -67,23 +78,22 @@ fn main() {
}
```

The first `Cons` value holds `1` and another `List<T>` value. This `List<T>`
value is another `Cons` value that holds `2` and another `List<T>` value. This
is one more `Cons` value that holds `3` and a `List<T>` value, which is finally
The first `Cons` value holds `1` and another `List` value. This `List`
value is another `Cons` value that holds `2` and another `List` value. This
is one more `Cons` value that holds `3` and a `List` value, which is finally
`Nil`, the non-recursive variant that signals the end of the list.

However, if we try to compile the above code, we get the error shown in Listing
15-3:
If we try to compile the above code, we get the error shown in Listing 15-3:

<figure>

```text
error[E0072]: recursive type `List` has infinite size
-->
|
1 | enum List<T> {
1 | enum List {
| _^ starting here...
2 | | Cons(T, List<T>),
2 | | Cons(i32, List),
3 | | Nil,
4 | | }
| |_^ ...ending here: recursive type has infinite size
Expand All @@ -100,10 +110,10 @@ Listing 15-3: The error we get when attempting to define a recursive enum
</figure>

The error says this type 'has infinite size'. Why is that? It's because we've
defined `List<T>` to have a variant that is recursive: it holds another value
of itself. This means Rust can't figure out how much space it needs in order to
store a `List<T>` value. Let's break this down a bit: first let's look at how
Rust decides how much space it needs to store a value of a non-recursive type.
defined `List` to have a variant that is recursive: it holds another value of
itself. This means Rust can't figure out how much space it needs in order to
store a `List` value. Let's break this down a bit: first let's look at how Rust
decides how much space it needs to store a value of a non-recursive type.
Recall the `Message` enum we defined in Listing 6-2 when we discussed enum
definitions in Chapter 6:

Expand All @@ -123,15 +133,15 @@ forth. Therefore, the most space a `Message` value will need is the space it
would take to store the largest of its variants.

Contrast this to what happens when the Rust compiler looks at a recursive type
like `List<T>` in Listing 15-2. The compiler tries to figure out how much
memory is needed to store value of `List<T>`, and starts by looking at the
`Cons` variant. The `Cons` variant holds a value of type `T` and a value of
type `List<T>`, so it can use however much the size of `T` is plus the size of
`List<T>`. To figure out how much memory a `List<T>` needs, it looks at its
variants, starting with the `Cons` variant. The `Cons` variant holds a value of
type `T` and a value of type `List<T>`, and this continues infinitely. Rust
can't figure out how much space to allocate for recursively defined types, so
the compiler gives the error in Listing 15-3.
like `List` in Listing 15-2. The compiler tries to figure out how much memory
is needed to store value of `List`, and starts by looking at the `Cons`
variant. The `Cons` variant holds a value of type `i32` and a value of type
`List`, so `Cons` needs an amount of space equal to the size of an `i32` plus
the size of a `List`. To figure out how much memory a `List` needs, it looks at
its variants, starting with the `Cons` variant. The `Cons` variant holds a
value of type `i32` and a value of type `List`, and this continues infinitely.
Rust can't figure out how much space to allocate for recursively defined types,
so the compiler gives the error in Listing 15-3.

The compiler did give a helpful suggestion in the error output:

Expand All @@ -152,8 +162,8 @@ like so:
<span class="filename">Filename: src/main.rs</span>

```rust
enum List<T> {
Cons(T, Box<List<T>>),
enum List {
Cons(i32, Box<List>),
Nil,
}

Expand All @@ -169,22 +179,22 @@ fn main() {

<figcaption>

Listing 15-4: Definition of `List<T>` that uses `Box<T>` in order to have a
Listing 15-4: Definition of `List` that uses `Box<T>` in order to have a
known size

</figcaption>
</figure>

The compiler will be able to figure out the size it needs to store a `List<T>`
value. Rust will look at `List<T>`, and again start by looking at the `Cons`
variant. The `Cons` variant will need the size of whatever `T` is, plus the
space to store a `usize`, since a box always has the size of a `usize`, no
matter what it's pointing to. Then Rust looks at the `Nil` variant, which does
not store a value, so `Nil` doesn't need any space. We've broken the infinite,
recursive chain by adding in a box. This is the main area where boxes are
useful: breaking up an infinite data structure so that the compiler can know
what size it is. We'll look at another case where Rust has data of unknown size
in Chapter 17 when we discuss trait objects.
The compiler will be able to figure out the size it needs to store a `List`
value. Rust will look at `List`, and again start by looking at the `Cons`
variant. The `Cons` variant will need the size of `i32` plus the space to store
a `usize`, since a box always has the size of a `usize`, no matter what it's
pointing to. Then Rust looks at the `Nil` variant, which does not store a
value, so `Nil` doesn't need any space. We've broken the infinite, recursive
chain by adding in a box. This is the main area where boxes are useful:
breaking up an infinite data structure so that the compiler can know what size
it is. We'll look at another case where Rust has data of unknown size in
Chapter 17 when we discuss trait objects.

Even though you won't be using boxes very often, they are a good way to
understand the smart pointer pattern. Two of the aspects of `Box<T>` that are
Expand Down
19 changes: 11 additions & 8 deletions src/ch15-03-drop.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

The other trait that's important to the smart pointer pattern is the `Drop`
trait. `Drop` lets us run some code when a value is about to go out of scope.
This is especially useful for smart pointers that manage a resource as opposed
to those that manage memory: often resources like files or network connections
need to be closed when our code is done with them. In other languages, we have
to remember to call code to close these kinds of resources every time we finish
using an instance of one. If we forget, the system our code is running on might
get overloaded and crash.

In Rust, we can specify that some code should be run when a value goes out of
The `Drop` trait is more generally useful than just smart pointers. For
example, the `Drop` trait is often used on structs that manage a resource:
often resources like files or network connections need to be closed when our
code is done with them. We're discussing `Drop` in the context of smart
pointers, though, because the functionality of the `Drop` trait is almost
always used when implementing smart pointers.

In some other languages, we have to remember to call code to free the memory or
resource every time we finish using an instance of a smart pointer. If we
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 'smart pointer' the right way refer to things in other languages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is in C++, and other similar kinds of languages, yeah

forget, the system our code is running on might get overloaded and crash. In
Rust, we can specify that some code should be run when a value goes out of
scope. The compiler will insert this code automatically. That means we don't
need to remember to put this code everywhere we're done with an instance of
these types, but we still won't leak resources!
Expand Down
34 changes: 17 additions & 17 deletions src/ch15-04-rc.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,29 @@ you'll get a compile-time error.
### Using `Rc<T>` to Share Data

Let's return to our cons list example from Listing 15-4. In Listing 15-8, we're
going to try to use `List<T>` as we defined it using `Box<T>`. First we'll
create one list instance that contains 5 and then 10. Next, we want to create
two more lists: one that starts with 3 and continues on to our first list
containing 5 and 10, then another list that starts with 4 and *also* continues
on to our first list containing 5 and 10. In other words, we want two lists
that both share ownership of the third list, which conceptually will be
something like this:
going to try to use `List` as we defined it using `Box<T>`. First we'll create
one list instance that contains 5 and then 10. Next, we want to create two more
lists: one that starts with 3 and continues on to our first list containing 5
and 10, then another list that starts with 4 and *also* continues on to our
first list containing 5 and 10. In other words, we want two lists that both
share ownership of the third list, which conceptually will be something like
this:

```text
b -> 3 ---v
a ------> 5 -> 10 -> Nil
c -> 4 ---^
```

Trying to implement this using our definition of `List<T>` with `Box<T>` won't
Trying to implement this using our definition of `List` with `Box<T>` won't
work:

<figure>
<span class="filename">Filename: src/main.rs</span>

```rust,ignore
enum List<T> {
Cons(T, Box<List<T>>),
enum List {
Cons(i32, Box<List>),
Nil,
}

Expand Down Expand Up @@ -86,7 +86,7 @@ error[E0382]: use of moved value: `a`
13 | let c = Cons(4, Box::new(a));
| ^ value used here after move
|
= note: move occurs because `a` has type `List<i32>`, which does not
= note: move occurs because `a` has type `List`, which does not
implement the `Copy` trait
```

Expand All @@ -99,15 +99,15 @@ we'd have to specify lifetime parameters and we'd have to construct elements of
a list such that every element lives at least as long as the list itself.
Otherwise, the borrow checker won't even let us compile the code.

Instead, we can change our definition of `List<T>` to use `Rc<T>` instead of
Instead, we can change our definition of `List` to use `Rc<T>` instead of
`Box<T>` as shown here in Listing 15-9:

<figure>
<span class="filename">Filename: src/main.rs</span>

```rust
enum List<T> {
Cons(T, Rc<List<T>>),
enum List {
Cons(i32, Rc<List>),
Nil,
}

Expand All @@ -123,7 +123,7 @@ fn main() {

<figcaption>

Listing 15-9: A definition of `List<T>` that uses `Rc<T>`
Listing 15-9: A definition of `List` that uses `Rc<T>`

</figcaption>
</figure>
Expand All @@ -148,8 +148,8 @@ reference cycles.
<span class="filename">Filename: src/main.rs</span>

```rust
# enum List<T> {
# Cons(T, Rc<List<T>>),
# enum List {
# Cons(i32, Rc<List>),
# Nil,
# }
#
Expand Down
Loading