Search code examples
rustcallbacklistenerborrow-checkermutable

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


I have an algorithm that manipulates an array of objects in some manner, but the caller needs to be able to listen to certain events (updates on the objects) triggered by the algorithm.

The following is a simplified example of what I'm trying to do. (Rust playground)

This is the algorithm module:

// 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());
}

Callers will call do_stuff(), which mutates an array of objects in some manner, and invokes the listener on each object as it mutates them. This algorithm module should not need to know what the caller wants to do when the callback is triggered.

This is the main module:

// 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);
}

Error:

   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

In my actual code, instead of updating the Vec, I'm trying to call a mutable function on &mut svg::Document (from the svg crate) to add a some SVG nodes to the document. The algorithm converts the objects into primitive shapes, and each listener takes those primitive shapes and the style of the current object (e.g. line width, color), and generates SVG commands and adds them to the svg::Document.

The code above obviously isn't going to work, because I'm borrowing the strings vector mutably multiple times. But there isn't really any way to split it up, all of them need to modify the same vector.

I could alternatively have do_stuff return the list of updates and have the caller apply them later, but that would involve a bunch of temporary vectors.

Is there any way to make the current design work?


Solution

  • Wrap the strings vector in a RefCell, then use shared references:

    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);
    }