First, let’s take a look at the ownership rules. Keep these rules in mind as we work through the examples that illustrate them:
Each value in Rust has an owner.
There can only be one owner at a time.
When the owner goes out of scope, the value will be dropped.
To state my confusion we have
There can only be one owner at a time.
What about Rc? Isn't that violating this rule?
When the owner goes out of scope, the value will be dropped.
When a variable is reassigned it's data must be dropped to avoid a memory leak, but the variable (owner) doesn't go out of scope? So shouldn't this say when the owner goes out of scope OR is reassigned? Or perhaps I don't actually know what owner means.
This reassignment business also makes the borrowing rules in the Rustnomicon a little vague.
Does reassignment create a mutable reference under the hood? The compiler gives a differently worded message when creating a mutable reference to something that is immutably references vs. reassigning something that is immutably referenced.
The text of the book isn't mathematically precise (and it shouldn't be). I think it's better to think of the "value" of an Rc<T> as a pointer together with some extra information ("which Rc is this"). That value only has one owner. You can't actually get the pointed-to T in most cases, just a reference to it.
Under the hood does Rc move ownership of the "value" between different instances of a cloned Rc, as the owner goes out of scope? And when there is no one else to move to, then drops it for real?
Under the hood, Rc stores a pointer. Rust ownership rules don't apply to pointers, so the library is free to create its own rules. It uses this freedom to define shared ownership.
Yeah, that part of the book is ignoring shared ownership. It (and most Rust learning material I've seen) has a "give the gist so you can get started, circle back to refine things later[1]" approach. There's a chapter on Rc much later.
That said, "shared ownership" is more of a high-level concept, and not something the compiler cares about per se (unlike destructive moves, overwriting values, and destructors).
You're correct, overwrites are another situation where drops happen.
Anyway, values are dropped at the end of their lexical scope if
they're alive
weren't overwritten
weren't moved from
weren't a temporary dropped at an end of a statement
they're not being returned out of the scope
be that a function or a block expression
Not exactly. If there is a destructor (Drop implementation or drop glue), it is called when the value is dropped. Drop implementations take a &mut self.
But not all types have destructors. See the first link below for an example of where this matters.
To be useful, most pointers are eventually converted to references, causing the aliasing rules to come into play. Enforcing those rules is a manual process with more footguns than C++, so there's a book about it.
That’s a neat interpretation. Of course the real implementation “under the hood” does not assign and re-assign any specific one of the Rc to be a true owner, but your description is a good mental model when thinking about certain details of Rc (and Arc): For example take Arc’s implementation for Sync or Send. They both requireT: Send + Sync for the contained type.
If we think of Arc just as “a &'static T , where the T would be dropped when theres no one referencing it” then it’s easy to miss what this “would be dropped” entails and why there isn’t just, like there would be for a true&'static T, an implementation of impl Send for Arc<T> where T: Sync (and impl Sync for Arc<T> where T: Sync).
If we think of Arc as “move ownership of the "value" between different instances of a cloned Arc, as the owner goes out of scope” then it’s much clearer! Dropping an Arc can (logically) move ownership to another clone of the Arc, so if clones of an Arc are allowed to be on different threads, then we must require the contained type T to be safe to move between threads, too (aka T: Send)!
Nope. The Book is talking about the low-level mechanisms of how variables and values work. Multiple Rcs pointing to the same value are still separate values, each with its single owner (be it a local variable, a struct field, or whatever). The individual Rc instances work just like any other value – if they go out of scope, they are dropped, etc.
Now the high-level purpose of Rc is to provide the "illusion" of shared ownership, and it achieves this by using raw pointers, heap allocation, and reference counting in the background. None of these are part of the language, and the compiler doesn't directly understand any of them. It only understands when it must invoke the Drop impl of individual Rc instances, and that will do the right thing as-if the pointed value had shared ownership.
The Book didn't say that something is dropped only when it goes out of scope. It's trying to teach the concepts gradually. There's no error here. If it listed all possible scenarios when something is dropped, people would complain about that instead.