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?
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With