我具有使用方法B
实现特征Trait
的结构do_something
。如果尚未调用struct B
,则需要执行一些其他操作。具体来说,如果从未调用过do_something
,则应该用Vec<A>
填充A::None
:
enum A {
V1,
V2,
None,
}
struct B {
data: Option<(A, Vec<A>)>,
}
trait Trait {
fn do_something(self) -> Vec<A>;
}
impl Trait for B {
fn do_something(mut self) -> Vec<A> {
let (a, mut vec) = self.data.take().unwrap();
vec.push(a);
vec
}
}
impl Drop for B {
fn drop(&mut self) {
match self.data.take() {
Some((a, mut vec)) => vec.push(A::None),
_ => {}
}
}
}
这在逻辑上是不必要的match
检查。我想避免它们,并提出以下解决方案:
struct B {
data: (A, Vec<A>),
}
trait Trait {
fn do_something(self) -> Vec<A>;
}
impl Trait for B {
fn do_something(mut self) -> Vec<A> {
let (a, mut vec) = std::mem::replace(&mut self.data, unsafe {
std::mem::MaybeUninit::<(A, Vec<A>)>::uninit().assume_init()
});
std::mem::forget(self);
vec.push(a);
vec
}
}
impl Drop for B {
fn drop(&mut self) {
self.data.1.push(A::None)
}
}
unsafe
解决方案正确吗?它是否包含未定义的行为?unsafe
或在B.data
中包装Option
来实现上述行为?这不仅将值移出结构,还将未初始化的存储器in]写入结构。编译器警告这将导致未定义的行为:
warning: the type `(A, std::vec::Vec<A>)` does not permit being left uninitialized --> src/main.rs:21:22 | 21 | unsafe { std::mem::MaybeUninit::<(A, Vec<A>)>::uninit().assume_init() }, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | this code causes undefined behavior when executed | help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done | = note: `#[warn(invalid_value)]` on by default note: std::ptr::Unique<A> must be non-null (in this struct field)
更好的解决方案是使用
mem::transmute()
:
impl Trait for B { fn do_something(self) -> Vec<A> { let (a, mut vec): (A, Vec<A>) = unsafe { std::mem::transmute(self) }; vec.push(a); vec } }
注意,这会阻止调用
Drop
。如果Drop
实现释放了内存或其他资源,则必须手动执行此操作。