在 Rust 中,同时在多个子对象上安装可变回调函数的惯用方法是什么?

In Rust, what is the idiomatic way to install a mutable callback function on multiple subobjects at the same time?

提问人:Bernard 提问时间:1/29/2023 更新时间:1/29/2023 访问量:92

问:

我有一个算法,可以以某种方式操作对象数组,但调用者需要能够侦听由该算法触发的某些事件(对象的更新)。

以下是我正在尝试执行的简化示例。 (Rust playground)

这是算法模块:

// Some module containing the algorithm.
// This module doesn't want to know what the caller wants to do in the listener.

trait Listener {
    fn update(&mut self, s: String);
}

struct Object<L: Listener> {
    /* other stuff */
    listener: L,
}

fn do_stuff<L: Listener>(objects: &mut [Object<L>]) {
    // do stuff, which eventually might call the listener of each object any number of times.
    objects[0].listener.update("zero".to_string());
    objects[1].listener.update("one".to_string());
    objects[0].listener.update("zeroes".to_string());
}

调用方将调用 ,它以某种方式改变对象数组,并在改变每个对象时调用侦听器。此算法模块不需要知道调用方在触发回调时想要做什么。do_stuff()

这是主要模块:

// Main module

// Obviously this can't implement Copy and Clone, because of the mut ref.
struct MyListener<'a>{
    node: &'a mut Vec<String>,
    prefix: &'static str,
}

impl<'a> Listener for MyListener<'a> {
    fn update(&mut self, s: String) {
        self.node.push(format!("{} {}", self.prefix, s));
    }
}

pub fn main() {
    let mut strings = Vec::new();
    let mut objects = vec![
        Object{listener: MyListener{node: &mut strings, prefix: "red"}},
        Object{listener: MyListener{node: &mut strings, prefix: "blue"}},
        Object{listener: MyListener{node: &mut strings, prefix: "green"}},
    ];
    do_stuff(&mut objects);
}

错误:

   Compiling playground v0.0.1 (/playground)
error[E0499]: cannot borrow `strings` as mutable more than once at a time
  --> src/main.rs:37:43
   |
35 |       let mut objects = vec![
   |  _______________________-
36 | |         Object{listener: MyListener{node: &mut strings, prefix: "red"}},
   | |                                           ------------ first mutable borrow occurs here
37 | |         Object{listener: MyListener{node: &mut strings, prefix: "blue"}},
   | |                                           ^^^^^^^^^^^^ second mutable borrow occurs here
38 | |         Object{listener: MyListener{node: &mut strings, prefix: "green"}},
39 | |     ];
   | |_____- first borrow later used here

error[E0499]: cannot borrow `strings` as mutable more than once at a time
  --> src/main.rs:38:43
   |
35 |       let mut objects = vec![
   |  _______________________-
36 | |         Object{listener: MyListener{node: &mut strings, prefix: "red"}},
   | |                                           ------------ first mutable borrow occurs here
37 | |         Object{listener: MyListener{node: &mut strings, prefix: "blue"}},
38 | |         Object{listener: MyListener{node: &mut strings, prefix: "green"}},
   | |                                           ^^^^^^^^^^^^ second mutable borrow occurs here
39 | |     ];
   | |_____- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `playground` due to 2 previous errors

在我的实际代码中,我没有更新 Vec,而是尝试调用一个可变函数(从 svg 板条箱)向文档添加一些 SVG 节点。该算法将对象转换为原始形状,每个侦听器获取这些原始形状和当前对象的样式(例如线宽、颜色),并生成 SVG 命令并将它们添加到 .&mut svg::Documentsvg::Document

上面的代码显然是行不通的,因为我多次可变地借用向量。但是实际上没有任何方法可以将其拆分,它们都需要修改相同的向量。strings

我也可以do_stuff返回更新列表并让调用方稍后应用它们,但这将涉及一堆临时向量。

有什么方法可以使当前的设计起作用吗?

回调 听者 借用检查器 可变

评论

0赞 Dogbert 1/29/2023
一种方法是修改 Listener 方法以接受为参数,例如 play.rust-lang.org/...&mut Vec<String>
0赞 Bernard 1/29/2023
这将使算法需要知道主类使用 ,这在某种程度上是我试图避免的泄漏抽象。Vec<String>
2赞 jthulhu 1/29/2023
这类问题通常可以用 a 而不是 来解决,但这不是最漂亮的解决方案。Rc<RefCell<Vec<String>>>&mut Vec<String>
0赞 Chayim Friedman 1/29/2023
@jthulhu 没必要,也可以。Rc&'a RefCell<Vec<String>>
0赞 jthulhu 1/29/2023
确实如此@ChayimFriedman。这是我第一次看到带有“常规”借用的二手车!RefCell

答:

2赞 Chayim Friedman 1/29/2023 #1

将向量包装在 RefCell 中,然后使用共享引用:strings

use std::cell::RefCell;

struct MyListener<'a> {
    node: &'a RefCell<Vec<String>>,
    prefix: &'static str,
}

impl<'a> Listener for MyListener<'a> {
    fn update(&mut self, s: String) {
        self.node.borrow_mut().push(format!("{} {}", self.prefix, s));
    }
}

pub fn main() {
    let strings = RefCell::new(Vec::new());
    let mut objects = vec![
        Object { listener: MyListener { node: &strings, prefix: "red" }},
        Object { listener: MyListener { node: &strings, prefix: "blue" }},
        Object { listener: MyListener { node: &strings, prefix: "green" }},
    ];
    do_stuff(&mut objects);
}

评论

0赞 Bernard 2/1/2023
这可行,是的,但我不禁觉得有点不幸,因为这个问题没有更好的抽象。
0赞 Chayim Friedman 2/2/2023
@Bernard 你说的“更好的抽象”是什么意思?在这里可以构建一个零成本的抽象,因为你只需要通过共享引用来推送,但应该足够好(更好的抽象的例子)。它没有在该标准库中实现,因为与 不同,它不是通用的,因此不是通常需要的。RefCellRefCell