Rust中的回调模式



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

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

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

但是最大的问题是你过度使用了临时引用。Rust中的&不等同于使用对象"通过引用"。在其他语言中,它的语义有相当严重的限制。在99%的情况下,在结构体内部使用引用是错误的。

存储某物"通过引用"您应该使用Box类型,例如Box<dyn FnMut()>。它是内存中的指针,就像&一样,但它没有尝试到单个作用域,也没有"感染"。带有生命周期注释和借用检查器限制的代码。

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

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

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

相关内容

  • 没有找到相关文章

最新更新