如何在不删除对象的情况下解构对象?
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 }
注意:这也将防止虚拟 a
、b
和 c
被删除;根据 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
}
这避免了不安全,并确保 a
、b
和 c
保持在一个组中,并且将一起删除。
我有一个结构,我想按值获取、变异然后 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 }
注意:这也将防止虚拟 a
、b
和 c
被删除;根据 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
}
这避免了不安全,并确保 a
、b
和 c
保持在一个组中,并且将一起删除。