Description
Destructors can invalidate borrows of their contents:
struct VecWrapper<'a>(&'a mut Box<u32>);
impl<'a> Drop for VecWrapper<'a> {
fn drop(&mut self) {
*self.0 = Box::new(0);
}
}
fn get_dangling<'a>(value: VecWrapper<'a>) -> &'a u32 {
let s_inner: &'a Box<u32> = &*value.0; // Borrow (*)
let result = &s_inner;
result
// the destructor of `value`, that runs here, invalidates the borrow (*)
}
Today's rustc's handling of the situation does not provide us much guidance, as can be seen by this example compiling, running, and accessing invalid memory (that is rust-lang/rust#31567).
However, we can't have any drop
invalidate all interior references. In the most trivial case, drops of "trivial drop" types, like references, don't even appear in MIR. And even if they appeared, having mutable references invalidate their contents when dropped would basically break all of Rust.
Note that I don't think we have to worry about the interior of containers in the common case, at least until we implement some sort of DerefPure
:
fn example_indexmut<'a>(mut v: Vec<&'a mut u32>) -> &'a mut u32 {
&mut *v[0] //~ ERROR
}
The desugaring converts that into:
// that is equivalent to this MIR modulo panics
t0 = &'a mut v; // can't be a shorter lifetime, because of constraints
t1 = IndexMut::index_mut(t0, 0);
ret = &mut *t1;
drop(v);
storagedead v;
Here the lifetime constraint in IndexMut
forces the borrow of v
(for t0
) to live for 'a
, which already causes a borrowck error with the storagedead
.
However, technically, we could have a container that "publicly" contains its contents, and which we know can't access its contents because of dropck:
struct NoisyDrop<T>(T);
impl<#[may_dangle] T> Drop for T {
fn drop(&mut self) {
println!("Splat!");
}
}
fn is_actually_sound<'a>(value: NoisyDrop<&'a mut Box<u32>>) -> &'a u32 {
let s_inner: &'a Box<u32> = &*value.0; // Borrow (*)
let result = &s_inner;
result
// the destructor can't actually access the mutable reference,
// because it is `#[may_dangle]`, and therefore this function
// is sound.
}
I don't think that is something we have to strive to implement, at least until someone comes with up with a use-case. So I think a reasonable rule would be that a drop
invalidates all contents that are reachable from a destructor. We already do this check today in order to check whether deinitialization/reinitialization would be valid.
However, the question remains - do we want Box
to have a destructor, or should it be treated like any other type?
fn foo<'a, T>(x: (SomethingWithADropImpl<&'a mut T>, Box<&'a mut T>, &'a mut T)) -> &'a mut T {
return &mut x.0; // obviously unsound unless we do dropck trickery - let's ban this
return &mut x.1; // not sure - allowed today (of course), sound, but do we want it?
return &mut x.2; // allowed today, probably want to support
}