4

There are some similar questions, but the answers require the field to implement Default or some way to initialize another value with the type of the field.


We have a Node which has a value of type T:

struct Node<T> {
    value: T,
    next: Option<Box<T>>
}

It has a method which moves value from a Node:

impl<T> Node<T> {
    fn value(self) -> T {
        self.value
    }
}

The code above compiles. But if we implement Drop for Node:

impl<T> Drop for Node<T> {
    fn drop(&mut self) {}
}

Then we will get a compile error:

error[E0509]: cannot move out of type `Node<T>`, which implements the `Drop` trait
   |         self.value
   |         ^^^^^^^^^^
   |         |
   |         cannot move out of here
   |         move occurs because `self.value` has type `T`, which does not implement the `Copy` trait

I guess it doesn't compile because if we implement a custom Drop, we need to make sure not to drop value field if the drop happens at the end of value method's block. However, we cannot check that; and even if we could, the compiler cannot statically check that we do it.

One way to workaround this is to store value field as Option<T>. But suppose that we don't want to use Option for some reasons (the overhead, etc),

What else can we do to have both a custom Drop and a value method that moves value field?

I guess we have to use some unsafe approach and that's fine.


Rust Playground

Daniel
  • 1,358
  • 11
  • 15
  • Does this answer your question? [How to move one field out of a struct that implements Drop trait?](https://stackoverflow.com/questions/31307680/how-to-move-one-field-out-of-a-struct-that-implements-drop-trait) – Stargateur Jul 18 '20 at 16:09
  • That not because the answer doesn't fit you that make your question not a duplicate. – Stargateur Jul 18 '20 at 16:10
  • @Stargateur That question I have mentioned at the top of the question and I have explained why it isn't applicable here. – Daniel Jul 18 '20 at 16:17
  • Again, the question is totally a duplicate, answer that require a default value are irrelevant. There is no way to trick the compiler to do what you want no matter the amount of unsafe you will use. Either use Option, use default, Clone the value, not implement Drop. – Stargateur Jul 18 '20 at 16:20
  • That's not irrelevant. I also think there _might_ be no way to do it _safely_, but I might be wrong (i.e. there actually is), hence the question. And it's also okay if the approach is unsafe. – Daniel Jul 18 '20 at 16:26
  • See https://internals.rust-lang.org/t/destructuring-structs-which-implement-drop/137 – Solomon Ucko Jul 19 '20 at 03:18

1 Answers1

1

I don't know of a way to do this without using unsafe (though someone else might), but here is a way to do it using unsafe:

use std::{ptr, mem};

impl<T> Node<T> {
    fn value(mut self) -> T {
        unsafe {
            let v: T = ptr::read(&self.value);
            ptr::drop_in_place(&mut self.next);
            mem::forget(self);
            v
        }
    }
}

We use ptr::read to move the desired value out. We then need to use mem::forget on the Node to make sure its drop method is not called (since otherwise value could be dropped twice and cause undefined behavior). To prevent the next member from leaking, we use ptr::drop_in_place to run its drop method.

It's interesting to me that this safe code does not work:

impl<T> Node<T> {
    fn value(self) -> T {
        match self {
            Node {value, next: _} => value
        }
    }
}

It gives that same error:

error[E0509]: cannot move out of type Node<T>, which implements the Drop trait

I would have expected that with the match expression taking ownership of all of self and breaking it into its components, there would be no way for drop to be called on self and hence no reason for the compiler to complain. But apparently it does not work this way.

Brent Kerby
  • 1,397
  • 8
  • 14