Rust如何在回调函数中修改外部捕获的变量
错误原因
Rust的闭包实现了三种不同的trait,对捕获变量的访问权限有严格限制:
Fn类型的闭包只能不可变访问捕获的变量,可重复调用,调用时不需要修改闭包自身状态FnMut类型的闭包可以修改捕获的变量,可重复调用,调用时需要可变引用闭包自身FnOnce类型的闭包只能调用一次,调用时会消耗捕获的变量
你代码中http_get的回调参数声明为&dyn Fn(),天然不允许修改捕获的外部变量,这是编译报错的直接原因。另外如果你的场景需要多个回调同时修改同一个外部变量,Rust的默认借用规则(同一时间仅允许一个可变引用)也会触发二次编译错误,需要用内部可变性容器解决。
解决方案
最简适配:单回调修改变量
如果你只有单个回调需要修改外部变量,不需要多个回调共享变量,直接修改http_get的签名接受FnMut类型的回调即可:
fn http_get(path: &str, f: &mut dyn FnMut()) { f(); } fn main() { let mut counter = 0; http_get("/foo", &mut || { counter = counter + 1; println!("/foo: {}", counter); }); }
场景1:单线程多回调共享变量
如果你的http_get是同步执行逻辑(调用后立刻执行回调,执行完成才返回),需要多个回调修改同一个变量,优先用Cell(针对实现了Copy的基础类型,比如示例中的整数计数器)实现内部可变性,不需要修改原有http_get的签名:
use std::cell::Cell; fn http_get(path: &str, f: &dyn Fn()) { f(); } fn main() { let counter = Cell::new(0); http_get("/foo", &|| { counter.set(counter.get() + 1); println!("/foo: {}", counter.get()); }); http_get("/bar", &|| { counter.set(counter.get() + 1); println!("/bar: {}", counter.get()); }); }
如果捕获的是没有实现Copy的复杂类型,可以把Cell替换为RefCell,通过borrow()/borrow_mut()访问内部值即可。
场景2:多线程/异步回调
如果你的http_get是异步逻辑,回调会在其他线程执行,需要使用线程安全的内部可变性容器Arc<Mutex>,同时调整http_get的参数接受带'static生命周期的闭包方便跨线程传递:
use std::sync::{Arc, Mutex}; fn http_get(path: &str, f: impl Fn() + 'static) { // 模拟异步回调:新开线程执行逻辑 std::thread::spawn(f).join().unwrap(); } fn main() { let counter = Arc::new(Mutex::new(0)); let counter_clone1 = counter.clone(); http_get("/foo", move || { let mut cnt = counter_clone1.lock().unwrap(); *cnt += 1; println!("/foo: {}", cnt); }); let counter_clone2 = counter.clone(); http_get("/bar", move || { let mut cnt = counter_clone2.lock().unwrap(); *cnt += 1; println!("/bar: {}", cnt); }); }
内容的提问来源于stack exchange,提问作者David Alsh
相关产品推荐
相关产品推荐

