Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I destructure an object without dropping it?

I have a struct that I want to take by value, mutate and then return. I want to also mutate its generic type as I use this state for statically ensuring correct order of function calls for making safe FFI (playground):

use core::marker::PhantomData;

struct State1 {}
struct State2 {}
struct Whatever {}

struct X<State> {
    a: Whatever,
    b: Whatever,
    c: Whatever,
    _d: PhantomData<State>,
}

impl<State> Drop for X<State> {
    fn drop(&mut self) {}
}

fn f(x: X<State1>) -> X<State2> {
    let X { a, b, c, _d } = x;
    //mutate a, b and c
    X {
        a,
        b,
        c,
        _d: PhantomData,
    } // return new instance
}

Because X implements Drop, I get:

error[E0509]: cannot move out of type `X<State1>`, which implements the `Drop` trait
  --> src/lib.rs:19:29
   |
19 |     let X { a, b, c, _d } = x;
   |             -  -  -         ^ cannot move out of here
   |             |  |  |
   |             |  |  ...and here
   |             |  ...and here
   |             data moved here
   |
   = note: move occurs because these variables have types that don't implement the `Copy` trait

I don't want to drop anything as I am not destroying x, just repackaging it. What is the idiomatic way to prevent dropping x?

like image 441
alagris Avatar asked Jun 14 '26 12:06

alagris


1 Answers

Moving data out of the value would leave it in an undefined state. That means that when Drop::drop is automatically run by the compiler, you'd be creating undefined behavior.

Instead, we can use unsafe Rust to prevent automatic dropping of the value and then pull the fields out ourselves. Once we pull one field out via ptr::read, the original structure is only partially initialized, so I also use MaybeUninit:

fn f(x: X<State1>) -> X<State2> {
    use std::{mem::MaybeUninit, ptr};

    // We are going to uninitialize the value.
    let x = MaybeUninit::new(x);

    // Deliberately shadow the value so we can't even try to drop it.
    let x = x.as_ptr();

    // SAFETY[TODO]: Explain why it's safe for us to ignore the destructor.
    // I copied this from Stack Overflow and didn't even change the comment!
    unsafe {
        let a = ptr::read(&(*x).a);
        let b = ptr::read(&(*x).b);

        X {
            a,
            b,
            _s: PhantomData,
        }
    }
}

You do need to be careful that you get all of the fields out of x, otherwise you could cause a memory leak. However, since you are creating a new struct that needs the same fields, this is an unlikely failure mode in this case.

See also:

  • How to move one field out of a struct that implements Drop trait?
  • Can not move out of type which defines the `Drop` trait [E0509]
  • How can I move a value out of the argument to Drop::drop()?
  • Temporarily move out of borrowed content
like image 198
Shepmaster Avatar answered Jun 17 '26 11:06

Shepmaster