Rust中如何保存带捕获环境的闭包并实现结构体自修改?
我想创建一个结构体,让它持有的闭包能够修改结构体自身的成员,但借用检查器报错了。我对闭包、捕获环境和生命周期了解不多,不太明白为什么会这样。
期望实现的代码如下:
struct A { pub a: usize, pub b: String, pub c: C, } struct C { pub modifier: Box<dyn FnMut()> } fn main() { let a = 3; let b = "Hello, World !".to_string(); let c = C { modifier: Box::new(|| { a += 1; b.push('c'); }) }; let mut my_struct = A { a, b, c }; println!("a, b : {}, {}", my_struct.a, my_struct.b); (my_struct.c.modifier)(); println!("a, b : {}, {}", my_struct.a, my_struct.b); }
我原本觉得,闭包捕获了a和b,只要它们的生命周期和闭包一样长就行——毕竟它们都存在同一个结构体里,应该会同时被销毁?但代码编译报错,提示a和b的生命周期不足,请问原因是什么?有没有现成的模式能实现这种需求?(目标是让用户定义的闭包可以修改它所在的结构体)
报错原因
捕获时机与所有权冲突:你创建闭包时,
a和b还是独立的局部变量,闭包捕获的是这两个变量的可变引用(因为要修改它们)。但之后你把a、b和闭包一起放进结构体A,相当于把被引用的变量移动到了包含闭包的结构体中——此时闭包持有的引用指向的变量已经被移动,生命周期关系彻底混乱,Rust的借用检查器无法保证引用有效性,因此报错。无法静态证明生命周期安全:即便变量和闭包在同一个结构体里,Rust也没法静态验证闭包执行时
a和b仍然存活。因为Box<dyn FnMut()>是无生命周期约束的 trait 对象,编译器追踪不到它引用的是结构体内部成员,只会认为它引用的是已被移动的外部变量,所以判定生命周期不足。
解决方案
方案1:让闭包接收结构体的可变引用(推荐)
这是最符合Rust安全规则的方式:闭包不捕获外部变量,而是在调用时接收整个结构体的可变引用,直接修改内部成员。
struct A { pub a: usize, pub b: String, pub c: C, } struct C { pub modifier: Box<dyn FnMut(&mut A)> } fn main() { let mut my_struct = A { a: 3, b: "Hello, World !".to_string(), c: C { modifier: Box::new(|s| { s.a += 1; s.b.push('c'); }) } }; println!("a, b : {}, {}", my_struct.a, my_struct.b); (my_struct.c.modifier)(&mut my_struct); println!("a, b : {}, {}", my_struct.a, my_struct.b); }
方案2:用Rc<RefCell<T>>实现共享可变状态
如果需要闭包隐式访问结构体成员(不需要传参),可以通过引用计数+内部可变性,让闭包和结构体共享成员的所有权:
use std::cell::RefCell; use std::rc::Rc; struct A { pub a: Rc<RefCell<usize>>, pub b: Rc<RefCell<String>>, pub c: C, } struct C { pub modifier: Box<dyn FnMut()> } fn main() { let a = Rc::new(RefCell::new(3)); let b = Rc::new(RefCell::new("Hello, World !".to_string())); // 克隆Rc,让闭包持有共享引用 let a_clone = Rc::clone(&a); let b_clone = Rc::clone(&b); let c = C { modifier: Box::new(move || { *a_clone.borrow_mut() += 1; b_clone.borrow_mut().push('c'); }) }; let mut my_struct = A { a, b, c }; println!("a, b : {}, {}", *my_struct.a.borrow(), *my_struct.b.borrow()); (my_struct.c.modifier)(); println!("a, b : {}, {}", *my_struct.a.borrow(), *my_struct.b.borrow()); }
方案3:unsafe 操作(不推荐)
如果能手动保证内存安全,可以用裸指针绕过借用检查,但这会失去Rust的安全保障,容易引发内存问题,仅在极端场景下考虑:
struct A { pub a: usize, pub b: String, pub c: C, } struct C { pub modifier: Box<dyn FnMut()>, // 存储结构体的裸指针 ptr: *mut A, } impl A { fn new() -> Self { let mut a = A { a: 3, b: "Hello, World !".to_string(), c: C { modifier: Box::new(|| {}), ptr: std::ptr::null_mut(), }, }; // 初始化裸指针 a.c.ptr = &mut a as *mut A; // 重新设置闭包,使用裸指针 a.c.modifier = Box::new(move || { unsafe { let s = &mut *a.c.ptr; s.a += 1; s.b.push('c'); } }); a } } fn main() { let mut my_struct = A::new(); println!("a, b : {}, {}", my_struct.a, my_struct.b); (my_struct.c.modifier)(); println!("a, b : {}, {}", my_struct.a, my_struct.b); }
内容的提问来源于stack exchange,提问作者LucioleMaléfique

