你是否可以在不加额外约束和不使用不安全代码的情况下,切换可变引用(&mut E<T>
)中变量的变体?
也就是说,给定一个枚举:
enum E<T> {
VariantA(T),
VariantB(T)
}
这应该怎么写才是正确的呢:?
let x: E<???> = E::VariantA(??);
change_to_variant_b(&mut x);
assert_eq!(x, E::VariantB(??));
我会大胆猜测不行。
只需要对签名进行小幅度的更改,就可以实现。
fn change_to_variant_b<T>(e: E<T>) -> E<T> {
match e {
E::VariantA(t) => E::VariantB(t),
E::VariantB(t) => E::VariantB(t),
}
}
可以使用unsafe
来实现:
fn change_to_variant_b<T>(e: &mut E<T>) {
use std::ptr;
unsafe {
match ptr::read(e as *const _) {
E::VariantA(t) => ptr::write(e as *mut _, E::VariantB(t)),
E::VariantB(t) => ptr::write(e as *mut _, E::VariantB(t)),
}
}
}
通过使用额外的范围(Default
或Clone
),这是可能的:
fn change_to_variant_b<T: Default>(e: &mut E<T>) {
match std::mem::replace(e, E::VariantA(T::default())) {
E::VariantA(t) => e = E::VariantB(t),
E::VariantB(t) => e = E::VariantB(t),
}
}
&mut E<T>
,因为我正在可变地迭代一个切片,所以我不能(?)暂时将元素移出它以使第一种解决方案起作用。 - Doeenum
中,(2) 被困在函数签名中,(3) 没有不安全或无边界......那么你就无法解决它。 - Matthieu M.std::mem::replace
? - kalkronline作为这种情况的替代方案,您可以考虑改为使用一个具有 T
和普通枚举的结构体:
struct Outer<T> {
val: T,
kind: Inner,
}
impl<T> Outer<T> {
fn promote_to_b(&mut self) {
self.kind.promote_to_b()
}
}
enum Inner {
VariantA,
VariantB,
}
impl Inner {
fn promote_to_b(&mut self) {
if let Inner::VariantA = *self {
*self = Inner::VariantB;
}
}
}
可否在可变引用的值中切换变体
正如Matthieu M.所说,一般来说是“不行”的。原因是这样做会让枚举处于不确定状态,从而允许访问未定义的内存,从而破坏Rust的安全保证。例如,假设此代码编译无误:
impl<T> E<T> {
fn promote_to_b(&mut self) {
if let E::VariantA(val) = *self {
// Things happen
*self = E::VariantB(val);
}
}
}
self
移动到val
中,应该怎样处理代表self
内T
的内存?val
和self
内部的T
的析构函数都会运行,但由于它们指向相同的数据,这将导致双重释放。val
,这将有一些用处。
按值传递的解决方案可行,因为编译器可以跟踪谁应该调用析构函数:即函数本身。一旦进入函数,编译器就知道哪些特定行可能需要释放该值,并在出现紧急情况时正确地调用它们。
Clone
或Default
解决方案可行,因为您从未将值移出原始枚举。相反,您可以用虚拟值替换原始枚举并获取原始枚举的所有权(使用Default
),或复制整个原始值(使用Clone
)。
replace_with
RFC (#1736)提议添加一种方法,使其能够在确保正确的内存语义的同时工作,但是该RFC未被接受。
replace_with
RFC(#1736)所解决的问题。 - kennytmT
和普通enum
的struct
在你的情况下是否可行? - Chris EmersonT
的变量,并容忍代码中的 "不可能" 路径,我已经在这里编写了一个安全、稳定的change_to_variant_b
版本(链接在此:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f731602e09c7f8be91fd4a21d8c0e7ca),使用了std::mem::replace
。 - Ryan1729