高阶函数是其参数或返回值本身就是函数的函数。换句话说,如果一种语言支持高阶函数,那么我们就说这些函数是一等公民,也就是说它们是值。
Rust中的函数
fn plus_one(n: i32) -> i32 {
n + 1
}
return关键字是可选的。如果我们不指定它,函数的最后一条语句被认为是返回语句。
fn main() {
let add_one = plus_one;
println!("{}", add_one(1));
}
函数作为参数
我们演示了如何定义函数并将其存储在变量中。现在,让我们看看如何将一个函数作为参数传递给另一个函数。
fn binary_operator(n: i32, m: i32, op: F) -> i32
where F: Fn(i32, i32) -> i32 {
op(n, m)
}
binary_operator输入两个数字n和m,以及一个函数op。它对n和m应用op,返回结果。
注意op参数的类型。它是一个泛型类型F,在binary_operator的where子句中进行了细化。特别地,我们将其定义为具有两个数值形参(i32, i32)的函数Fn,返回形参i32。Fn在这里表示一个函数指针,即存储函数的内存地址。
实名函数
add(n: i32, m: i32) -> i32 {
n + m
}
fn binary_operator(n: i32, m: i32, op: F) -> i32
where F: Fn(i32, i32) -> i32 {
op(n, m)
}
fn main() {
println!("{}", binary_operator(5, 6, add));
}
在上面的例子中,我们首先定义一个函数add来相加两个数字,并将其用作binary_operator的参数。结果打印11,如预期。
匿名函数
fn main() {
println!("{}", binary_operator(5, 6, |a: i32, b: i32| a - b));
}
在上面的例子中,我们在调用binary_operator时直接定义了一个匿名函数。||之间是参数列表,然后是函数体本身。
匿名函数是一个非常强大的工具,因为在Rust中,它们可以“捕获”上下文环境。在这种情况下,函数也称为闭包。
函数作为返回值
正如我们前面提到的,在Rust中一个函数也可以返回另一个函数。由于Rust中内存管理的结果,这有点复杂,我们很快就会看到。
fn unapplied_binary_operator<'a, F>(n:&'a i32, m:&'a i32, op:&'a F) -> Box<dyn Fn() -> i32 + 'a>
where F: Fn(i32, i32) -> i32 {
Box::new(move || op(*n, *m))
}
unapplied_binary_operator的定义现在看起来要复杂得多。
返回函数的主要问题是定义函数生命周期的长度。在Rust中,生命周期是借用检查器用来确保所有借用都是有效的。
在上面的例子中,我们定义了一个生命周期'a和泛型的F类型。然后,我们将'a与三个参数以及函数的返回值关联起来。
基本上,我们告诉借用检查器,只要三个参数(n、m和op)存在,就考虑unapplied_binary_operator返回的函数的生命期。
此外,Rust中的生命周期只存在于引用中。因此,我们必须将参数和返回值分别用&和Box转换为引用。
一般来说,Box<dyn Fn()>表示实现Fn特征的装箱值。
上述实现中另一个有趣的地方是move的使用。该关键字表示所有捕获(即对封闭环境的所有引用)都是按值发生的。否则,只要返回的匿名函数存在,任何引用捕获都将被删除,从而使闭包保留无效引用。换句话说,通过move,闭包获得了它使用的变量的所有权。
fn main() {
let n = 5;
let m = 6;
println!("{}", unapplied_binary_operator(&n, &m, &add)());
}
总结
在本文中,我们快速研究了Rust中的高阶函数。我们演示了如何定义一个简单的函数。然后,我们探索了将函数作为参数传递的方法。最后,我们研究了返回一个函数有多复杂,简要地提到了Rust的一些关键特性,比如借用和生命周期。