Search code examples
rustborrow-checkerborrowing

Array of methods in Rust


I'm trying to write a requests "router" in Rust: a list of expressions associated to the functions to call when a match occurs. The functions might be methods coming from various objects, which of course leads the borrow checker to complain. Here's a sample of what I'd like:

use std::collections::HashMap;

struct Foo(bool);

impl Foo {
    fn say(&self) {
        println!("I'm {}", self.0);
    }
    fn morph(&mut self) {
        self.0 = !self.0;
    }
}

fn main() {
    let mut foo = Foo(true);

    let mut routes: HashMap<String, Box<FnMut()>> = HashMap::new();
    routes.insert("foo/say".to_string(), Box::new(|| foo.say()));  //< First (immutable) borrow of foo
    routes.insert("foo/morph".to_string(), Box::new(|| foo.morph())); //< Second (mutable) borrow of foo
    routes.insert("bar".to_string(), Box::new(|| println!("hello"))); //< Compiler also says immutable foo is used here
}

I understand why the borrow checker isn't happy about this, but I'm wondering what might be the idiomatic way of implementing that in Rust.

Side note: any general comment on the best way to get a list/array/hashmap or any collection of heterogeneous functions will be appreciated.


Solution

  • The solution is quite simple, really. Because you're going to need a structure that allows you to obtain a mutable borrow on-demand, you're going to want a RwLock. And because I'm guessing what you're building is an HTTP router, to bypass lifetime requirements on Foo, you're going to want to wrap that RwLock in an Arc, like so:

    use std::collections::HashMap;
    use std::sync::{Arc, RwLock};
    
    struct Foo(bool);
    
    impl Foo {
        fn say(&self) {
            println!("I'm {}", self.0);
        }
        fn morph(&mut self) {
            self.0 = !self.0;
        }
    }
    
    fn main() {
        let mut foo = Arc::new(RwLock::new(Foo(true)));
    
        let mut routes: HashMap<String, Box<FnMut()>> = HashMap::new();
        routes.insert("foo/say".to_string(), Box::new(|| foo.read().unwrap().say()));  //< First (immutable) borrow of foo
        routes.insert("foo/morph".to_string(), Box::new(|| foo.write().unwrap().morph())); //< Second (mutable) borrow of foo
        routes.insert("bar".to_string(), Box::new(|| println!("hello"))); //< Compiler also says immutable foo is used here
    }
    

    Do note that I've abused the fact that everything can just borrow foo read-only for this. If you ever need to move stuff in closures, Arc implements Clone, so you should definitely take advantage of this.

    The rest is fine - as long as your closures all have the same signature, boxing them and storing them in any collection is idiomatic.