绕过“摆脱借来的自我”的优选模式检查器 [英] Preferable pattern for getting around the "moving out of borrowed self" checker

查看:96
本文介绍了绕过“摆脱借来的自我”的优选模式检查器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下模式:在该模式中,有多个状态已向调度程序注册,并且每个状态在接收到适当的事件时都知道要转换为哪个状态。这是一个简单的状态转换模式。

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:


  1. 像我一样将其放入 Option 中:这感觉很棘手,每当我需要
    时检查选项始终为某些的不变量,否则
    紧急! / code>或使用使其成为NOP,如果让Some()= 并忽略
    else子句,但这会导致代码膨胀。如果让Some()
    感觉有点不舒服,请执行展开
    或使用膨胀代码。

  2. 将其放入共享所有权 Rc :需要将所有此类变量或构造堆分配给
    另一个名为 Inner
    的结构具有所有这些不可克隆的类型,并将其放入
    Rc

  3. 将内容传递回 Dispatcher ,表明它基本上可以将我们的$ b $删除b从地图上移开,然后将东西移出下一个 State ,其中
    也将通过我们的返回值表示:耦合过多,
    破坏了OOP,无法扩展,因为 Dispatcher 需要了解所有
    State s并且需要经常更新。我认为这不是一个很好的
    范例,但可能是错误的。

  4. 实施默认适用于以上MOF:现在,我们可以使用默认值
    mem :: replace ,同时移出旧值。恐慌或
    返回错误或执行NOP的负担现在隐藏在
    MOF 的实现中。这里的问题是我们并不总是可以访问MOF
    类型,而对于我们所做的操作,这又再次将
    从用户代码膨胀到MOF代码。

  5. 让函数 handle_event 通过移动 fn handle_event来获取 self (mut self,...)->选项< Self> :现在,您需要具有 Box< State>而不是 Rc< RefCell<>> ; 并每次将其移出调度程序,如果返回值为 Some ,则将其放回去。这几乎感觉像是一把大锤,使许多其他成语变得不可能,例如,如果我想在一些注册的关闭/回调中进一步分享自我,我通常会把 Weak< RefCell<> 以前但现在无法在回调等中共享self。

  1. Put it into an Option as I have done: this feels hacky and every time I need to check the invariant that the Option is always Some otherwise panic! or make it a NOP with if let Some() = and ignore the else clause, but this causes code-bloat. Doing an unwrap or bloating the code with if let Some() feels a bit off.
  2. Get it into a shared ownership Rc<RefCell<>>: Need to heap allocate all such variables or construct another struct called Inner or something that has all these non-clonable types and put that into an Rc<RefCell<>>.
  3. Pass stuff back to Dispatcher indicating it to basically remove us from the map and then move things out of us to the next State which will also be indicated via our return value: Too much coupling, breaks OOP, does not scale as Dispatcher needs to know about all the States and needs frequent updating. I don't think this is a good paradigm, but could be wrong.
  4. Implement Default for MOF above: Now we can mem::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 of MOF. 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.
  5. Let the function handle_event take self by move as fn handle_event(mut self, ...) -> Option<Self>: Now instead of Rc<RefCell<>> you will need to have Box<State> and move it out each time in the dispatcher and if the return is Some 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 a Weak<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?

推荐答案



  1. 让handle_event函数以 fn handle_event(mut self,... )->选项< Self> :现在,您需要具有 Box< State>而不是 Rc< RefCell<>> ; 并每次将其移出调度程序,如果返回为Some,则将其放回去。

  1. Let the function handle_event take self by move as fn handle_event(mut self, ...) -> Option<Self>: Now instead of Rc<RefCell<>> you will need to have Box<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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆