Daniel
Daniel

Reputation: 1527

How to move a field whose type does not implement Default from a struct which implements Drop?

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

Upvotes: 4

Views: 425

Answers (1)

Brent Kerby
Brent Kerby

Reputation: 1447

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.

Upvotes: 1

Related Questions