This code DOES panic. Try it in playground.
If the match only do partial move like in the following though,
enum MyEnum {
V1(String),
V2
}
impl Drop for MyEnum { fn drop(&mut self) { panic!(); } }
fn main() {
let foo = MyEnum::V1("v1".into());
if let MyEnum::V1(s) = foo { println!("{}", s) }
}
You get
error[E0509]: cannot move out of type `MyEnum`, which implements the `Drop` trait
--> src/main.rs:10:10
|
10 | if let MyEnum::V1(s) = foo { println!("{}", s) }
| ^^^^^^^^^^^-^
| | |
| | hint: to prevent move, use `ref s` or `ref mut s`
| cannot move out of here
Deeper thoughts
I believe dropping an object includes two related but different things:
- Fulfill the invariant for an object to be dropped (for example, call some FFI to release external resources). After that, the drop clue for the main object is considered being used and so should not be run again
- Destruct the object into its parts, and allows further dropping clue on child objects to run
I believe today's Drop
is good for step 1, as it expect a fully functional object, including its drop clue. However, trying to do partial move in such a drop
method is trying to mix it up with step 2, which is the source of the problems we have today.
Having it in consideration, we can go forward to the right thing without breaking any backward compatibility issues. Here is the proposal:
New trait: Destruct
trait Destruct {
#[destruct]
fn destruct(self);
}
impl<T:Destruct> for Option<T> {
#[destruct]
fn destruct(self) {
match self {
Some(v) => v.destruct(),
None => ()
}
}
}
Rules:
- A function/method have
#[destruct]
attribute must accept a single argument and its first HIR operation must be a pattern match that moves things from the arguments out (we call this "moving match"). This guarantees no references to the destructing object can occur in user code. destruct
will be called when the object was implicitly dropped. But if it is destructed through moving matches it is not called.- If an object implements both
Drop
andDestruct
, it does not cause E0509: a moving match will calldrop
before the match, to justify the call. However, partial move indrop
will be unconditionally UB. - If an object implements only
Destruct
, it can also beCopy
. I don't think this is useful, but there is no reason to stop this. - For objects implements only
Drop
, it behaves exactly the same as today: partial moving match results in E0509.drop
can do partial move without UB. We may later consider lint against it and finally obsolete this style. #[destruct]
can apply to any functions, as long as it follows the same rule. Especially, this allows closures being declared like this, making it possible to pass a "pattern match snippet" across. However, for methods that call-by-reference rather than call-by-move, the match need not be a moving match.
Compare to the DefaultDrop<T>
proposal:
I understand @stepancheg 's DefaultDrop<T>
means a T
with the drop clue removed. However, it have more magic here, and didn't resolve E0509 at all.