Rust中如何接收以闭包(或任意trait实现)为参数的闭包?
嘿,这个问题我之前写Rust的时候也纠结过!你那段用impl FnOnce()当闭包参数的代码确实过不了编译,Rust现在还不允许在Fn trait的约束里直接用impl Trait。不过别担心,有几个完全不用装箱、零动态分发开销的方案,正好对应你的需求。
方案一:高阶Trait约束(HRTBs)—— 处理“接受任意trait实现”的通用场景
如果你需要外层闭包能接受任意符合某个trait(比如FnOnce)的类型作为参数,那可以用Rust的高阶Trait约束(也就是for<>语法,常叫HRTBs)。这种写法相当于明确告诉编译器:“这个外层闭包得能接住所有满足条件的内层类型”。
把你的原始代码改成这样就能正常运行了:
fn call_closure_with_closure<F>(f: F) where F: for<G> FnOnce(G) where G: FnOnce(), { let mut called = false; f(|| { called = true; }); assert!(called); }
调用的时候,你需要传一个泛型闭包(Rust 1.64及以上版本支持),比如:
let my_closure = |g: impl FnOnce()| { g(); }; call_closure_with_closure(my_closure);
这种写法完全是静态分发,没有任何装箱开销,编译器会为每个具体的内层闭包类型生成专属代码,性能拉满。
方案二:显式泛型参数—— 处理“接受特定trait实现”的场景
如果你不需要外层闭包接受任意trait实现,只需要它能接受你指定的某个具体类型(比如你在函数内部创建的那个闭包),那更简单:直接给内层类型加一个泛型参数就行。
代码示例:
fn call_closure_with_closure<F, G>(f: F) where F: FnOnce(G), G: FnOnce(), { let mut called = false; let g = || { called = true; }; f(g); assert!(called); }
调用的时候编译器会自动推导G的类型,完全不用你手动指定,同样是静态分发,没有额外开销。
扩展到任意Trait的情况
你提到的“闭包接受任意trait实现”的通用问题,用HRTBs同样能解决。比如我自定义一个Trait,然后写一个接收能处理该Trait任意实现的闭包的函数:
trait MyTrait { fn do_something(&self); } fn call_closure_with_mytrait<F>(f: F) where F: for<G> FnOnce(G) where G: MyTrait, { // 随便写一个MyTrait的实现 struct MyStruct; impl MyTrait for MyStruct { fn do_something(&self) { println!("执行自定义操作"); } } // 把这个实现传给外层闭包 f(MyStruct); }
关于async场景的优化
你担心的async版本的装箱问题,用上面的静态分发方案也能完美解决。比如写一个async版本的函数,同样用HRTBs,完全不需要装箱闭包或者Future:
async fn async_call_closure<F>(f: F) where F: for<G> FnOnce(G) -> impl std::future::Future<Output = ()> where G: FnOnce(), { let mut called = false; f(|| { called = true; }).await; assert!(called); }
编译器会为每个具体的闭包和Future类型生成专属代码,完全没有动态分发的开销,比装箱的方案高效得多。
备注:内容来源于stack exchange,提问作者jacobsa

