可以移出字段,然后在安全rust中覆盖枚举?[复制]

eqoofvh9  于 11个月前  发布在  其他
关注(0)|答案(3)|浏览(133)

此问题在此处已有答案

Temporarily move out of borrowed content(3个答案)
11天前关闭
截至10天前,社区正在审查是否重新讨论这个问题。
我有一个函数,它接受一个有两个变量的枚举,并将其转换为一个特定的变量。我只能让它在不安全的ptr::{read,write}下编译,但感觉应该可以在安全的代码中完成-我无条件地分配给*self,所以它应该被找到以消耗(部分)当前值。
有没有办法在安全的Rust中编写这个函数?

error[E0507]: cannot move out of `*x` which is behind a mutable reference
 --> src/lib.rs:9:29
  |
9 |             *self = Self::B(*x);
  |                             ^^ move occurs because `*x` has type `T`, which does not implement the `Copy` trait

个字符
Playground

toe95027

toe950271#

问题是你不能“临时”移动&mut self中的T--如果后面的代码在移动一个值之前发生了恐慌怎么办?这就是为什么在安全的Rust中不可能以你描述的方式移动。
解决方案是在self中放入一个值作为临时值,这样&mut self就保持在一个定义良好的状态,同时获得所包含的T的所有权。如果T: Copy,如果T: Default,或者如果Foo有一个独立于T的廉价变体,这很容易实现。

#[derive(PartialEq, Debug)]
enum Foo<T> {
    A(T),
    B(T),
}

impl<T: Default> Foo<T> {
    fn ensure_b(&mut self) {
        // Use `std::mem::replace` to take ownership of self, putting a default
        // in its place. Then construct a new owned value (which is always of
        // the B-variant, and assign it to `self`. This is always safe, even
        // if we panic while constructing the new value for `self`.
        *self = match std::mem::replace(self, Foo::A(Default::default())) {
            Foo::A(t) | Foo::B(t) => Foo::B(t),
        }
    }
}

fn main() {
    let mut f = Foo::A("foo");
    f.ensure_b();
    assert_eq!(f, Foo::B("foo"));
}

字符串

56lgkhnf

56lgkhnf2#

如果Foo::AFoo::B在结构上是相同的,但只需要通过它们的标签来区分,那么标签可能应该完全是一个单独的枚举。

struct Foo {
  foo_type: FooType,
  inner: T
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum FooType {
  A,
  B,
}

字符串
现在不需要移动任何东西了,我们只需要更新标签,然后做其他内部逻辑需要做的事情。

fn ensure_b(&mut self) {
  if self.foo_type == FooType::A {
    self.foo_type = FooType::B;
    // Do whatever needs to be done to self.inner to "update".
  }
}

yshpjwxd

yshpjwxd3#

第三种选择是修改Foo<T>,以考虑在获取内部值和再次放入内部值之间的中间状态。

enum Foo<T> {
    A(T),
    B(T),
    Poisoned,
}

impl<T> Foo<T> {
    fn ensure_b(&mut self) {
        *self = match std::mem::replace(self, Self::Poisoned) {
            Self::A(x) | Self::B(x) => Self::B(x),
            Self::Poisoned => unreachable!(),
        }
    }
}

字符串
请注意,在这样一个小例子中,多一个变量可能会很不方便,因为你必须考虑其他地方的中毒情况。但是,如果你在一个更大的代码库中,很难确保在你获取值的那一刻和你把它放回去的那一刻之间没有任何事情发生。(特别是如果你使用unsafe Rust),特别是因为exception safety。如果你强迫每个人都处理出错的情况,突然之间你就不必再确保一切正常了,这是一个不太可能在未来产生bug的设计选择。
这种设计在实践中使用,特别是在并发数据结构中,其中推理两个连续指令之间应该发生什么和不应该发生什么特别困难。

相关问题