In the past, there used to be another thing to take into consideration.
But this situation can sometimes lead to overly restrictive code. That's why the standard library uses an unstable and unsafe
attribute to opt back into the old "unchecked" drop-checking behavior, that this very documentation warned about: the #[may_dangle]
attribute.
This section can be skipped if you are only writing your own library code; but if you are curious about what the standard library does with the actual Vec
definition, you'll notice that it still needs to use a _owns_T: PhantomData<T>
field for soundness.
Click here to see why
Consider the following example:
fn main() {
let mut v: Vec<&str> = Vec::new();
let s: String = "Short-lived".into();
v.push(&s);
drop(s);
} // <- `v` is dropped here
with a classical impl<T> Drop for Vec<T> {
definition, the above is denied.
Indeed, in this case we have a Vec</* T = */ &'s str>
vector of 's
-lived references to str
ings, but in the case of let s: String
, it is dropped before the Vec
is, and thus 's
is expired by the time the Vec
is dropped, and the impl<'s> Drop for Vec<&'s str> {
is used.
This means that if such Drop
were to be used, it would be dealing with an expired, or dangling lifetime 's
. But this is contrary to Rust principles, where by default all Rust references involved in a function signature are non-dangling and valid to dereference.
Hence why Rust has to conservatively deny this snippet.
And yet, in the case of the real Vec
, the Drop
impl does not care about &'s str
, since it has no drop glue of its own: it only wants to deallocate the backing buffer.
In other words, it would be nice if the above snippet was somehow accepted, by special casing Vec
, or by relying on some special property of Vec
: Vec
could try to promise not to use the &'s str
s it holds when being dropped.
This is the kind of unsafe
promise that can be expressed with #[may_dangle]
:
unsafe impl<#[may_dangle] 's> Drop for Vec<&'s str> { /* … */ }
or, more generally:
unsafe impl<#[may_dangle] T> Drop for Vec<T> { /* … */ }
is the unsafe
way to opt out of this conservative assumption that Rust's drop checker makes about type parameters of a dropped instance not being allowed to dangle.
And when this is done, such as in the standard library, we need to be careful in the case where T
has drop glue of its own. In this instance, imagine replacing the &'s str
s with a struct PrintOnDrop<'s> /* = */ (&'s str);
which would have a Drop
impl wherein the inner &'s str
would be dereferenced and printed to the screen.
Indeed, Drop for Vec<T> {
, before deallocating the backing buffer, does have to transitively drop each T
item when it has drop glue; in the case of PrintOnDrop<'s>
, it means that Drop for Vec<PrintOnDrop<'s>>
has to transitively drop the PrintOnDrop<'s>
s elements before deallocating the backing buffer.
So when we said that 's
#[may_dangle]
, it was an excessively loose statement. We'd rather want to say: "'s
may dangle provided it not be involved in some transitive drop glue". Or, more generally, "T
may dangle provided it not be involved in some transitive drop glue". This "exception to the exception" is a pervasive situation whenever we own a T
. That's why Rust's #[may_dangle]
is smart enough to know of this opt-out, and will thus be disabled when the generic parameter is held in an owned fashion by the fields of the struct.
Hence why the standard library ends up with:
// we pinky-swear not to use `T` when dropping a `Vec`…
unsafe impl<#[may_dangle] T> Drop for Vec<T> {
fn drop(&mut self) {
unsafe {
if mem::needs_drop::<T>() {
/* … except here, that is, … */
ptr::drop_in_place::<[T]>(/* … */);
}
// …
dealloc(/* … */)
// …
}
}
}
struct Vec<T> {
// … except for the fact that a `Vec` owns `T` items and
// may thus be dropping `T` items on drop!
_owns_T: core::marker::PhantomData<T>,
ptr: *const T, // `*const` for variance (but this does not express ownership of a `T` *per se*)
len: usize,
cap: usize,
}