Reputation: 19164
Why does Drop
’s method have signature fn drop(&mut self)
instead of fn drop(self)
? This makes it difficult to move values out of the fields e.g. self.join_handle.join()
or std::mem::drop(self.file)
(error: cannot move out of type X
, which defines the Drop
trait).
Upvotes: 57
Views: 6140
Reputation: 15113
Just want to add a few things to the other answers here.
Commented here, in reference to making fn drop()
take self
:
Sure, but the compiler could easily special case this case.
Not feasibly, no. It'd have to check the entire call graph, or probably something much more invasive and complex.
Take this example:
struct MyType {
// ...
}
fn do_something(v: MyType) {
drop(v); // Implicitly added by compiler
}
impl Drop for MyType {
fn drop(self) {
do_something(self);
}
}
In the above, the compiler would have to recursively "special case" do_something
, and anything do_something
might call / pass v
to, and anything those functions call and pass v
to, etc.
That'd be a compiler implementation nightmare, and would grind compilation speed to a halt.
It'd also require that several different implementations of functions get generated (one with the implicit drop, one without), or pass "state" along everywhere, which would certainly make matters worse.
Finally, if you're wondering a nicer way to handle fields whereby you need to "consume" the value in the fn drop()
destructor, use the ManuallyDrop
wrapper type on the field.
use std::mem::ManuallyDrop;
struct MyInnerValue;
struct MySpecialInnerValue;
struct MyOuterValue {
inner: MyInnerValue,
special_inner: ManuallyDrop<MySpecialInnerValue>,
}
impl Drop for MyInnerValue {
fn drop(&mut self) {
println!("MyInnerValue dropped");
}
}
impl Drop for MySpecialInnerValue {
fn drop(&mut self) {
println!("MySpecialInnerValue dropped");
}
}
fn consume_and_drop_special(value: MySpecialInnerValue) {
println!("consuming MySpecialInnerValue");
drop(value); // Automatically inserted by compiler.
}
impl Drop for MyOuterValue {
fn drop(&mut self) {
// SAFETY: We don't use the field after we take it.
let special_inner = unsafe { ManuallyDrop::take(&mut self.special_inner) };
consume_and_drop_special(special_inner);
println!("MyOuterValue dropped");
}
}
pub fn main() {
let outer = MyOuterValue {
inner: MyInnerValue,
special_inner: ManuallyDrop::new(MySpecialInnerValue),
};
drop(outer);
}
which outputs:
consuming MySpecialInnerValue
MySpecialInnerValue dropped
MyOuterValue dropped
MyInnerValue dropped
The only unsafe
requirement is that you don't use the original field value after the ManuallyDrop::take()
, which unless you use it multiple times within the fn drop()
is always the case (since the value is dropped / freed and never accessible after drop()
returns).
Just make sure, of course, you actually drop the value at some point - there's chance the ManuallyDrop::take()
is removed but the field isn't changed to also remove the ManuallyDrop
, in which case any resources held by that field might leak.
Upvotes: 0
Reputation: 299999
Actually, it is unnecessary for Drop::drop
to take ownership of the value.
In Rust, ownership is automatically handled at language level, and therefore the compiler makes sure to properly implement ownership semantics; thus when a Foo { a: int, b: String }
goes out of scope, the compiler will drop Foo
by dropping its inner fields automatically.
It is thus unnecessary for Drop::drop
to drop the fields!
Actually, after Drop::drop
is called on Foo
, the compiler will itself mem::drop
the different fields (which may also invoke Drop::drop
on those fields which define it, such as b: String
here).
What does Drop::drop
do, then?
It is used to implement extra logic on top of what the compiler will do; taking your JoinHandle
example:
#[stable(feature = "rust1", since = "1.0.0")]
#[unsafe_destructor]
impl<T> Drop for JoinHandle<T> {
fn drop(&mut self) {
if !self.0.joined {
unsafe { imp::detach(self.0.native) }
}
}
}
Here, Drop::drop
is used to detach the thread, for example.
In a collection such as Vec::vec
:
#[unsafe_destructor]
#[stable(feature = "rust1", since = "1.0.0")]
impl<T> Drop for Vec<T> {
fn drop(&mut self) {
// This is (and should always remain) a no-op if the fields are
// zeroed (when moving out, because of #[unsafe_no_drop_flag]).
if self.cap != 0 && self.cap != mem::POST_DROP_USIZE {
unsafe {
for x in &*self {
ptr::read(x);
}
dealloc(*self.ptr, self.cap)
}
}
}
}
Here, as the raw memory is manipulated in a way opaque to the compiler, this implementation takes care of:
Upvotes: 7
Reputation: 65822
Let's look at how std::mem::drop
is implemented:
pub fn drop<T>(_x: T) { }
That's right: it's an empty function! That's because it takes advantage of move semantics to acquire ownership of its argument. If T
implements Drop
, the compiler automatically inserts a call to Drop::drop(_x)
at the end of the function. This happens to all arguments received by value (that is, in fact, all arguments whatsoever, but dropping a reference doesn't drop the referent).
Now consider what would happen if Drop::drop
took its argument by value: the compiler would try to invoke Drop::drop
on the argument within Drop::drop
— this would cause a stack overflow! And of course, you would be able to call mem::drop
on the argument, which would also try to recursively call Drop::drop
.
Upvotes: 20