Here are the ownership rules as defined in the Rust Book
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
This reassignment business also makes the borrowing rules in the Rustnomicon a little vague.
There are two kinds of reference:
- Shared reference:
- Mutable reference:
Which obey the following rules:
- A reference cannot outlive its referent
- A mutable reference cannot be aliased
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?
You can mentally model
Rc<T> as a
&'static T, where the
T would be dropped when theres no one referencing it.
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" 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
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
Arc): For example take
Arc’s implementation for
Send. They both require
T: 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
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.
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.