我有一个代表状态机的 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
还有其他解决方法。例如,如果其类型有这样的值,则可以将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()
。
我最终使用了罗德里戈答案的变体。我不愿意在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())
}
}
}