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
.