如何在不删除对象的情况下解构对象?

How do I destructure an object without dropping it?

我有一个结构,我想按值获取、变异然后 return。我还想改变它的泛型类型,因为我使用这个状态静态地确保函数调用的正确顺序以制作安全的 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
}

因为 X 实现了 Drop,我得到:

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

我不想丢弃任何东西,因为我不会破坏 x,只是重新包装它。防止丢弃 x 的惯用方法是什么?

您可以避免 unsafe 代码,正如其他答案中所建议的那样,通过确保在移动时每个值都被替换为一个值,这样 x 永远不会留在无效的状态。

如果字段类型实现 Default 你可以使用 std::mem::take:

use std::mem;

fn f(mut x: X<State1>) -> X<State2> {
    let mut a = mem::take(&mut x.a);
    let mut b = mem::take(&mut x.b);
    let mut c = mem::take(&mut x.c);
    
    // mutate a, b and c
    // ...

    // return a new X    
    X { a, b, c, _d: PhantomData }
}

现在删除 x 是安全的,因为它包含每个字段的有效值。如果字段类型未实现 Default,那么您可以使用 std::mem::swap 将它们替换为合适的虚拟值。

将数据移出值会使它处于未定义状态。这意味着当 Drop::drop 被编译器自动 运行 时,您将创建未定义的行为。

相反,我们可以使用不安全的 Rust 来防止自动删除值,然后我们自己拉出字段。一旦我们通过 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,
        }
    }
}

您确实需要注意从 x 中获取所有字段,否则可能会导致内存泄漏。但是,由于您正在创建一个需要相同字段的新结构,因此在这种情况下这是不太可能的失败模式。

另请参阅:

  • Temporarily move out of borrowed content

您通过实现 Drop 与编译器创建的合同是您拥有 必须 运行 的代码 X被销毁,并且 X 必须完成 才能这样做。解构与该契约背道而驰。

您可以使用 ManuallyDrop to avoid Drop being called, but that doesn't necessarily help you destructure it, you'll still have to pull the fields out yourself. You can use std::mem::replace or std::mem::swap 将它们移出,留下虚拟值。

let mut x = ManuallyDrop::new(x);
let mut a = std::mem::replace(&mut x.a, Whatever {});
let mut b = std::mem::replace(&mut x.b, Whatever {});
let mut c = std::mem::replace(&mut x.c, Whatever {});

// mutate a, b, c

X { a, b, c, _d: PhantomData }

注意:这也将防止虚拟 abc 被删除;根据 Whatever,可能会导致问题或内存泄漏。因此,如果 unsafe 令人讨厌,我实际上会反对并使用 Peter Hall 的回答。


如果您真的想要相同的行为并避免创建虚拟值,您可以通过 std::ptr::read 使用 unsafe 代码将值移出,并保证不会访问原始值。

let x = ManuallyDrop::new(x);
let mut a = unsafe { std::ptr::read(&x.a) };
let mut b = unsafe { std::ptr::read(&x.b) };
let mut c = unsafe { std::ptr::read(&x.c) };
drop(x); // ensure x is no longer used beyond this point

// mutate a, b, c

X { a, b, c, _d: PhantomData }

另一个 unsafe 选项是使用 std::mem::transmute 直接从 X<State1> 转到 X<State2>

let mut x: X<State2> = unsafe { std::mem::transmute(x) };

// mutate x.a, x.b, x.c

x

如果状态类型实际上根本没有用于字段(意味着所有 X 确实相同),它 可能 安全,因为您也用 #[repr(C)] 修饰 X 以确保编译器不会移动字段。但我可能缺少一些其他保证,std::mem::transmute 非常 unsafe 并且容易出错。

您可以将状态跟踪 PhantomData 与可放置结构分开:

use core::marker::PhantomData;

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

struct Inner {
    a: Whatever,
    b: Whatever,
    c: Whatever,
}

struct X<State> {
    i: Inner,
    _d: PhantomData<State>,
}

impl Drop for Inner {
    fn drop(&mut self) {}
}

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

这避免了不安全,并确保 abc 保持在一个组中,并且将一起删除。