Rust中如何传递捕获变量的闭包作为函数参数?
Rust中传递捕获变量的闭包给高阶函数的问题解决
我在Rust中使用高阶函数时遇到问题:想要传递的参数函数是一个捕获了值的闭包。
第一次尝试
/// 将值n翻倍后应用函数f fn double_then_f(n: u64, f: fn(u64) -> u64) -> u64 { f(n * 2) } fn main() { // 无捕获的闭包可以正常运行 let example_1 = double_then_f(5, |n| n + 1); // 捕获变量的闭包无法运行 let dynamic_value = vec![1, 2, 3].iter().sum::<u64>(); let example_2 = double_then_f(5, |n| n + dynamic_value); }
编译失败,报错:
note: expected fn pointer
fn(u64) -> u64
found closure[closure@src/main.rs:12:38: 12:41]
note: closures can only be coerced tofntypes if they do not capture any variables
第二次尝试
了解Fn trait和fn类型的区别后,我修改了代码:
/// 将值n翻倍后应用函数f fn double_then_f(n: u64, f: Box<dyn Fn(u64) -> u64>) -> u64 { f(n * 2) } fn main() { // 无捕获的闭包可以正常运行 let example_1 = double_then_f(5, Box::new(|n| n + 1)); // 捕获变量的闭包仍无法运行 let dynamic_value = vec![1, 2, 3].iter().sum::<u64>(); let example_2 = double_then_f(5, Box::new(|n| n + dynamic_value)); }
这次编译仍失败,出现生命周期相关错误:cast requires that dynamic_valueis borrowed for'static``,猜测是闭包转换为trait对象时的强制转换导致的。
解决方案
方法一:使用泛型约束(推荐)
直接给函数添加泛型参数,约束它实现Fn(u64) -> u64 trait,既不需要装箱,也能兼容所有符合条件的闭包(包括捕获变量的):
fn double_then_f<F>(n: u64, f: F) -> u64 where F: Fn(u64) -> u64, { f(n * 2) } fn main() { let example_1 = double_then_f(5, |n| n + 1); let dynamic_value = vec![1, 2, 3].iter().sum::<u64>(); let example_2 = double_then_f(5, |n| n + dynamic_value); println!("example_1: {}, example_2: {}", example_1, example_2); }
这种方式是Rust处理高阶函数的惯用写法,泛型会在编译时单态化,无运行时开销,同时完美支持捕获环境的闭包。
方法二:使用带生命周期的trait对象(若必须装箱)
如果因场景限制必须使用Box<dyn Fn>,可以给函数添加生命周期参数,让trait对象的生命周期与捕获变量绑定:
fn double_then_f<'a>(n: u64, f: Box<dyn Fn(u64) -> u64 + 'a>) -> u64 { f(n * 2) } fn main() { let example_1 = double_then_f(5, Box::new(|n| n + 1)); let dynamic_value = vec![1, 2, 3].iter().sum::<u64>(); let example_2 = double_then_f(5, Box::new(|n| n + dynamic_value)); println!("example_1: {}, example_2: {}", example_1, example_2); }
或者使用move闭包将dynamic_value的所有权转移到闭包中,消除对外部变量引用的依赖:
fn double_then_f(n: u64, f: Box<dyn Fn(u64) -> u64>) -> u64 { f(n * 2) } fn main() { let example_1 = double_then_f(5, Box::new(|n| n + 1)); let dynamic_value = vec![1, 2, 3].iter().sum::<u64>(); let example_2 = double_then_f(5, Box::new(move |n| n + dynamic_value)); println!("example_1: {}, example_2: {}", example_1, example_2); }
内容的提问来源于stack exchange,提问作者JoshOrndorff
相关产品推荐
相关产品推荐

