Search code examples
rustdestructormove-semantics

How do I move out of a struct that has a destructor, if I don't want to run the destructor?


Suppose I have a Rust struct that has a destructor:

pub struct D1 {
    a: String,
    b: String
}

impl Drop for D1 {
    fn drop(&mut self) {
        println!("{0}", self.a)
    }
}

I want to implement a method that moves some or all of the fields out of the object, destroying it in the process. Obviously, that can't be done safely if the object's destructor gets run – it would attempt to unsafely read from the moved fields. However, it should be safe if the destructor is suppressed somehow. But I can't find a way to suppress the destructor in a way that makes the move safe.

mem::forget doesn't work, because the compiler doesn't treat it specially when it comes to destructors:

impl D1 {
    fn into_raw_parts(self) -> (String, String) {
        let (a, b) = (self.a, self.b); // cannot move here
        mem::forget(self);
        (a, b)
    }
}

and ManuallyDrop doesn't work either – it provides the interior data only via a reference, preventing a move:

use std::mem;

impl D1 {
    fn into_raw_parts(self) -> (String, String) {
        let md = mem::ManuallyDrop::new(self);
        (md.a, md.b) // cannot move out of dereference
    }
}
use std::mem;

impl D1 {
    fn into_raw_parts(self) -> (String, String) {
        let md = mem::ManuallyDrop::new(self);
        (md.value.a, md.value.b) // field `value` is private
    }
}

I could presumably solve this problem by dipping into unsafe code, but I'm wondering whether there's a safe way to do this – have I missed some API or other technique to tell Rust that I can safely move out of a value because I'm not planning to run the destructor?


Solution

  • There is no way in safe code to leave a type implementing Drop uninitialized. However, you can leave it initialized with dummy values:

    use std::mem;
    
    impl D1 {
        fn into_raw_parts(self) -> (String, String) {
            (mem::take(&mut self.a), mem::take(&mut self.b))
        }
    }
    

    This will leave self with empty strings (which do not even allocate any memory). Of course, the Drop implementation has to tolerate this and not do something undesired.

    If the fields’ types in your real situation do not have convenient placeholder values, then you can make the fields be Options. This is also a common technique when Drop::drop() itself needs to move out of self.