`RefCell` simple confusion

let a = RefCell::new(42);
let b = a.borrow();
let b = a.borrow_mut();

This raises an error, already borrowed: BorrowMutError.
This works:

let a = RefCell::new(42);
{
    let b = a.borrow();
}
let b = a.borrow_mut();

Then I looked at doc of RefCell::borrow()

/// The borrow lasts until the returned `Ref` exits scope. ...
pub fn borrow(&self) -> Ref<'_, T> { ... }

In my eyes, the first b should drop before the second b.

How can this happen, what is the detail here?

The first b does drop first, because its scope (the {} block) is closed first.

Objects are dropped at the end of their scope/block.

Oh, I mean the original code snippet.

Variables drop in the opposite order of declaration.

let a = RefCell::new(42);
let b1 = a.borrow();
let b2 = a.borrow_mut();
// b2 drops
// b1 drops
// a drops

Note that in this case, the drop order doesn't matter anyway -- the drop of b1 happening after the call to borrow_mut() is what matters.


Also, shadowing doesn't effect drops.

let a = RefCell::new(42);
let b = a.borrow();
let b = a.borrow_mut();
// b "2" (`a.borrow_mut()`) drops
// b "1" (`a.borrow()`) drops
// a drops

The second b doesn't cause the first b to drop earlier. (And drop order still doesn't matter.)

2 Likes

Then why this can compile?

let mut a = 42;
let b1 = &a;    // <- b1 drops immediately
let b2 = &mut a;

Due to NLL(Non-Lexical Lifetimes), b1 drops since its last use at line 2.

But why here, b1 does not drop immediately?

--
Oh, write until here, I suddenly know that b1: Ref<'_> must impled Drop. So its scope is to the end... Thank you!

It sounds like you get it now. In the reference case, NLL and the fact that references have trivial destructors are what allows it to compile.

Technically b1 still goes out of scope ("drops") after b2, but the borrow checker knows that b1 can't observe a when it goes out of scope, so the borrow of a can still end before b1 goes out of scope.

The presence or absense of a non-trivial destructor, such as a Drop implementation, is key.

1 Like

No it doesn't. Values aren't dropped eagerly upon their last use. NLL does consider the last use for borrow checking reasons, but dropping is never implicitly moved around. This is because dropping is very much an observable effect.

This is fundamentally why the two snippets differ in their behavior. RefCell's guard types are Drop so they can't be assumed by the compiler to be unused any earlier than the end of their scope.

Alas, library types can't be used for modeling everything that the built-in analyses of the language do, and you'll have to live with this. Insert explicit drops if you need different behavior.

2 Likes

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.