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

时间:2016-04-11 19:19:42

标签: rust

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

    String id = "HERE+IS+ID";   
    ODocument location = new ODocument("OPoint");
    location.field("coordinates", Arrays.asList(12.2323, 34.3233));       
    Vertex sink = graph.addVertex(id, Constants.NAME, "Sink-Root", Constants.LOCATION, location);

这不起作用,因为我们无法从enum X { X1(String), X2(String), } fn increment_x(x: &mut X) { match x { &mut X::X1(s) => { *x = X::X2(s); } &mut X::X2(s) => { *x = X::X1(s); } } } 移动s

请不要建议实施&mut X和使用enum X { X1, X2 }等内容。这是一个简化的示例,假设在变体中有很多其他字段,并且想要移动一个字段一种变体到另一种变体。

2 个答案:

答案 0 :(得分:11)

  

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

然后不要这样做......按值取结构并返回一个新结构:

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

答案 1 :(得分:4)

如果你想以零成本的方式移出价值,你就不得不诉诸一些不安全的代码(AFAIK):

#[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
}

编辑:感谢@Shepmaster考虑了替换...