Skip to content

Commit

Permalink
Try to make examples a bit clearer
Browse files Browse the repository at this point in the history
  • Loading branch information
carols10cents committed Mar 1, 2017
1 parent 84255f9 commit 52d109f
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 103 deletions.
84 changes: 47 additions & 37 deletions src/ch15-01-box.md
Original file line number Diff line number Diff line change
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
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
44 changes: 23 additions & 21 deletions src/ch15-05-interior-mutability.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ error[E0499]: cannot borrow `s` as mutable more than once at a time
```

In contrast, using `RefCell<T>` and calling `borrow_mut` twice in the same
scope *will* compile, but it will panic at runtime instead. This code:
scope *will* compile, but it'll panic at runtime instead. This code:

```rust,should_panic
use std::cell::RefCell;
Expand Down Expand Up @@ -180,16 +180,20 @@ Well, remember when we said that `Rc<T>` has to store immutable data? Given
that `RefCell<T>` is immutable, but has interior mutability, we can combine
`Rc<T>` and `RefCell<T>` to get a type that's both reference counted and
mutable. Listing 15-12 shows an example of how to do that, again going back to
our cons list from Listing 15-9. In this example, the type we're filling in for
`T` is `Rc<RefCell<i32>>`:
our cons list from Listing 15-9. In this example, instead of storing `i32`
values in the cons list, we'll be storing `Rc<RefCell<i32>>` values. We want to
store that type so that we can have an owner of the value that's not part of
the list (the multiple owners functionality that `Rc<T>` provides), and so we
can mutate the inner `i32` value (the interior mutability functionality that
`RefCell<T>` provides):

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

```rust
#[derive(Debug)]
enum List<T> {
Cons(T, Rc<List<T>>),
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}

Expand All @@ -216,18 +220,18 @@ fn main() {

<figcaption>

Listing 15-12: Using `Rc<RefCell<T>>` to create a `List<T>` that we can mutate
Listing 15-12: Using `Rc<RefCell<i32>>` to create a `List` that we can mutate

</figcaption>
</figure>

We're creating a value, which is an instance of `T`. We're storing it in a
variable named `value` because we want to be able to access it directly later.
Then we create a `List<T>` in `a` that has a `Cons` variant that holds `value`,
and `value` needs to be cloned since we want `value` to also have ownership in
addition to `a`. Then we wrap `a` in an `Rc<T>` so that we can create lists `b`
and `c` that start differently but both refer to `a`, similarly to what we did
in Listing 15-9.
We're creating a value, which is an instance of `Rc<RefCell<i32>>. We're
storing it in a variable named `value` because we want to be able to access it
directly later. Then we create a `List` in `a` that has a `Cons` variant that
holds `value`, and `value` needs to be cloned since we want `value` to also
have ownership in addition to `a`. Then we wrap `a` in an `Rc<T>` so that we
can create lists `b` and `c` that start differently but both refer to `a`,
similarly to what we did in Listing 15-9.

Once we have the lists in `shared_list`, `b`, and `c` created, then we add 10
to the 5 in `value` by dereferencing the `Rc<T>` and calling `borrow_mut` on
Expand All @@ -242,14 +246,12 @@ b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
```

This is pretty neat! We didn't have to modify our definition of `List<T>` at
all, since the generic parameter lets us substitute any immutable type. By
using `RefCell<T>`, we satisfy the immutable type requirement since
`RefCell<T>` is outwardly immutable, but we can use the methods on `RefCell<T>`
that provide access to its interior mutability to be able to modify our data
when we need to. The runtime checks of the borrowing rules that `RefCell<T>`
does protect us from data races, and we've decided that we want to trade a bit
of speed for the flexibility in our data structures.
This is pretty neat! By using `RefCell<T>`, we can have an outwardly immutable
`List`, but we can use the methods on `RefCell<T>` that provide access to its
interior mutability to be able to modify our data when we need to. The runtime
checks of the borrowing rules that `RefCell<T>` does protect us from data
races, and we've decided that we want to trade a bit of speed for the
flexibility in our data structures.

`RefCell<T>` is not the only standard library type that provides interior
mutability. `Cell<T>` is similar but instead of giving references to the inner
Expand Down
Loading

0 comments on commit 52d109f

Please sign in to comment.