48

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

yonran
  • 18,156
  • 8
  • 72
  • 97

2 Answers2

20

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.

llogiq
  • 13,815
  • 8
  • 40
  • 72
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • 24
    Sure, but the compiler could easily special case this case. – Lucretiel Feb 09 '20 at 20:59
  • 8
    This answer begs the question. The only reason `std::mem::drop` exists at all is so that the caller is forced to give up ownership before `drop` is called. If `drop` accepted `self` directly there would be no need for `std::mem::drop`. – GManNickG Dec 30 '20 at 23:50
  • @GManNickG: there is *absolutely nothing* special about `std::mem::drop`. The only reason it exists at all is for *convenience*, because occasionally you need to drop something (e.g. a lock guard), and `drop(guard);` is prettier than `let _x = guard;`. That `std::ops::Drop::drop` and `std::mem::drop` have the same name is essentially coincidence. – Chris Morgan Mar 11 '23 at 16:14
8

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
Peter Varo
  • 11,726
  • 7
  • 55
  • 77
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 5
    Sorry, I wasn’t clear. I do not intend to manually delete each field. My intention is to call functions that consume some of my fields so that I can wait on a thread or log any errors (I admit that I shouldn’t have brought up `std::mem::drop` which doesn’t tell you any errors that happened). – yonran Jun 18 '15 at 06:32
  • 1
    @yonran: Well, asking for a rationale as you did is a valid question so I am reluctant to tell you to edit it and completely change it, instead I would advise you to simply ask another question on how to do what you want to do, preferably with a MCVE on [the playpen](https://play.rust-lang.org/) that we can tinker with to make sure our solution does compile. – Matthieu M. Jun 18 '15 at 06:40
  • 1
    @yonran: Did you ever ask another question? I'm having exactly the same problem as you; specifically with using [`WavWriter::finalize()`](https://docs.rs/hound/3.0.0/hound/struct.WavWriter.html#method.finalize) in a `Drop()`. – Timmmm Dec 26 '16 at 21:30
  • 1
    @Timmmm, sorry, no I didn’t. I think I just used an `Option` in order to consume it during `drop`. – yonran Dec 26 '16 at 23:11