Skip to content

Chapter 15: Smart pointers #407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 42 commits into from
Mar 3, 2017
Merged

Chapter 15: Smart pointers #407

merged 42 commits into from
Mar 3, 2017

Conversation

steveklabnik
Copy link
Member

This isn't done yet, but it's a start.

Copy link

@parir parir left a comment

Choose a reason for hiding this comment

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

I just checked for typos.

That is, they have extra capabilities that references don't, hence the "smart"
nickname.

You've already encounted a few smart pointers in the book, we didn't call them
Copy link

Choose a reason for hiding this comment

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

s/encounted/encountered

nickname.

You've already encounted a few smart pointers in the book, we didn't call them
that by name, though. For example, in a certian sense, `String` and `Vec<T>`
Copy link

Choose a reason for hiding this comment

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

s/certian/certain

Most of this should look familliar: a struct, a trait, a main function. There
is one tricky bit: like we said in chapter 13 on Iterators, the `type Target =
T;` syntax is "associated types", which is covered in Chapter 20. Don't worry
about it too much, it is a slightly different way of delcaring a generic
Copy link

Choose a reason for hiding this comment

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

s/delcaring/declaring

The first two are the same, except for mutability: if you have a `&T`, and
`T` implements `Deref` to some type `U`, you can get a `&U` transparently. Same
for mutable references. The last one is more tricky: if you have a mutable
reference, it will also coerece to an immutable one. The other case is _not_
Copy link

Choose a reason for hiding this comment

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

s/coerece/coerce

`T` implements `Deref` to some type `U`, you can get a `&U` transparently. Same
for mutable references. The last one is more tricky: if you have a mutable
reference, it will also coerece to an immutable one. The other case is _not_
possible though: immutable references will never coerece to mutable ones.
Copy link

Choose a reason for hiding this comment

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

s/coerece/coerce

```rust,ignore
enum List<T> {
Cons(T, List<T>),
Nil,
Copy link
Member

Choose a reason for hiding this comment

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

Ok soooo I know it's usually called Nil in lisp, but wdyt about calling this variant Empty instead, to avoid confusion with "i thought Rust didn't have nil?!?!" Just a thought, I don't feel super strongly about this.

type `SomeEnum<T>`, so we need to figure out how much memory a `SomeEnum<T>`
needs. Let's look at..." into infinity. In order to figure out how much memory
`SomeEnum::A` needs, we need to figure out how much memory `SomeEnum::A` needs.
It's impossible to know!
Copy link
Member

Choose a reason for hiding this comment

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

omg i got the point after like 2 iterations of this :P

make `main::List` representable
```

Because a `Box<T>` is a pointer, we always know what size it is: a `usize`.
Copy link
Member

Choose a reason for hiding this comment

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

We've talked about usize being the type of an index, but I don't see that we've explicitly said "usize is how big pointers are"-- do you think this would be a good spot to say that? if so, I'm happy to add something in my pass through :)

```

We use `*y` to access the thing that `y` refers to, rather than `y` itself.
Here's an example of overloading `*` using `Deref`:
Copy link
Member

Choose a reason for hiding this comment

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

Why would we want to overload Deref like this? When shouldn't we overload Deref?

### `Deref` coercions.

There's one other trick with `Deref`: it's one place where Rust will do
automatic coercions. Consider this code:
Copy link
Member

Choose a reason for hiding this comment

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

Hm I wouldn't call Deref the "place" where Rust will do automatic coercion-- the place is function arguments, as opposed to uhh what are the places that people expect automatic deref and it doesn't happen? I forget.

I would reword this as "There's one other trick with Deref and function calls: Rust will do automatic coercion using Deref on arguments passed to functions" or similar, wdyt?

Copy link
Member

Choose a reason for hiding this comment

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

check the RFC on this


The ability to run some code when something goes out of scope is very powerful.
For example, here's something that looks similar to a box. This code won't
_work_, but it illustrates the concept:
Copy link
Member

Choose a reason for hiding this comment

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

I would reword "This code won't work" as "here's some pseudocode that assumes we have functions allocate_memory and free_memory that implement a custom memory allocator" (is that the only reason it won't work?)

So first, we declare `MyBox<T>` to have a reference to some data. We can't use
actual `&T` references for this, we'd use `*const T`, but we won't talk about
*raw pointer*s until Chapter 20. So for now, pretend that this works; it's the
same idea.
Copy link
Member

Choose a reason for hiding this comment

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

hnnngg i see, ok. This seems like a lot to handwave over :-/ What if we show a socket that we call code to close on Drop instead?

Choose a reason for hiding this comment

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

Agreed that an example that a) doesn't work, b) looks forward to chapter in the future is maybe a bit much. Something that's a little more real-world and builds on what they know (socket sounds good, yeah) sounds like it might be a better fit here.

Copy link
Member

@carols10cents carols10cents left a comment

Choose a reason for hiding this comment

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

Had some time so I decided to review what you've got so far, I've got a few questions that if you clear up for me what your intentions are, I can probably fix the text when I take my pass through :)


You've already encounted a few smart pointers in the book, we didn't call them
that by name, though. For example, in a certian sense, `String` and `Vec<T>`
are both smart pointers. They own some memory, and allow you to manipualate it.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/manipualate/manipulate

12: it manages a file handle that the operating system gives us.

Given that this is a design pattern in Rust, this chapter won't cover every
smart pointer that exists. Many libraries will build their own, as well, and
Copy link
Contributor

Choose a reason for hiding this comment

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

s/own, as well/own as well

@steveklabnik
Copy link
Member Author

@carols10cents @jonathandturner this is ready for a review! 🎊

@steveklabnik
Copy link
Member Author

oh, for the cycle thing, i took an example from here https://www.reddit.com/r/rust/comments/34dy5z/reference_counted_cycles/cqtsbkt/?st=izhduq8s&sh=f47a5d42

we should give them some kind of shout-out

Copy link

@sophiajt sophiajt left a comment

Choose a reason for hiding this comment

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

Sections up to RefCell feel pretty good with some nits and some areas for polish.

RefCell and some of the sections after feel like they need a tighter narrative. There feels like a fair amount of "this stuff is hard, and complicated" but I think that we could lead the reader through understanding how this stuff works so that they can write their code successfully.

I think we could tighten it up a bit. I mention not having the Cell section at all, which I think could work since we don't use it much after that section.

I think it's important to talk about cycles, though something like Rc<RefCell> feels perhaps like it's something also we could mention but not spend as much time on. Yes, some code definitely uses it, but I'm thinking about complexity budget for this chapter and how much the reader can hold in their heads. If we remove a few things from the last few sections and focus on working with the patterns like interior mutability and working around cycles, we're doing pretty good, I think.

So what are smart pointers, anyway? Well, we've learned about references in
Rust. "Pointer" is a generic term for something like a reference, that is,
pointers "point at" something else. References are a kind of pointer that only
borrows data; in many cases, smart pointers *own* the data that they point to.

Choose a reason for hiding this comment

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

Needs better linking phrase to set up comparison.

Heap allocated
Express Ownership of a heap allocated thing
Just like any value that has ownership, when a box goes out of scope, it will
be dealloacated.

Choose a reason for hiding this comment

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

s/dealloacated/deallocated


The three situations to use Box
It turns out that putting a single value on the heap isn't very useful, so you
won't use boxes very often. When do you need a box? When you want to ensure

Choose a reason for hiding this comment

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

I think you mean something like "so you won't use boxes by themselves very often." to contrast to your next sentence.

The compiler will look at this type, and say "How much memory do I need to
allocate for a value of `SomeEnum`? Let's look at `A`. Well, it has a value of
type `Box<SomeEnum<T>>`, and we know that a box always has the size of a
`usize`. Then, let's look at `B`. It doesn't save a value, so we don't need any

Choose a reason for hiding this comment

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

I know this, but how does the reader know this?

You just said SomeEnum leads to recursion, how does adding one more level of abstraction break the loop? Don't I have to know the size of SomeEnum to know the Box of it? How is Box different?

I mean, you don't have to go into too much detail, but you're asking them to trust you here but I think there are probably a couple sentences mixing that lets them follow you the whole way.

Copy link
Member

Choose a reason for hiding this comment

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

I'm adding this text just before the enum definition that uses Box, I think this will resolve it, please comment if not (and feel free to wait til i push up my changes to see them in context):

Because a Box<T> is a pointer, we always know what size it is: a usize, which is the size of a pointer. The value of the usize will be the address of the heap data. The heap data can be any size, but the address to the start of that heap data will always fit in a usize.

The part you commented on now reads (also changed this example to use List/Cons/Nil):

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.

thus removing the "we know" part.

value: T,
}

impl<T> Deref for DerefExample<T> {

Choose a reason for hiding this comment

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

I like that we go into explaining Deref. It feels like it helps unlock their ability to learn Rust more deeply on their own.

I was wondering if we wanted to show the Deref trait so they know what you're implementing.

Copy link
Member

Choose a reason for hiding this comment

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

We haven't covered associated types at this point yet, so we kind of want to gloss over that.

Copy link
Member

Choose a reason for hiding this comment

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

Also it's basically just this. I'm going to add a sentence before this example that says "the Deref trait requires implementing the deref method that borrows self and returns a reference to whatever part of the data you want returned" or similar.


1. We use `5` in sample one, but `Cell::new(5)` in sample two.
2. We use `=` in sample one, but `set` in sample two.
3. `five` is mutable in sample one, but not in sample two.

Choose a reason for hiding this comment

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

These three points don't help me understand what interior mutability is or why you would want it. I think the reader can assume that some patterns will be syntactically different. It feels like the first thing the reader will want is "okay what is this interior mutability thing?"

}
```

There's a lot going on here. Fundamentally, `Cycle` is a type that contains an

Choose a reason for hiding this comment

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

Probably don't need "Fundamentally,"

```

There's a lot going on here. Fundamentally, `Cycle` is a type that contains an
`Rc<Cycle>`. This means that we can have a 'referene cycle', hence the name of

Choose a reason for hiding this comment

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

s/referene/reference


Now, as you can see, doing this is very hard. To be honest, your authors had to
look up previous discussions of an example to get this right. But it can
happen, and the way that it happens is reasonably obvious: If you have an

Choose a reason for hiding this comment

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

s/If/if

Copy link
Member

Choose a reason for hiding this comment

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

i reworded and mooted


Now let's talk about concurrency, and some smart pointers that can be used
with multiple threads.
Next, let's talk about concurrency in Rust. We'll even learn aobut a few new

Choose a reason for hiding this comment

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

s/aobut/about

Copy link
Contributor

@matthewjasper matthewjasper left a comment

Choose a reason for hiding this comment

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

Some thoughts.
Also +1 to most of Jonathan's comments.

A(SomeEnum<T>),
B,
}
```
Copy link
Contributor

Choose a reason for hiding this comment

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

What happened to List<T>?

Copy link
Member

Choose a reason for hiding this comment

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

i like you, literally just said the same thing to steve before reading this

enum List<T> {
Cons(T, List<T>),
Nil,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In the following examples, would make things simpler to not use generics?

{
let y = &mut x;

*y += 1
Copy link
Contributor

Choose a reason for hiding this comment

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

This example should probably be a use of Deref, not DerefMut.
Alternatively, it could have one of each and be referred to when DerefMut is mentioned.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, good point. This example doesn't actually exist in chapter 4 currently; we're also going to go back there and add something along these lines and then rework this example so that it's actually referring back to something.

parameter.

if you look at the `assert_eq!`, we're comparing `'a'` to `*example`: the `*`
is what calls `Deref::deref`. And in the implementation, we can see that we
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps it would help to mention that
(a) *example is turned into *Deref::deref(&example).
(b) * on references is built in.

string slices! This means that `&String` will automatically coerce to a slice
of the full string.

There's also a `DerefMut` trait for overriding `*` on `&mut T`s in the same
Copy link
Contributor

Choose a reason for hiding this comment

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

Overriding * in assignments. This is where the example at the start of the previous could be used.

```

The difference between `Cell<T>` and `RefCell<T>` mirrors the difference
between types that are `Copy` and types that are not; if we don't need the
Copy link
Contributor

Choose a reason for hiding this comment

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

The point here is then that with Copy types we can pass around copies to avoid references.

call interior mutability a "pattern" is that it's not really a language
feature, it's a design pattern for libraries. More specifically, it's a pattern
that uses `unsafe` code inside to bend Rust's usual rules. We haven't yet covered
unsafe code in-depth, we will in Chapter 20. Luckily for us, you don't have to
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if Chapter 20 can hope to cover unsafe code "in-depth".


### The Interior Mutability Pattern
`Weak<T>` is exactly like `Rc<T>`, except that its reference count doesn't, well,
count when determining if something should be freed. You can turn an `Rc<T>` into
Copy link
Contributor

Choose a reason for hiding this comment

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

dropped, not freed.

### The Interior Mutability Pattern
`Weak<T>` is exactly like `Rc<T>`, except that its reference count doesn't, well,
count when determining if something should be freed. You can turn an `Rc<T>` into
a `Weak<T>` with the `Rc::downgrade` associated method, which takes an `&Rc<T>`
Copy link
Contributor

Choose a reason for hiding this comment

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

associated function

So in this example, when we call `oh_no.clone()`, we increment the count to two.
But when we pass that clone to `downgrade`, that count goes down again, to one.
Now, at the end of the function, when `oh_no` goes out of scope, it reduces the
count from one to zero, and the memory is freed. Success! We've broken the cycle.
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, being accurate with drop/free would make this more complicated.

Copy link
Member

Choose a reason for hiding this comment

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

How so?

Copy link
Contributor

Choose a reason for hiding this comment

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

Dropping the T value in the Rc<T> and freeing the underlying memory are separate steps.
That said, I don't think that this matters any more (here).

To say a bit more:
Memory is freed once there are no Rc<T> or Weak<T> pointing to that memory (to keep the reference counts around), so I thought that it's clone that actually frees the memory. But in this case clone is inside the Rc so oh_no frees the memory.

@carols10cents
Copy link
Member

Ok @steveklabnik @matthewjasper @jonathandturner @parir @gypsydave5, I think this is ready-ish for rereview. There are still a few TODOs, but the main content is done and I'd love your feedback.

The TODOs I have are:

  • I still need to go back to chapter 4 to put in a bit about deref in general and then update the back references here and here
  • I'm confused about why we're not allowed to call Drop::drop explicitly, only std::mem::drop, and I'd like some help explaining that here
  • I want to add some diagrams, probably graphviz things, here and here. Please imagine super awesome diagrams in the meantime.
  • I might be recommending a Bad Thing™ here in terms of software design-- i've created two separate scenarios that return the same value. It's setting off my internal alarm bells, I'm trying to tell them to shut up because this chapter is complicated enough, I'm wondering if other people think this is good or bad?

To create `Weak<T>` values, we call the `Rc::downgrade` associated function,
which takes an `&Rc<T>` as an argument, and gives a `Weak<T>` back.

Listing 15-16 shows a `main` method where we're trying to create `a` and `b` lists that point to each other, similarly to what we did in Listing 15-14, but this time we won't have a reference cycle and the values will be dropped when they go out of scope at the end of `main`:
Copy link
Member Author

Choose a reason for hiding this comment

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

needs wrapped

Copy link
Member

@aturon aturon left a comment

Choose a reason for hiding this comment

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

The interior mutability section looks solid! I left just a few suggestions/clarifications, as well as some notes on other parts of the chapter.

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
Copy link
Member

Choose a reason for hiding this comment

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

I think File is probably not a good example, if you're treating Deref as crucial (which I think you should).

frequently in Rust, this chapter won't cover every smart pointer that exists.
Many libraries have their own and you may write some yourself. The ones we
cover here are the most common ones from the standard library: `Box<T>`,
`Rc<T>`, and `RefCell<T>`. Along the way, we'll also cover:
Copy link
Member

Choose a reason for hiding this comment

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

Note that RefCell itself isn't a smart pointer, but the access to data it provides (Ref and RefMut) is.

Copy link
Member

Choose a reason for hiding this comment

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

Well poop. Uh. Idk what to do about this now.

Copy link
Member

Choose a reason for hiding this comment

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

Like..... should we make RefCell/interior mutability into its own chapter...? Frame RefCell as something that's also useful to use with smart pointers?

Copy link
Member

Choose a reason for hiding this comment

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

Well so RefCell does involve smart pointers -- the Ref and RefMut it gives you when you borrow are smart pointers. I think it's fine to go in this chapter, but the text just needs a little bit more precision.

It's worth talking about Ref and RefMut in more detail anyway, since understanding what happens when you drop them can help you understand how RefCell works (which I think is important for grokking "deep Rust").

Copy link
Member

Choose a reason for hiding this comment

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

Is this book meant to help people understand "deep Rust" though, or is that more for the nomicon....?

Copy link
Member

Choose a reason for hiding this comment

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

Heh, let me try a different argument. I personally find understanding how RefCell works really helpful for getting another perspective on borrow checking, and for seeing how various pieces (like Deref and Drop) come together. Obviously it's your call what makes the most sense here in the book. But at the very least, this section can be fixed up by just saying that when you call borrow what you get back is a smart pointer, Ref, which tracks the borrowing dynamically, and for which drop releases the borrow dynamically.

Copy link
Member

Choose a reason for hiding this comment

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

To be super clear: I think the best definition of a "smart pointer" in Rust is just something that implements Deref. The RefCell type doesn't itself, but it crucially depends on its sibling types Ref and RefMut, which do.

Copy link
Member

Choose a reason for hiding this comment

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

Ok @aturon, in this commit I tried to change the text to be more accurate but not go off into the weeds too much. Wdyt?

Copy link
Member

Choose a reason for hiding this comment

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

@aturon oops and this commit too, forgot to hit save

Copy link
Member

Choose a reason for hiding this comment

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

Perfect!


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
Copy link
Member

Choose a reason for hiding this comment

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

The pointer to the value doesn't necessarily live on the stack; you can put Box<T> values themselves on the heap.


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
Copy link
Member

Choose a reason for hiding this comment

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

This is another case where you're talking about things like File as smart pointers, which isn't right (they don't implement Deref). You should make clear that Drop is a more general concept, that happens to almost always be used when building a smart pointer.

Copy link
Member

Choose a reason for hiding this comment

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

sooooo do you think the WebSocket example in this section should be changed then? If so, what would be a better example?

Copy link
Member

Choose a reason for hiding this comment

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

Nope! You just need to be more clear that the section is a digression about Drop, which is more general than smart pointers. Alternatively, the material on Drop could be moved into an earlier chapter.

Copy link
Member

Choose a reason for hiding this comment

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

I don't really like either of those options :(

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, let me recommend something more specific. Change:

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.

to:

Smart pointers perform important cleanup when being dropped, like deallocating memory or decrementing a reference count. More generally, data types can manage resources beyond memory, like files or network connections, and use Drop to release those resources when our code is done with them.

@@ -0,0 +1,238 @@
## `RefCell<T>` and the Interior Mutability Pattern

*Interior mutability* is a design pattern in Rust for allowing you to mutate
Copy link
Member

Choose a reason for hiding this comment

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

This opening sentence is a bit problematic; it's not that you can mutate immutable data, it's that you can mutate data even though it's behind a shared reference, which would normally prevent mutation.

*Interior mutability* is a design pattern in Rust for allowing you to mutate
data that's immutable. The interior mutability pattern involves using `unsafe`
code inside a data structure to bend Rust's usual rules around mutation and
borrowing. We haven't yet covered unsafe code, we will in Chapter 20. The
Copy link
Member

Choose a reason for hiding this comment

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

I think it'd be good to add here: the pattern is used when you can somehow ensure that the rules about borrowing will be followed at runtime, even though the compiler can't prove that to itself.


### `RefCell<T>` has Interior Mutability

Unlike `Rc<T>`, the `RefCell<T>` type represents single ownership over the data
Copy link
Member

Choose a reason for hiding this comment

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

@nikomatsakis, do you have a good explanation for the name Cell?

Static analysis, like the Rust compiler performs, is inherently conservative.
That is, if Rust accepts an incorrect program, people would not be able to
trust in the guarantees Rust makes. If Rust rejects a correct program, the
programmer will be inconvenienced, but nothing catastrophic can occur.
Copy link
Member

Choose a reason for hiding this comment

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

Probably want to add: it's not possible for a static analysis to have perfect precision (maybe with a link to the halting problem?) Otherwise it's not clear why it's inherently conservative.


1. When you know that the borrowing rules are respected, but when the compiler
can't understand that that's true.
2. When you need interior mutability.
Copy link
Member

Choose a reason for hiding this comment

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

I would drop the second of these situations, because it follows from the first.


Here, we've created a new `RefCell<T>` containing the value 5. We can get an
immutable reference to the value inside the `RefCell<T>` by calling the `borrow`
method. More interestingly, we can get a mutable reference to the value inside
Copy link
Member

Choose a reason for hiding this comment

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

It's hard for the reader to see the really key point here: the fact that you can do this with just a & pointer to data. Maybe you can break out the body of main into a function that takes &RefCell<i32> to drive this home.

`weak_count` of references to an `Rc`. When an `Rc` goes out of scope, the
inner value will get dropped if the `strong_count` is 0, even if the
`weak_count` is not 0. When we attempt to use a `Weak<T>` reference, we'll get
an `Option<T>` that will be `Some` if the `Rc` value has not been dropped yet,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think 'attempt to use a Weak' is a bit ambiguous. I think you mean: to be able to use [get the value from] a Weak<T>, it first has to be upgraded to an Option<Rc<T>> [that will be some ...].

@matthewjasper
Copy link
Contributor

Some thoughts on the Weak<T> example:

  • Putting value first in Node<T> would maybe make the Debug output clearer.
  • Adding some more new lines to the output of the example would help.
  • Just less printing would also help.
  • Moving some of the common code into functions would make the example shorter, maybe easier to understand.
  • Using a struct that has a Drop implementation that prints something might be helpful to show that everything is cleaned up.
  • This could then allow an exercise like "try replacing Weak<T> with Option<Rc<T>> to see that some things aren't dropped, don't forget to comment out all of the debug printing.".

Copy link
Contributor

@matthewjasper matthewjasper left a comment

Choose a reason for hiding this comment

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

These are hopefully my last comments.

@@ -162,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
Copy link
Contributor

Choose a reason for hiding this comment

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

One last (I think) case of 'immutable data'.

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
Member 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

@carols10cents
Copy link
Member

These are hopefully my last comments.

i hope so too omg i'm so sick of this chapter

@carols10cents
Copy link
Member

I'm going to ship this to nostarch and explain that the one diagram isn't quite what we want but we're working on fixing it. I'm expecting to have to rearrange and reword lots of stuff when we get comments from nostarch, so if anyone has any comments/suggestions/feedback between now and then, please open a new issue and we'll take care of it then! Thank you to everyone who helped on this chapter! ❤️

@carols10cents carols10cents merged commit b7f5dc5 into master Mar 3, 2017
@carols10cents carols10cents deleted the ch15-smart-pointers branch March 3, 2017 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants