yonran
yonran

Reputation: 19164

Why does Drop take &mut self instead of self?

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

Answers (3)

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

Matthieu M.
Matthieu M.

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:

  1. Dropping each element held by the vector
  2. Deallocating the memory

Upvotes: 7

Francis Gagn&#233;
Francis Gagn&#233;

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

Related Questions