rustclosures

Closures in a struct member (HashMap) which capture a mutable reference to another struct member


How could I achieve something like this in Rust

struct TestStruct {
    map:HashMap<String, Box<FnMut(i32) -> ()>>,
    val:i32
}

impl TestStruct {
    fn new() -> Self {
        let mut ts = TestStruct{ map: Default::default(), val: 0 };
        ts.map.insert(String::from("add"),Box::new(|a| ts.val+=a ));
        ts.map.insert(String::from("mult"),Box::new(|a| ts.val*=a ));
        ts
    }

    fn execute(&mut self, cmd:&str,arg:i32) {
        let f =  self.map.get_mut(cmd).unwrap();
        f(arg);
    }
}

This obviously doesn't work since ts is mutably borrowed multiple times

Is the solution really this ugly?

impl TestStruct {
    fn new() -> Self {
        let mut map:HashMap<String, Box<Fn(i32) -> ()>> = HashMap::new();
        let val = Rc::new(RefCell::new(0));
        let v1 = val.clone();
        map.insert(String::from("add"),Box::new(move |a|
            {
                let mut mutator = v1.borrow_mut();
                *mutator+=a;
            }
        ));
        let v1 = val.clone();
        map.insert(String::from("mult"),Box::new(move |a| {
            {
                let mut mutator = v1.borrow_mut();
                *mutator*=a;
            }
        }
        ));
        TestStruct{ map, val }
    }

    fn execute(&mut self, cmd:&str,arg:i32) {
        let f =  self.map.get_mut(cmd).unwrap();
        f(arg);
    }
}

Is there a way to achieve something like this with a completely different approach?

For completeness I am including the Rc version:

struct TestStruct {
    map:HashMap<String, Box<Fn(i32) -> ()>>,
    val:Rc<Cell<i32>>
}

impl TestStruct {
    fn new() -> Self {
        let mut map:HashMap<String, Box<Fn(i32) -> ()>> = HashMap::new();
        let val = Rc::new(Cell::new(0));
        let v1 = val.clone();
        map.insert(String::from("add"),Box::new(move |a| v1.set(v1.get()+a)));
        let v1 = val.clone();
        map.insert(String::from("mult"),Box::new(move |a| v1.set(v1.get()*a)));
        TestStruct{ map, val }
    }

    fn execute(&mut self, cmd:&str,arg:i32) {
        let f =  self.map.get_mut(cmd).unwrap();
        f(arg);
    }
}

Solution

  • I would recommend avoiding shared mutability unless absolutely necessary. In this case you can give a mutable reference to your functions only when they are executing by passing val as a mutable reference parameter:

    use std::collections::HashMap;
    
    struct TestStruct {
        map: HashMap<String, Box<dyn FnMut(&mut i32, i32) -> ()>>,
        val: i32
    }
    
    impl TestStruct {
        fn new() -> Self {
            let mut ts = TestStruct { map: Default::default(), val: 0 };
            ts.map.insert(String::from("add"), Box::new(|val, a| *val += a ));
            ts.map.insert(String::from("mult"), Box::new(|val, a| *val *= a ));
            ts
        }
    
        fn execute(&mut self, cmd: &str, arg: i32) {
            let f = self.map.get_mut(cmd).unwrap();
            f(&mut self.val, arg);
        }
    }