结构成员 (HashMap) 中的闭包,用于捕获对另一个结构成员的可变引用

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

提问人:Jaka 提问时间:9/30/2023 最后编辑:Jaka 更新时间:10/1/2023 访问量:66

问:

我怎么能在 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);
    }
}

这显然是行不通的,因为可以多次可变地借用ts

解决方案真的这么丑陋吗?

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

有没有办法用完全不同的方法实现这样的事情?

为了完整起见,我包括 Rc 版本:

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);
    }
}
防锈 封盖

评论

1赞 cdhowie 9/30/2023
如果您使用共享引用,它甚至不起作用,因为值不能包含对自身的引用;一旦您返回,闭包捕获的引用将变得无效。您可能需要使用类似此处的内容。tsTestStruct::newRc<Cell>
2赞 kmdreko 9/30/2023
我建议不要在函数中保留句柄,而是在调用时将其作为可变引用传递。游乐场示范valexecute
0赞 Jaka 9/30/2023
@kmdreko 我更喜欢您的建议而不是“Rc<RefCell<......>>的恐怖
1赞 cdhowie 9/30/2023
这是另一种选择。请注意,它甚至比 更轻量级,您甚至不需要 here(尽管两者都不是)。CellRefCellRefCellSync

答:

2赞 kmdreko 9/30/2023 #1

除非绝对必要,否则我建议避免共享可变性。在这种情况下,只有在函数执行时,您才能通过作为可变引用参数传递来为函数提供可变引用:val

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

评论

0赞 Jaka 10/1/2023
此外,由于使用解决方案时,闭包不需要捕获任何内容,因此可以更改映射签名,因为闭包可以成为简单的函数指针。map: HashMap<String, fn(&mut i32, i32) -> ()>