Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Drop take &mut self instead of self?

Tags:

rust

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).

like image 508
yonran Avatar asked Jun 18 '15 03:06

yonran


People also ask

How long does drop usually take to ship?

Typically brands will take 24-72 hours to process and ship orders, but shipping times will vary depending on the brand. The delivery will be sent in accordance to the rules of the site.

How long does drop take to ship in stock?

Come to find out shipping will take 4-8 weeks for an item listed as "in stock".

How long does Massdrop take to process orders?

Orders are packaged and shipped usually within 3 business days, shipping times will vary based on the shipping method chosen.

How long does drop take to deliver to Canada?

Canada: 1 to 3 weeks from the date of shipping. Australia, New Zealand, Germany, France, and the UK: 2 to 4 weeks from the date of shipping. Other countries not listed above: 3 to 6 weeks from the date of shipping.


3 Answers

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.

like image 183
Francis Gagné Avatar answered Oct 19 '22 18:10

Francis Gagné


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
like image 39
Matthieu M. Avatar answered Oct 19 '22 19:10

Matthieu M.


I still needed clarification despite the good answers above. This is what I got...

The Drop trait,

pub trait Drop {
    pub fn drop(&mut self);
}

requires a mutable reference instead of a move because the drop function is not intended to release memory but merely perform preparatory work prior to that release. Consequently, a better name for that drop method is perhaps, prepare_to_drop.

The actual memory release is performed by a function in the std::mem module that actually takes a self as is defined as follows:

pub fn drop<T>(_x: T) { }

This function is plainly using the compiler lifetime management semantics, as expected, in taking ownership of T and discarding it implicitly. A more revealing encoding of this particular function sheds light on the compiler's gymnastics, if we presume the compiler implicitly derives an empty Drop trait for all types that do not implement their own...

//Note: this is a bare function (no Self parameter)

pub fn drop<T: Drop>(x: T) { 
    x.drop();    //prepare_to_drop
}                //release memory since argument is owned
                 //  and scope is at end.

For completeness, the reason the Drop trait requires a mutable reference, &mut T, instead of a shared reference, &T, is that the preparatory work may include altering the contents of T such as reassigning owned values or references by swapping out the value with a default value (see Option.take), since mutations aren't permitted on shared references.

like image 7
George Avatar answered Oct 19 '22 18:10

George