绕过“摆脱借来的自我”的优选模式检查器 [英] Preferable pattern for getting around the "moving out of borrowed self" checker
问题描述
请考虑以下模式:在该模式中,有多个状态已向调度程序注册,并且每个状态在接收到适当的事件时都知道要转换为哪个状态。这是一个简单的状态转换模式。
Consider the pattern where there are several states registered with a dispatcher and each state knows what state to transition to when it receives an appropriate event. This is a simple state transition pattern.
struct Dispatcher {
states: HashMap<Uid, Rc<RefCell<State>>>,
}
impl Dispatcher {
pub fn insert_state(&mut self, state_id: Uid, state: Rc<RefCell<State>>) -> Option<Rc<RefCell<State>>> {
self.states.insert(state_id, state)
}
fn dispatch(&mut self, state_id: Uid, event: Event) {
if let Some(mut state) = states.get_mut(&state_id).cloned() {
state.handle_event(self, event);
}
}
}
trait State {
fn handle_event(&mut self, &mut Dispatcher, Event);
}
struct S0 {
state_id: Uid,
move_only_field: Option<MOF>,
// This is pattern that concerns me.
}
impl State for S0 {
fn handle_event(&mut self, dispatcher: &mut Dispatcher, event: Event) {
if event == Event::SomeEvent {
// Do some work
if let Some(mof) = self.mof.take() {
let next_state = Rc::new(RefCell::new(S0 {
state_id: self.state_id,
move_only_field: mof,
}));
let _ = dispatcher.insert(self.state_id, next_state);
} else {
// log an error: BUGGY Logic somewhere
let _ = dispatcher.remove_state(&self.state_id);
}
} else {
// Do some other work, maybe transition to State S2 etc.
}
}
}
struct S1 {
state_id: Uid,
move_only_field: MOF,
}
impl State for S1 {
fn handle_event(&mut self, dispatcher: &mut Dispatcher, event: Event) {
// Do some work, maybe transition to State S2/S3/S4 etc.
}
}
参考上面的内联注释:
// This is pattern that concerns me.
S0 :: move_only_field
Option
之所以如此,是因为 self
是在 handle_event
中借用的,
S0::move_only_field
needs to be an Option
in this pattern because self
is borrowed in handle_event
, but I am not sure that this is best way to approach it.
以下是我可以考虑每个缺点的以下方法:
Here are the ways I can think of with demerits of each one:
- 像我一样将其放入
Option
中:这感觉很棘手,每当我需要
时检查选项
始终为某些
的不变量,否则
紧急! / code>或使用
感觉有点不舒服,请执行使其成为NOP,如果让Some()=
并忽略
else子句,但这会导致代码膨胀。如果让Some()展开
或使用膨胀代码。
- 将其放入共享所有权
Rc
:需要将所有此类变量或构造堆分配给
另一个名为Inner
或
的结构具有所有这些不可克隆的类型,并将其放入
Rc
。 - 将内容传递回
Dispatcher
,表明它基本上可以将我们的$ b $删除b从地图上移开,然后将东西移出下一个State
,其中
也将通过我们的返回值表示:耦合过多,
破坏了OOP,无法扩展,因为Dispatcher
需要了解所有
State
s并且需要经常更新。我认为这不是一个很好的
范例,但可能是错误的。 - 实施
默认
适用于以上MOF:现在,我们可以使用默认值
mem :: replace
,同时移出旧值。恐慌或
返回错误或执行NOP的负担现在隐藏在
MOF
的实现中。这里的问题是我们并不总是可以访问MOF
类型,而对于我们所做的操作,这又再次将
从用户代码膨胀到MOF代码。 - 让函数
handle_event
通过移动fn handle_event来获取
:现在,您需要具有self
(mut self,...)->选项< Self>Box< State>而不是
并每次将其移出调度程序,如果返回值为Rc< RefCell<>>
;Some
,则将其放回去。这几乎感觉像是一把大锤,使许多其他成语变得不可能,例如,如果我想在一些注册的关闭/回调中进一步分享自我,我通常会把Weak< RefCell<>
以前但现在无法在回调等中共享self。
- Put it into an
Option
as I have done: this feels hacky and every time I need to check the invariant that theOption
is alwaysSome
otherwisepanic!
or make it a NOP withif let Some() =
and ignore the else clause, but this causes code-bloat. Doing anunwrap
or bloating the code withif let Some()
feels a bit off. - Get it into a shared ownership
Rc<RefCell<>>
: Need to heap allocate all such variables or construct another struct calledInner
or something that has all these non-clonable types and put that into anRc<RefCell<>>
. - Pass stuff back to
Dispatcher
indicating it to basically remove us from the map and then move things out of us to the nextState
which will also be indicated via our return value: Too much coupling, breaks OOP, does not scale asDispatcher
needs to know about all theState
s and needs frequent updating. I don't think this is a good paradigm, but could be wrong. - Implement
Default
for MOF above: Now we canmem::replace
it with the default while moving out the old value. The burden of panicking OR returning an error OR doing a NOP is now hidden in implementation ofMOF
. The problem here is we don't always have the access to MOF type and for those that we do, it again takes the point of bloat from user code to the code of MOF. - Let the function
handle_event
takeself
by move asfn handle_event(mut self, ...) -> Option<Self>
: Now instead ofRc<RefCell<>>
you will need to haveBox<State>
and move it out each time in the dispatcher and if the return isSome
you put it back. This almost feels like a sledgehammer and makes many other idioms impossible, for instance if I wanted to share self further in some registered closure/callback I would normally put aWeak<RefCell<>>
previously but now sharing self in callbacks etc is impossible.
还有其他选择吗?在Rust中,是否有任何被认为是 最惯用 的方式?
Are there any other options? Is there any that is considered the "most idiomatic" way of doing this in Rust?
推荐答案
- 让handle_event函数以
fn handle_event(mut self,... )->选项< Self>
:现在,您需要具有Box< State>而不是
并每次将其移出调度程序,如果返回为Some,则将其放回去。Rc< RefCell<>>
;
- Let the function handle_event take self by move as
fn handle_event(mut self, ...) -> Option<Self>
: Now instead ofRc<RefCell<>>
you will need to haveBox<State>
and move it out each time in the dispatcher and if the return is Some you put it back.
这就是我要做的。但是,如果只有一个强引用,则无需从 Rc
切换到 Box
= https://doc.rust-lang.org/std/rc/struct.Rc.html#method.try_unwrap rel = nofollow noreferrer> Rc :: try_unwrap
可以移出 Rc
。
This is what I would do. However, you don't need to switch from Rc
to Box
if there is only one strong reference: Rc::try_unwrap
can move out of an Rc
.
这是重写<$的部分方法c $ c> Dispatcher :
struct Dispatcher {
states: HashMap<Uid, Rc<State>>,
}
impl Dispatcher {
fn dispatch(&mut self, state_id: Uid, event: Event) {
if let Some(state_ref) = self.states.remove(&state_id) {
let state = state_ref.try_unwrap()
.expect("Unique strong reference required");
if let Some(next_state) = state.handle_event(event) {
self.states.insert(state_id, next_state);
}
} else {
// handle state_id not found
}
}
}
(注意:调度
取值 state_id
在原始版本中,这不是必需的-可以将其更改为通过引用传递。在此版本中,由于 state_id
被传递给 HashMap :: insert
。看来 Uid
是 Copy
,所以差别不大。)
(Note: dispatch
takes state_id
by value. In the original version, this wasn't necessary -- it could have been changed to pass by reference. In this version, it is necessary, since state_id
gets passed to HashMap::insert
. It looks like Uid
is Copy
though, so it makes little difference.)
目前尚不清楚 state_id
是否确实需要成为该结构的成员不再实现 State
,因为您不需要在 handle_event
中使用它-所有插入和删除操作都在 impl Dispatcher
,这很有意义,并减少了 State
和 Dispatcher
。
It's not clear whether state_id
actually needs to be a member of the struct that implements State
anymore, since you don't need it inside handle_event
-- all the insertion and removal happens inside impl Dispatcher
, which makes sense and reduces coupling between State
and Dispatcher
.
impl State for S0 {
fn handle_event(self, event: Event) -> Option<Rc<State>> {
if event == Event::SomeEvent {
// Do some work
let next_state = Rc::new(S0 {
state_id: self.state_id,
move_only_field: self.mof,
});
Some(next_state)
} else {
// Do some other work
}
}
}
现在,您不必处理选项为无的怪异,应为不可能的极端情况。
Now you don't have to handle a weird, should-be-impossible corner case where the Option is None.
这几乎感觉像是一把大锤,使许多其他成语变得不可能,例如,如果我想在一些已注册的闭包/回调中进一步分享自我,我通常会放一个
弱的RefCell<>
,但现在无法在回调等中共享自我。
This almost feels like a sledgehammer and makes many other idioms impossible, for instance if I wanted to share self further in some registered closure/callback I would normally put a
Weak<RefCell<>>
previously but now sharing self in callbacks etc is impossible.
如果您只有唯一的参考文献,则可以退出 Rc
,因此不必牺牲此技巧。
Because you can move out of an Rc
if you have the only strong reference, you don't have to sacrifice this technique.
感觉就像大锤可能是主观的,但是对我来说,像 fn handle_event(mut self,...)->这样的签名是什么?选项< Self>
确实编码了一个不变式。在原始版本中,每个 impl State ...
都必须知道何时将自身插入调度程序中以及从调度程序中删除自身,以及是否无法检查。例如,如果您在逻辑的深处忘记了调用 dispatcher.insert(state_id,next_state)
,状态机将不会过渡,并且可能陷入困境甚至更糟。当 handle_event
接受自己
的值时,这不再可能-您已经返回下一个状态,否则代码将根本无法编译。
"Feels like a sledgehammer" might be subjective, but to me, what a signature like fn handle_event(mut self, ...) -> Option<Self>
does is encode an invariant. With the original version, each impl State for ...
had to know when to insert and remove itself from the dispatcher, and whether it did or not was uncheckable. For example, if somewhere deep in the logic you forgot to call dispatcher.insert(state_id, next_state)
, the state machine wouldn't transition, and might get stuck or worse. When handle_event
takes self
by-value, that's not possible anymore -- you have to return the next state, or the code simply won't compile.
(此外:原始版本和我的代码每次<$ c $都至少进行两次哈希表查找c> dispatch 被调用:一次获取当前状态,再一次插入新状态,如果要摆脱第二次查找,可以组合以下方法:store HashMap
中的> Option< Rc< State>> ,然后从中获取
选项
,而不是将其从地图上完全删除。)
(Aside: both the original version and mine do at least two hashtable lookups each time dispatch
is called: once to get the current state, and again to insert the new state. If you wanted to get rid of the second lookup, you could combine approaches: store Option<Rc<State>>
in the HashMap
, and take
from the Option
instead of removing it from the map entirely.)
这篇关于绕过“摆脱借来的自我”的优选模式检查器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!