如何在枚举项之间移动非复制数据

How to move non-Copy data between enum items

我有一个代表状态机的 Rust 枚举。我需要在状态之间移动一些数据(数据未实现 Copy)。什么是好的使用方法?

基本上,我想消除这段代码中对 bravo.clone() 的调用。当原始数据将被删除时,不得不克隆该数据是令人失望的。我宁愿做的是按照 bravo: *bravo 的思路——将 bravo 的旧值从 State1 移到 State2 中。但我不能直接这样做,因为这会在构造 State2.

时短暂地使 self.state 的值无效
enum MyStateMachine {
    Idle,
    State1 {
        alpha: usize,
        bravo: String,
    },
    // State2 is a superset of State1
    State2 {
        alpha: usize,
        bravo: String,
        charlie: usize,
    },
}

impl MyStateMachine {
    fn to_state2(&mut self, charlie: usize) -> Result<(), String> {
        use MyStateMachine::*;

        match self {
            State1 { alpha, bravo } => {
                *self = State2 {
                    // Copy type moves between states OK
                    alpha: *alpha, 
                     // Non-copy types require a call to .clone()
                    bravo: bravo.clone(),
                    charlie,
                };
                Ok(())
            }
            _ => Err("Must be in State1".into())
        }
    }
}

你不能直接这样做,因为 Rust 确保 *self 每次都必须有效。这很好,因为如果您的程序在某处崩溃并且它必须调用 drop() 而您的 *self 不一致会发生什么?

幸运的是,您的对象有一个方便的 Idle 状态,可以用作中间值。最后的技巧在 std::mem::replace():

impl MyStateMachine {
    fn to_state2(&mut self, charlie: usize) -> Result<(), String> {
        use MyStateMachine::*;

        // take ownership of the old status, installing the dummy Idle
        let old = std::mem::replace(self, Idle);

        match old {
            State1 { alpha, bravo } => {
                //assign the final status
                *self = State2 {
                    alpha: alpha, 
                     // no clone!
                    bravo: bravo,
                    charlie,
                };
                Ok(())
            }
            _ => { 
                // restore old status before returning error
                std::mem::replace(self, old);
                Err("Must be in State1".into())
            }
        }
    }
}

如果您没有 Idle,还有其他解决方法。例如,您可以将 bravoself 移出,将其替换为虚拟值(如果其类型存在这样的值),然后轻松构建新状态。也许是这样的:

impl MyStateMachine {
    fn to_state2(&mut self, charlie: usize) -> Result<(), String> {
        use MyStateMachine::*;

        *self = match self {
            State1 { alpha, bravo } => {
                //steal the value replacing it with a dummy
                //remember that String::new() does not allocate memory
                let bravo = std::mem::replace(bravo, String::new());
                State2 {
                    alpha: *alpha, 
                     // no clone!
                    bravo,
                    charlie,
                }
            }
            _ =>  return Err("Must be in State1".into())
        };
        Ok(())
    }
}

如果您的 brave 类型没有合适的虚拟值,您也可以将其类型替换为 Option<_> 并使用 Option::take() 而不是 mem::replace() .

我最终使用了 的变体。我不愿意在 match 语句之前使用 mem::replace(),因为这会在验证可以更改状态之前推进状态。因此,我只在非复制类型上使用 mem::replace()

impl MyStateMachine {
    fn to_state2(&mut self, charlie: usize) -> Result<(), String> {
        use MyStateMachine::*;

        match self {
            State1 { alpha, bravo: old_bravo } => {
                // Use mem::replace on non-Copy types
                let bravo = std::mem::replace(old_bravo, Default::default());
                *self = State2 {
                    // Copy types can use a simple dereference
                    alpha: *alpha, 
                    bravo, 
                    charlie,
                };
                Ok(())
            }
            _ => return Err("Must be in State1".into())
        }
    }

}