简体   繁体   English

如何在将字段移动到新变体时更改枚举变体?

[英]How can I change enum variant while moving the field to the new variant?

I want to update an enum variant while moving a field of the old variant to the new one without any cloning:我想更新枚举变体,同时将旧变体的字段移动到新变体而不进行任何克隆:

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),
    }
}

This doesn't work because we can't move s from &mut X :这不起作用,因为我们不能从&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

Please don't suggest things like implementing an enum X { X1, X2 } and using struct S { variant: X, str: String } etc. This is a simplified example, imagine having lots of other fields in variants, and wanting to move one field from one variant to another.请不要建议诸如实现enum X { X1, X2 }和使用struct S { variant: X, str: String }之类的东西。这是一个简化的示例,想象一下变体中有很多其他字段,并且想要移动一个字段从一个变体到另一个。

This doesn't work because we can't move s from &mut X .这不起作用,因为我们不能从&mut X移动s

Then don't do that... take the struct by value and return a new one:然后不要这样做......按值获取结构并返回一个新的:

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),
    }
}

Ultimately, the compiler is protecting you because if you could move the string out of the enumeration, then it would be in some half-constructed state.最终,编译器会保护您,因为如果您可以将字符串移出枚举,那么它将处于某种半构造状态。 Who would be responsible for freeing the string if the function were to panic at that exact moment?如果函数在那个时刻发生恐慌,谁来负责释放字符串? Should it free the string in the enum or the string in the local variable?它应该释放枚举中的字符串还是局部变量中的字符串? It can't be both as a double-free is a memory-safety issue.它不能两者兼而有之,因为双重释放是一个内存安全问题。

If you had to implement it on a mutable reference, you could store a dummy value in there temporarily:如果必须在可变引用上实现它,则可以在其中临时存储一个虚拟值:

use std::mem;

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

Creating an empty String isn't too bad (it's just a few pointers, no heap allocation), but it's not always possible.创建一个空String还不错(它只是几个指针,没有堆分配),但这并不总是可行的。 In that case, you can use Option :在这种情况下,您可以使用Option

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

See also:也可以看看:

If you want to do this without moving out of the value in a zero-cost way, you have to resort to a bit of unsafe code (AFAIK):如果你想在不以零成本的方式移出价值的情况下做到这一点,你必须求助于一些不安全的代码(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
}

In some particular cases, what you want is in fact std::rc在某些特定情况下,您想要的实际上是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())},
    }
}

As @VladFrolov has commented, there was an RFC proposed that would've added a method to the standard library, std::mem::replace_with , that would allow you to temporarily take ownership of the value behind a mutable reference.正如@VladFrolov 评论的那样,有一个 RFC 提议向标准库添加一个方法, std::mem::replace_with ,这将允许您暂时获得可变引用背后的值的所有权。 However, it was not accepted.然而,它没有被接受。

There are third-party crates that provide similar functionality: take-mut and replace-with being notable ones I'm aware of.有提供类似功能的第三方 crate: take-mutreplace-with是我所知道的著名的。 By reading the documentation though, you might be able to see why this was not accepted into the standard library.不过,通过阅读文档,您可能会明白为什么标准库不接受它。 There are potentially dire consequences of aborting the program if the function that was given ownership panics, since it is required to put some value back into the mutable reference before unwinding can continue.如果被授予所有权的 function 出现恐慌,则中止该程序可能会产生可怕的后果,因为在继续展开之前需要将一些值放回可变引用中。 Mechanisms other than panicking are available to "put back" a value.除了恐慌之外,还有其他机制可以“放回”一个值。

Here is an example using replace-with:下面是一个使用 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),
    });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM