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

标签 rust ownership

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

基本上,我想消除这段代码中对 bravo.clone() 的调用。当原始数据将被删除时,不得不克隆该数据是令人失望的。我宁愿做的是 bravo: *bravo — 将 bravo 的旧值移出 State1 并移入状态 2。但我不能直接这样做,因为这会在构造 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还有其他解决方法。例如,您可以移出 bravo来自 self如果它的类型有这样的值,则用虚拟值替换它,然后轻松构建新状态。也许是这样的:

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() .

关于rust - 如何在枚举项之间移动非复制数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57614924/

相关文章:

syntax - 是否有在相似结构之间移动字段的语法?

sql - 如何对编译时不知道的类型使用 rusqlite 的 Row::get 方法?

rust - 为什么 Tokio 的 Runtime::block_on_all 需要一个具有“静态生命周期”的 future ?

rust - 结构所有权

rust - 为什么 `a` 在 Rust 中保留 `let b = &*&a;` 之后的所有权?

rust - 如果不实现 Copy 或 Clone 特征,重用借用的结构字段的最佳方法是什么?

multidimensional-array - Rust:切片上的ndarray点乘积的 “multiple applicable items in scope”错误

mysql - 如何在 sqlx 中构建和提交多查询事务?

rust - 改变循环中的两个相关值

string - 如何使用另一个 HashSet<String> 扩展 HashSet<String>?