高阶函数是指接受其他函数作为参数或将其作为结果返回的函数。自 Rust 发展之初,就强调类型安全性和性能,这体现在处理此类函数的方式上。
问题背景:
在函数式语言中,高阶函数被视为标准,但在许多系统编程语言中,它们往往导致性能泄漏(例如,由于分配或无法内联代码)。在 Rust 中,这种功能是通过严格的类型系统、静态调度或特征(traits) (Fn, FnMut, FnOnce) 来实现的,这在大多数情况下可以避免开销。
问题:
主要问题在于需要传递函数或闭包,同时保持类型安全、捕获变量的能力(轻松的 lambda 表达式)和性能,避免分配或虚拟调用。
解决方案:
在 Rust 中,高阶函数是通过泛型参数和用于函数/闭包的特征包装来实现的。标准特征 Fn、FnMut 和 FnOnce 允许非常明确地声明对传递函数的要求(它是否可以改变或消耗环境)。通过泛型参数传递可以在编译时内联调用。也有通过 Box<dyn Fn...> 的动态调度,当类型在运行时未知。
示例代码:
fn apply_to_vec<F: Fn(i32) -> i32>(v: Vec<i32>, f: F) -> Vec<i32> { v.into_iter().map(f).collect() } let nums = vec![1, 2, 3]; let doubled = apply_to_vec(nums, |x| x * 2); // doubled == [2, 4, 6]
关键特点:
Fn、FnMut 和 FnOnce 有什么区别?
许多人认为它们仅在语法上不同,或者认为 Fn 和 FnMut 可以互换使用。实际上:
FnOnce 只能调用一次(例如,如果 lambda 移动了捕获的值)。FnMut 可以更改捕获的环境状态,但可以调用多次。Fn 不改变环境。示例:
let mut sum = 0; let mut add = |x| { sum += x; }; // add 实现了 FnMut,但不实现 Fn
可以不使用 boxing 传递函数作为值吗?
人们常常认为任何函数参数都必须被 boxed(Box<dyn Fn...>)。实际上,box 只在动态调度时需要,当类型在运行时未知时。通过泛型参数,函数可以完全静态类型,无需分配和 box。
在什么情况下闭包不再是 Copy?
一些人认为,只要内部变量是 Copy,那么简单的闭包总是 Copy 或 Clone。实际上,默认情况下,闭包不是 Copy,即使捕获的变量是 Copy。需要明确实现特征或仅使用简单函数。
在一个项目中,所有回调在集合中都仅使用了 Box<dyn Fn()>,没有考虑内联和分配。因此,无法获得性能提升,频繁的分配导致了延迟。
优点:
缺点:
事件处理程序通过带有特征限制 FnMut 的泛型函数进行配置,完全避免了分配。
优点:
缺点: