Rust中如何实现结构体闭包捕获自身成员可变引用?
Rust中实现命令式状态修改的替代方案
我尝试在Rust中实现一个通过命令映射闭包来修改内部状态的结构体,最初的代码如下:
use std::collections::HashMap; 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,违反了Rust的借用规则。
我目前通过两种基于引用计数的方案实现了需求:
基于Rc+RefCell的实现
use std::collections::HashMap; use std::rc::Rc; use std::cell::RefCell; struct TestStruct { map: HashMap<String, Box<Fn(i32) -> ()>>, val: Rc<RefCell<i32>> } 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+Cell的参考实现
use std::collections::HashMap; use std::rc::Rc; use std::cell::Cell; 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:用枚举封装操作(最简洁安全)
将命令对应的操作封装成枚举变体,完全避免闭包和引用计数,编译期安全且无运行时开销:
use std::collections::HashMap; enum Operation { Add, Multiply, } struct TestStruct { map: HashMap<String, Operation>, val: i32, } impl TestStruct { fn new() -> Self { let mut map = HashMap::new(); map.insert(String::from("add"), Operation::Add); map.insert(String::from("mult"), Operation::Multiply); TestStruct { map, val: 0 } } fn execute(&mut self, cmd: &str, arg: i32) { match self.map.get(cmd).unwrap() { Operation::Add => self.val += arg, Operation::Multiply => self.val *= arg, } } }
方案2:让闭包接收可变引用
调整闭包定义,使其接收&mut i32作为参数,创建闭包时不捕获结构体,而是在执行时传递状态的可变引用:
use std::collections::HashMap; struct TestStruct { map: HashMap<String, Box<dyn Fn(&mut i32, i32) -> ()>>, val: i32, } impl TestStruct { fn new() -> Self { let mut map = HashMap::new(); map.insert(String::from("add"), Box::new(|val, a| *val += a)); map.insert(String::from("mult"), Box::new(|val, a| *val *= a)); TestStruct { map, val: 0 } } fn execute(&mut self, cmd: &str, arg: i32) { let f = self.map.get(cmd).unwrap(); f(&mut self.val, arg); } }
方案3:使用unsafe(不推荐)
如果一定要保留闭包捕获原始状态的形式,可以用unsafe绕过借用检查,但会失去Rust的内存安全保障,仅在特殊场景下考虑:
use std::collections::HashMap; use std::ptr; struct TestStruct { map: HashMap<String, Box<FnMut(i32) -> ()>>, val: i32, } impl TestStruct { fn new() -> Self { let mut ts = TestStruct { map: Default::default(), val: 0 }; let val_ptr = ptr::addr_of_mut!(ts.val); ts.map.insert(String::from("add"), Box::new(move |a| unsafe { *val_ptr += a; })); ts.map.insert(String::from("mult"), Box::new(move |a| unsafe { *val_ptr *= a; })); ts } fn execute(&mut self, cmd: &str, arg: i32) { let f = self.map.get_mut(cmd).unwrap(); f(arg); } }
注意:此方案存在风险,若结构体被移动,指针会失效导致未定义行为。
内容的提问来源于stack exchange,提问作者Jaka
相关产品推荐
相关产品推荐

