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).
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.
- 12,825
- 5
- 41
- 67
- 52,934
- 3
- 149
- 138
-
12Sure, but the compiler could easily special case this case. – Lucretiel Feb 09 '20 at 20:59
-
3This 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
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:
- Dropping each element held by the vector
- Deallocating the memory
- 11,000
- 7
- 50
- 69
- 268,556
- 42
- 412
- 681
-
2Sorry, 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
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 defined as follows:
pub fn drop<T>(_x: T) { }
It is plainly using the compiler lifetime management semantics, as expected in taking ownership of T. 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 taking content out of a field by swapping out its value with a default value (see Option.take), alterations not permissible on shared references.
- 1,796
- 22
- 32
-
1But then you can only move out by `std::mem::swap`, `Option::take()` or similar. You cannot directly move out (without replacing) from a `&mut` reference. – Pavel Šimerda May 26 '22 at 10:19