Just randomly curious: Was there ever a time where it was considered if Drop should take ownership instead of &mut self, or if there should be an alternative DropOwned which does that? (With the obvious compiler support to not make it recurse into itself).
I suggest you repost this on https://internals.rust-lang.org/ since that's where the design of the language (present, future, past, and hypothetical) is discussed.
IIRC it's been considered, but it creates a situation where you can make self escape drop() (save in a global variable), and end up with an object that stays alive after Drop, and will have Drop called on it again elsewhere later. it's solvable, but a weird case to deal with.
There's ManuallyDrop::take() for moving fields in drop.
Yeah, I work around the partial move issues with a combination of Option::take() and ManuallyDrop::take(), but the issue with ManuallyDrop is that I know that one of these days I will forget to drop something (.. if I haven't already!).
One version of this that would've avoided the double-drop issue is forcing you (via compiler magic) to use destructuring in the receiver. Drop has some magic to it anyway (preventing calling from own code), so this wouldn't be a "worse" solution in that sense.
I think if it were possible at all to destructure in that way (currently you cannot move out of a struct that has a Drop impl), it'd make sense to make it possible to do more places than just Drop:
let bypass_drop!(MyStruct {a, b}) = my_struct;
The main use case for this outside of Drop would be to “disarm” guard objects, causing something to be done that is different than, and mutually exclusive with, if they were just dropped. Right now, to implement that you have to, depending on the situation, do one or both of:
Use Option in the type of each field that should be moved out of
Call std::mem::forget() on the struct afterward so its Drop doesn't run
This destructuring operation would remove the need for both.
Implementing such a bypass_drop! macro is actually already kind of possible [1]:
macro_rules! bypass_drop{
(let $struct_name: ident { $( $field: ident $(: $rename: pat )? ),* $(,)? } = $value: expr ; ) => {
fn __check_duplicates_and_exhaustiveness() {
#[allow(unreachable_code)]
let _ = $struct_name {
$(
$field: unreachable!()
),*
};
}
let __manually_drop = ManuallyDrop::new($value);
$(
//SAFETY: the trickery above ensures that no fields are moved out of twice
//SAFETY: the struct is in a ManuallyDrop, so the destructor can't observe the value
__unsafe_field!(__manually_drop $field $($rename)?);
)*
};
(let $struct_name: ident { $( $field: ident $(: $rename: pat )? , )* .. $(,)? } = $value: expr ; ) => {
#[allow(unreachable_code)]
let __value = match true {
false => {
$struct_name {
$(
$field: unreachable!(),
)*
..unreachable!()
}
},
true => $value
};
let __manually_drop = ManuallyDrop::new(__value);
$(
//SAFETY: the trickery above ensures that no fields are moved out of twice
//SAFETY: the struct is in a ManuallyDrop, so the destructor can't observe the value
__unsafe_field!(__manually_drop $field $($rename)?);
)*
};
}
macro_rules! __unsafe_field{
($manually_drop: ident $field: ident $rename: pat) => {
let $rename = unsafe{ ptr::read(& $manually_drop.$field) };
};
($manually_drop: ident $field: ident) => {
let $field = unsafe{ ptr::read(& $manually_drop.$field) };
};
}