Ah, I overlooked a problem that was exposed during my attempt to prototype this handling of #[unsafe_no_drop_flag]
.
While I do believe one can skip the zeroing/filling after the destructor runs for a struct with #[unsafe_no_drop_flag]
(assuming its destructor has been written accordingly to ensure resources are cleaned up only once), there is another case where zeroing/filling is done currently in the generated code: When you move a value out of one location into another, we fill the original location with zeroes today (and with DONE_DROP
tomorrow) so that we do not run the destructor at all when we hit the end of the scope for the original location.
I had not really thought carefully about this before; certainly if we are going to support #[unsafe_no_drop_flag]
in any proper form, we need to actually provide the means for structs to customize how those moves are handled.
E.g. I am currently thinking maybe a second method on Drop
, perhaps named forget
, with an inlined empty default implementation, that structs with #[unsafe_no_drop_flag]
are expected to implement. And then a move such a let b = a;
would also call a.forget()
.
So for example one might have:
#[unsafe_no_drop_flag]
pub struct Vec<T> { ptr: Unique<T>, len: usize, cap: usize }
impl<T> Drop for Vec<T> {
fn forget(&mut self) {
// a zero capacity indicates no work to do
self.cap = 0;
}
fn drop(&mut self) {
// a zero capacity indicates no work to do
if self.cap != 0 {
unsafe {
for x in &*self {
ptr::read(x);
}
dealloc(*self.ptr, self.cap)
}
self.cap = 0;
}
}
}
(Or maybe we should just remove support for #[unsafe_no_drop_flag]
entirely; it becomes harder to justify supporting it with stack-local drop flags.)