rust 当我把字段移到新的变量时,我如何改变枚举变量?

8yoxcaq7  于 2023-01-13  发布在  其他
关注(0)|答案(4)|浏览(137)

我想更新一个枚举变量,同时将旧变量的一个字段移动到新变量,而不进行任何克隆:

enum X {
    X1(String),
    X2(String),
}

fn increment_x(x: &mut X) {
    *x = match *x {
        X::X1(s) => X::X2(s),
        X::X2(s) => X::X1(s),
    }
}

这不起作用,因为我们无法从&mut X移动s

error[E0507]: cannot move out of borrowed content
 --> src/lib.rs:7:16
  |
7 |     *x = match *x {
  |                ^^
  |                |
  |                cannot move out of borrowed content
  |                help: consider removing the `*`: `x`
8 |         X::X1(s) => X::X2(s),
  |               - data moved here
9 |         X::X2(s) => X::X1(s),
  |               - ...and here

请不要建议实现enum X { X1, X2 }和使用struct S { variant: X, str: String }等。这是一个简化的示例,想象一下在变体中有许多其他字段,并希望将一个字段从一个变体移动到另一个变体。

bfrts1fy

bfrts1fy1#

这不起作用,因为我们不能从&mut X移动s
那就不要这样做......用值来获取结构体,然后返回一个新的:

enum X {
    X1(String),
    X2(String),
}

fn increment_x(x: X) -> X {
    match x {
        X::X1(s) => X::X2(s),
        X::X2(s) => X::X1(s),
    }
}

最后,编译器会保护你,因为如果你 * 可以 * 把字符串移出枚举,那么它将处于某种半构造状态。如果函数在那一刻死机,谁来负责释放字符串?它应该释放枚举中的字符串还是局部变量中的字符串?它不能同时释放两个字符串,因为双释放是内存安全问题。
如果你必须在一个可变的引用上实现它,你可以在那里临时存储一个伪值:

use std::mem;

fn increment_x_inline(x: &mut X) {
    let old = mem::replace(x, X::X1(String::new()));
    *x = increment_x(old);
}

创建一个空的String并不太糟糕(它只有几个指针,没有堆分配),但它并不总是可行的,在这种情况下,可以使用Option

fn increment_x_inline(x: &mut Option<X>) {
    let old = x.take();
    *x = old.map(increment_x);
}

另见:

icomxhvb

icomxhvb2#

如果你想做到这一点,而不是以零成本的方式移出值,你必须求助于一点不安全的代码(AFAIK):

use std::mem;

#[derive(Debug)]
enum X {
    X1(String),
    X2(String),
}

fn increment_x(x: &mut X) {
    let interim = unsafe { mem::uninitialized() };
    let prev = mem::replace(x, interim);
    let next = match prev {
        X::X1(s) => X::X2(s),
        X::X2(s) => X::X1(s),
    };
    let interim = mem::replace(x, next);
    mem::forget(interim); // Important! interim was never initialized
}
u3r8eeie

u3r8eeie3#

在某些特定情况下,实际上需要的是std::rc

enum X {
    X1(Rc<String>),
    X2(Rc<String>),
}

fn increment_x(x: &mut X) -> X {
    match x {
        X::X1(s) => {x = X::X2(s.clone())},
        X::X2(s) => {x = X::X1(s.clone())},
    }
}
jrcvhitl

jrcvhitl4#

正如@VladFrolov所评论的,an RFC proposed会给标准库std::mem::replace_with添加一个方法,允许你临时拥有一个可变引用后面的值,但是它没有被接受。
有提供类似功能的第三方板条箱:take-mutreplace-with是我所知道的值得注意的两个函数。通过阅读文档,您可能会明白为什么标准库不接受这两个函数。如果被赋予所有权的函数出现异常,则可能会导致异常终止程序的严重后果。因为在展开继续之前,需要将 * 一些 * 值放回可变引用中。2除了恐慌之外,还有其他机制可以用来“放回”一个值。
下面是一个使用replace-with的示例:

enum X {
    X1(String),
    X2(String),
}

fn increment_x(x: &mut X) {
    replace_with::replace_with_or_abort(x, |x| match x {
        X::X1(s) => X::X2(s),
        X::X2(s) => X::X1(s),
    });
}

相关问题