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



我有一个代表状态机的 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())
}
}
}

最新更新