【问题标题】:Callback pattern in RustRust 中的回调模式
【发布时间】:2021-10-10 20:44:26
【问题描述】:

希望实现两种风格的回调接口。不可变(不能改变分配回调的结构)和可变(可以改变分配回调的结构)。

type Callback<'a> = dyn FnMut(&'a MyStruct<'a>);
type CallbackMut<'a> = dyn FnMut(&'a mut MyStruct<'a>);

struct MyStruct<'a> {
    callback: &'a Callback<'a>,
    callback_mut: &'a CallbackMut<'a>
}

impl<'a> MyStruct<'a> {
    pub fn new(callback: &'a Callback<'a>, callback_mut: &'a CallbackMut<'a>) -> MyStruct<'a> {
        MyStruct {
            callback,
            callback_mut,
        }
    }

    pub fn trigger_callback(&'a self) {
        (self.callback)(self);
    }

    pub fn trigger_callback_mut(&'a mut self) {
        (self.callback_mut)(self);
    }
}

#[cfg(test)]
mod tests {
    use crate::minimal::*;

    #[test]
    fn it_works() {

        let mut triggered1 = false;
        let callback = |_my_struct: &MyStruct| {
            triggered1 = true;
        };

        let mut triggered2 = false;
        let callback_mut = |_my_struct: &mut MyStruct| {
            triggered2 = true;
        };

        let mut my_struct = MyStruct::new(&callback, &callback_mut);
        my_struct.trigger_callback();
        my_struct.trigger_callback_mut();
        assert!(triggered1, "Should call immutable callback");
        assert!(triggered2, "Should call mutable callback");
    }
}

所以我试图了解如何使这种模式在 Rust 中工作,并且对如何解决以下几个编译器错误感到困惑。

  1. 如何使用分配的回调并将结构传递给它?还有哪些其他更适合 Rust 的模式?
error[E0596]: cannot borrow `*self.callback_mut` as mutable, as it is behind a `&` reference
  --> src/minimal.rs:22:9
   |
6  |     callback_mut: &'a CallbackMut<'a>
   |                   ------------------- help: consider changing this to be mutable: `&'a mut CallbackMut<'a>`
...
22 |         (self.callback_mut)(self);
   |         ^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
  1. 如何改变回调中捕获的变量?还有哪些其他更适合 Rust 的模式?
error[E0597]: `triggered1` does not live long enough
  --> src/minimal.rs:35:13
   |
34 |         let callback = |_my_struct: &MyStruct| {
   |                        ----------------------- value captured here
35 |             triggered1 = true;
   |             ^^^^^^^^^^ borrowed value does not live long enough
...
43 |         let mut my_struct = MyStruct::new(&callback, &callback_mut);
   |                                           --------- cast requires that `triggered1` is borrowed for `'static`
...
48 |     }
   |     - `triggered1` dropped here while still borrowed
  1. 当不可变借用直到可变借用才存在时,我似乎应该能够执行不可变借用操作,然后严格执行可变借用操作。
error[E0502]: cannot borrow `my_struct` as mutable because it is also borrowed as immutable
  --> src/minimal.rs:45:9
   |
44 |         my_struct.trigger_callback();
   |         --------- immutable borrow occurs here
45 |         my_struct.trigger_callback_mut();
   |         ^^^^^^^^^^--------------------^^
   |         |         |
   |         |         immutable borrow later used by call
   |         mutable borrow occurs here

  1. 然后我将如何引用在回调中增加的变量?
error[E0503]: cannot use `triggered1` because it was mutably borrowed
  --> src/minimal.rs:46:17
   |
34 |         let callback = |_my_struct: &MyStruct| {
   |                        ----------------------- borrow of `triggered1` occurs here
35 |             triggered1 = true;
   |             ---------- borrow occurs due to use of `triggered1` in closure
...
43 |         let mut my_struct = MyStruct::new(&callback, &callback_mut);
   |                                           --------- cast requires that `triggered1` is borrowed for `'static`
...
46 |         assert!(triggered1, "Should call immutable callback");
   |                 ^^^^^^^^^^ use of borrowed `triggered1`

编辑:一些附加信息。

希望在状态机中实现挂钩功能,从而我可以在事件上调用挂钩 - 状态进入、状态退出、边缘遍历。

不可变回调示例

let mut collected_values = Vec::new();
let immutable_callback_example = |my_struct: &MyStruct| {
    collected_values.push(my_struct.some_val.clone());
};
# run through a bunch of code that might call the trigger_callback an arbitrary number of times
# Do something with the collected values - report out etc
let mut my_structs = Vec::new();
let immutable_callback_example = |my_struct: &MyStruct| {
# Some event indicates we need to create a new struct
    state_machines.push(MyStruct {
      -10,
      callback,
      callback_mut,
    });
};
# run through a bunch of code that might call the trigger_callback an arbitrary number of times
# Do something with the final collection of structs - report out etc

可变回调示例

let mut collected_values = Vec::new();
let mutable_callback_example = |my_struct: &mut MyStruct| {
    collected_values.push(my_struct.some_val.clone());
# Wrap this back around to 0
    if my_struct.some_val > 10 {
        my_struct.some_val = 0;
    }
};
# run through a bunch of code that might call the trigger_callback_mut an arbitrary number of times
# Again, report out

【问题讨论】:

  • 您的回调真的需要访问MyStruct 值吗?您的两个测试都没有使用它们,因此很难就如何解决其中的一些问题提出建议。另外,为什么要存储对函数的引用而不是函数本身?
  • 这个设计根本行不通。如果这些回调需要是FnMut,这意味着它们可以修改它们的捕获变量,这意味着它们正在改变自己。这意味着,这些字段必须是callback: &amp;'a mut Callback&lt;'a&gt;。但是你永远不能在传递&amp;self 时调用它们,因为你将同时拥有一个不可变和可变借用。如果允许,这些函数实际上可能会做坏事。
  • 很难就如何解决您的问题提出建议,因为我们不知道您实际需要实现什么。也许可以通过渠道解决?我不知道。
  • 这样的事情怎么样:play.rust-lang.org/…
  • 这个怎么样:play.rust-lang.org/…

标签: design-patterns rust callback borrow-checker


【解决方案1】:

FnMut 必须能够自我变异,因此您将无法使用不可变的&amp;dyn FnMut。你需要&amp;mut dyn FnMut

但最大的问题是您过度使用临时引用。 Rust 中的&amp; 不等同于在其他语言中“通过引用”使用对象,它的语义具有相当严重的局限性。在 99% 的情况下,在结构中使用引用是错误的。

存储“通过引用”的东西,您应该使用Box 类型,例如Box&lt;dyn FnMut()&gt;。它是内存中的一个指针,与&amp; 完全一样,但它并未尝试针对单个作用域,也不会使用生命周期注释和借用检查器限制“感染”代码。

同样,要在回调函数内部和外部访问一些数据,您需要将这些数据包装在Arc 中,而不是使用&amp;。对于可变访问:Arc&lt;Mutex&gt;。对于布尔值,Arc&lt;AtomicBool&gt; 也可以。

通过引用捕获局部变量的闭包不能在变量范围之外使用,这通常使得它们无法用于观察者和基于事件的回调编程模式。

  1. 不要在结构中放置临时引用。
  2. 将所有内容包装在 Arc 中,克隆 Arcs,并使用 move || 闭包。

【讨论】:

  • 所以我真的应该为基于事件的回调模式寻找其他机制吗?最终,我试图将其与外部函数调用(即在 python 中)联系起来。我看起来像通道,但我真的希望回调的执行是同步的。
  • 您可以使用回调,但您需要使用Arc&lt;Callback&gt;(与Python 中的引用相同)而不是&amp;Callback,这对于您正在尝试执行的操作在语义上是无效的。您没有遇到语言的限制,您使用的是一种功能,将其误认为是另一种。要通过引用获取 args,请使用 Arc&lt;Mutex&lt;dyn for&lt;'a&gt; FnMut(&amp;'a i32)&gt;&gt;
猜你喜欢
  • 2017-04-26
  • 2015-11-05
  • 1970-01-01
  • 1970-01-01
  • 2022-08-21
  • 1970-01-01
  • 2022-08-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多