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.
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.