Help about RefCell

I try to compile the following code, but failed

struct MyTest<'a> (RefCell<Option<&'a MyTest<'a>>>);
impl <'a> Drop for MyTest<'a> {
   fn drop(&mut self) {
       //do something
     }
   }
   fn Test () {
       let a = MyTest(RefCell::new(None)); 
       let b = MyTest(RefCell::new(Some(&a)));
   }

this is the error info:

error[E0597]: `a` does not live long enough
  --> src\mod.rs:59:42
   |
59 |         let b = MyTest(RefCell::new(Some(&a)));
   |                                          ^^ borrowed value does not live long enough
60 |     }
 |     -
   |     |
   |     `a` dropped here while still borrowed
   |     borrow might be used here, when `a` is dropped and runs the `Drop` code for type `mod::mod_ts::MyTest`

But once I remove the implement of trait "Drop" as following, the code will be success to compile.

struct MyTest<'a> (RefCell<Option<&'a MyTest<'a>>>);
    fn Test () {
         let a = MyTest(RefCell::new(None));
         let b = MyTest(RefCell::new(Some(&a)));
    }

Or I change RefCell to other smart ptr such as Box, this problem also disappear.

Maybe this usage is unusual, I just want to demonstrate the compilation error.
I very confuse, the problem seemed to be related to 'RcCell' and 'Drop' trait, but I don't know specific reason, please help, thanks.

Don't put references inside structs. Structs need to own their content. RefCell is also supposed to own its contents.

So instead of RefCell<Option<&'a MyTest<'a>>> you should at very least use RefCell<Option<MyTest<'a>>>.

But more realistically don't make the struct itself temporary and bound to a scope (due to borrowing contents instead of storing it), and make it RefCell<Option<MyTest>>.

You can't use & to store something "by reference". The correct Rust type to store things by reference is Box or Arc.

Thank you.
These codes have been simplified to illustrate the problem.
I am confused why these codes are not compiled, and once the "Drop" function is removed, these codes will be compiled smoothly.
Or I change Refcell to another container, such as Box, these codes will also compile successfully.

In fact, even if b have reference of a, but it will be dropped before a.
Why is the customed 'drop' function defined, the error message ' a dropped here while still borrowed' appears. But while using default function 'drop', it will be compiled successfully.

Expect answer, thanks

Note that @kornel didn't formulate it as such, but it is possible to put references inside structs. Structs don't need to own their contents at all times. It's just that dealing with explicit lifetimes is more subtle than just slapping an <'a> on everything, hence the advice that beginners should prefer to create owning types instead of borrowing ones.


To that end, another piece of useful advice is that &'a Type<'a> is almost never what you need – i.e. a reference that is required to point to a value with the exact same lifetime as its parameter is rarely useful. The reason for this is that borrowing "view" types (which contain refefences to other types) usually need to have a lifetime argument for a pointer that points to an entity living longer than the instance of the view/borrowing type itself.

Hence, when dealing with a pointer to a view type, you usually want something like &'a Type<'b> instead.

2 Likes

Thank you @H2CO3 and @kornel for their patient answers.
Maybe these codes are unusual, I just want to demonstrate this compilation error.
I wonder why these codes are not compiled. It seems to be related to the 'RefCell' and 'Drop' trait, but I don't know the specific reason, please help, thank you.

Please read the pinned post about syntax highlighting.

Here’s your code blocks properly formatted.

use std::cell::RefCell;
struct MyTest<'a>(RefCell<Option<&'a MyTest<'a>>>);
impl<'a> Drop for MyTest<'a> {
    fn drop(&mut self) {
        //do something
    }
}
fn Test() {
    let a = MyTest(RefCell::new(None));
    let b = MyTest(RefCell::new(Some(&a)));
}
error[E0597]: `a` does not live long enough
  --> src/lib.rs:10:38
   |
10 |     let b = MyTest(RefCell::new(Some(&a)));
   |                                      ^^ borrowed value does not live long enough
11 | }
   | -
   | |
   | `a` dropped here while still borrowed
   | borrow might be used here, when `a` is dropped and runs the `Drop` code for type `MyTest`
use std::cell::RefCell;
struct MyTest<'a>(RefCell<Option<&'a MyTest<'a>>>);
fn Test() {
    let a = MyTest(RefCell::new(None));
    let b = MyTest(RefCell::new(Some(&a)));
}
2 Likes

It fails to compile due to the double-use of 'a inside the struct. By reusing the same lifetime, you force both lifetimes to be equal to each-other.

Since the inner 'a is annotated on the struct, that lifetime must contain the entire region the struct lives in. This is what it means to annotate a struct with a lifetime. However it is also the lifetime of the borrow of the struct. Since you forced them to be equal, you have borrowed the struct for a lifetime that must contain the entire region in which the struct exists, and since the struct also exists when the destructor runs, the destructor is inside this 'a region. However a destructor requires &mut access to the struct, so it cannot overlap with an immutable borrow, hence the error.

2 Likes

The error also occurs with

fn foo<'a>(_: &'a MyTest<'a>) {}

fn Test() {
    let x = MyTest(RefCell::new(None));
    foo(&x);
}

The problem is the type &'a MyTest<'a>. In the expression foo(&x), the type of foo dictates that the lifetime of the borrow &x has to be (at least) as long as the lifetime of the reference that x may contain.

(x does not contain any references since it’s containing a None value, but judging by its type it could contain a reference with lifetime 'a being the same lifetime as the &x borrow of x itself. Rust’s type checking is not as sophisticated as to care what runtime value a variable actually contains.)

Now think about the implicit call to drop(&mut self) on x once it goes out of scope. When can this call happen? Well, since self and thus the fields of x are accessible inside the body of .drop() when it is called, this call has to happen inside of that lifetime 'a from MyType<'a> because otherwise inside drop one could access an invalid reference (contained in the .0 field of MyType when it’s a Some value).

But .drop() has to borrow x mutably, i.e. there’s an implicit borrow &mut x happening for this and by the fundamental principles of Rust’s ownership system, that borrow must not overlap with the immutable borrow &x that was passed to foo(). As I explained above, foo()’s type ensures that the &x borrow lives at least as long as the 'a parameter in the type MyType<'a> of x. So to avoid overlap of the implicit &mut x for .drop(), the .drop() call has to happen outside of the lifetime of the &x borrow which means outside of the (shorter or equal) lifetime 'a.

In effect, there is no possible point in time left for drop to happen. Neither inside nor outside of 'a would be okay. The compiler checks for and prohibits thise kinds of scenarios in the so-called “drop check” and thus rejects this code. You can read more about drop check in the Rustonomicon.


This is also the reason that your code compiles when you don’t implement Drop. There is also an unsafe workaround where you have to assert that you do not access the references contained in MyType in the Drop impl. Then the compiler can make the drop for MyType<'a> happen outside of 'a and during that .drop() invocation the lifetime-'a references may already dangle (point to deallocated memory, etc.):

#![feature(dropck_eyepatch)]
use std::cell::RefCell;
struct MyTest<'a>(RefCell<Option<&'a MyTest<'a>>>);
unsafe impl<#[may_dangle] 'a> Drop for MyTest<'a> {
    fn drop(&mut self) {
        //do something
        //but without dereferencing the reference contained inside `MyTest`!!
    }
}
fn Test() {
    let a = MyTest(RefCell::new(None));
    foo(&a);
}

fn foo<'a>(_: &'a MyTest<'a>) {}

This unsafe feature is not yet fully stabilized and thus only available on the nightly compiler. Please note that in this particular case (and in general) the correct way to solve your problem is usually to restructure your type instead, in your case by getting rid of the referenceing and moving to some variant of owning smart pointer like Box, Rc, or Arc.

3 Likes

Thank @steffahn very much for your patient and very clear answers. I probably understand, but I still have to study harder at the details.
I still have a question, why is RefCell? I tried some other smart pointer, only RefCell and Cell can cause this problem. Box, Rc, and even the wrapper implemented by myself can compile smoothly
Looking forward to your further reply.
By the way, I have formatted my original question. Thank you very much again. :slight_smile:

Thank @alice for reply, I need to study harder :slight_smile:
But I still have a problem in another reply, hope your futher answer,thanks

Interesting why it doesn’t create any problem for Box. I will try to figure this out. Note however that RefCell is not a smart pointer type. The RefCell (similar to Cell or Mutex) contain their argument type directly, without indirection. They just manage special forms of access to the inner type through shared references like &RefCell<T> but don’t change anything about ownership, (they also all have an into_inner and get_mut method that allows getting the inner type back out without any restrictions when you own the RefCell or have an exclusive (mut) reference to it).

Smart pointer types are types like Box, Rc and Arc.

1 Like

Consider the following types for two lifetimes 'long and 'short.

let a: MyTest<'long> = MyTest(RefCell::new(None));
let b: MyTest<'short> = MyTest(RefCell::new(Some(&a)));

If the lifetimes could be assigned like this, everything would be good. Unfortunately the trouble is that MyTest<'short> contains a &'short MyTest<'short> inside, so you can't use a MyTest<'long> in that position — the types don't match.

However if you replace the RefCell with a Box, the MyTest type is no longer invariant, which means that a &'short MyTest<'long> can be converted to a &'short MyTest<'short>, and as such we no longer have a type-mismatch. This conversion is prevented when using a RefCell, but not when using a Box.

4 Likes

Thanks @alice and @steffahn for kindly help. I think I know a little bit about this problem and got more information from the cited article.
Rust is very safe and very interesting, I will study more hard.
Thanks again :grinning: :grinning:

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.