Rust 之闭包 - Ⅰ
闭包 是在以函数作为一等公民等编程语言中实现词法绑定等一种技术, 闭包 和 匿名函数 在一些语境下经常互为替换, 但严格来说 匿名函数 也即字面意义上等没有被赋予名称等函数, 而 闭包 实际上是函数的一个实例, 相对于常规函数, 闭包 可以捕捉环境上下文环境中的自由变量, 从一般定义来说即:
f(x) = g(x) + h(y)
这里函数 f(x) 可以表示为关于 x 和 y 的函数, 而函数 f 中无 y 的信息(相对的是 f(x, y)), 即 y 是自由变量. 除了本文介绍的 Rust 外其他诸如 Swift, Go, Erlang, JavaScript, Python 等都有 闭包.
我们先来看一下在 Rust 中如何定义一个常规函数:
fn max(left: i64, right: i64) -> i64 {
if left > right {
left
} else {
right
}
}
我们将其转换为 闭包:
|left: i64, right: i64| -> i64 {
if left > right {
left
} else {
right
}
}
可以看到 闭包 的语法与 函数 即为相似, 我们只是简单地把 ( 和 ) 用 | 替换并且移除了 fn 关键字和 函数名. 为了进行调用我们可以将其赋值给一个变量并通过该变量来调用定义的 闭包:
let max_closure = |left: i64, right: i64| -> i64 {
if left > right {
left
} else {
right
}
}
let max_number = max_closure(10, 11);
相对于函数而言, 以上闭包我们还可以进一步简化:
// First
let max_closure = |left, right| {
if left > right {
left
} else {
right
}
};
// -----------------------------------------------
// Second
let max_closure = |left, right| if left > right { left } else { right };
可以看到除了 | 我们移除了多于的 {} 和 i64 类型定义.
前文提到过 闭包 相对于 函数 具有捕捉上下文环境的能力, 也即可以拥有 自由变量. 仍以上面定义的 max_closure 闭包为例:
let addition = 10;
let max_closure = |left, right| {
if left > right {
left + addition
} else {
right + addition
}
};
这里 addition 为外部变量, 但在 max_closure 内部我们仍然可以访问, 如果是函数则不行.
以上的例子并无特别之处, 闭包 可以实现的功能函数一般也可以实现, 除了捕捉上下文环境外. 但上面闭包所展现的场景并不能有效说明为何要用闭包而不是直接书写如下语句:
let addition = 10;
let left = ;
let right = ;
let max_value = if left > right {
left + addition
} else {
right + addition
};
前文说过 闭包 是以函数为 一等公民 的编程语言中所具有的鲜明特性, 因此让以上定义的 max_closure 闭包发挥作用往往是在如下场景中:
fn max_by_number(addition: i64) -> impl Fn(i64, i64) -> i64 {
return move |left: i64, right: i64| -> i64 {
if left > right {
left + addition
} else {
right + addition
}
};
}
let max = max_by_number(10);
println!("MAX: {}", max(11, 12));
这里较之前语法有过多元素需要介绍, 首先在定义 max_by_number 函数时其返回等不再是一个普通的类型而是 impl Fn(i64, i64) -> i64. Fn 是一个 Trait, 其表示 调用者可以反复调用并且不修改其参数, 即参数以 & 借用. 同时 Rust 还为我们提供了另外两种类似的 Trait: FnMut - 调用者可多次调用并且会修改其参数, 即参数以 &mut 形式借用; FnOnce - 调用者仅可调用一次, 参数以 move 形式传入. 另外需要补充等是 FnOnce 是 FnMut 的 父 Trait, FnMut 是 Fn 的 父 Trait. 也即任何实现了 FnMut 的对象必然实现了 FnOnce, 任何实现了 Fn 的对象必然也实现了 FnMut. 那么这个 max_by_number 函数定义真正函数为 返回一个实现了 Fn Trait 的闭包, 该闭包有两个参数都是 i64 类型, 同时该闭包会返回一个 i64 的结果. max_by_number 函数体里直接通过 return 返回 闭包, 该 闭包 通过 move 捕捉外部环境的 addition 变量. 语句 let max = max_by_number(10); 返回一个闭包并赋值给 max, 后续再通过 max(11, 12) 调用该闭包获得最终结果.
如果以上还不够明晰的话我们看一个 Rust 中经常会接触到的实用 闭包 - unwrap_or_else:
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T,
{
match self {
Some(x) => x,
None => f(),
}
}
unwrap_or_else 函数获得自身所有权并接收一个 FnOnce 等闭包同时该闭包返回泛型 T - 其结果与 Option<T> 相吻合.