序
"闭包是什么",是JavaScript 中最经典的问题之一。
你总是会写一段这样的代码
function init() {
var name = "Mozilla"; // name 是 init 创建的局部变量
function displayName() {
// displayName() 是内部函数,它创建了一个闭包
console.log(name); // 使用在父函数中声明的变量
}
displayName();
}
init();
来向人们解释闭包由 在一个作用域中可以访问另一个函数内部的局部变量的函数 形成。
这是一个相当抽象的概念,因为在 JavaScript 中 , function 在语法上是混淆的。
你可以在 Rust 中写一段类似的代码
fn main() {
let mut x = 5;
fn add_one(n:i32) -> i32 {
x += 1
}
add_one();
println!("The value of x is: {}", x);
}
很遗憾,这段代码编译不会通过,因为你不能在 Rust 的 fn 中捕获外部的变量。
Rust 中的闭包
Rust中,有专门的闭包语法,你可以像这样完成上述代码的功能
fn main() {
let mut x = 5;
let mut add_one = |n:i32| {
x += n;
};
add_one(1);
println!("The value of x is: {}", x);
}
闭包语法
/**
* 闭包语法
* |参数| -> 返回值 {
* 函数体
* }
*/
let mut add_one_and_return = |n:i32| -> i32 {
x = x + n;
x
};
如果闭包体的最后一行是一个表达式(没有显式的 return 语句),那么闭包会默认返回该表达式的结果。
若 Rust 能够推断出闭包的类型, 我们可以最终将闭包简写为 |参数| 函数体 的形式。
let x = 5;
let add_one_and_return_new = |n| x + n;
let y = add_one_and_return_new(1);
println!("{}", y);
闭包提供了简洁、灵活且强大的函数定义方式,这点特别像 JavaScript 中的箭头函数,Java 中的 Lambda 表达式, 尽管它们在语法上有一些差异。
闭包类型
Rust 会根据闭包使用的上下文推导出闭包的类型。闭包的类型分为三种:
Fn
Fn:表示一个不可变的闭包(它不会改变捕获的环境)。
let closure: fn(i32, i32) -> i32 = |a, b| a + b;
let result = closure(1, 2);
println!("The result is: {}", result);
FnMut
FnMut:表示一个可以修改环境的闭包。
let mut x = 5;
let mut add_one = |n:i32| {
x += n;
};
add_one(1);
println!("The value of x is: {}", x);
FnOnce
FnOnce:表示一个消耗环境的闭包。
let s = String::from("Hello");
// `move` 关键字将 `s` 的所有权移入闭包
let consume_string = move || {
println!("Consumed string: {}", s);
};
// 调用闭包,`s` 的所有权已被移入闭包
consume_string(); // 输出: Consumed string: Hello
// 此时,`s` 的所有权已被消耗,无法再访问 `s`,以下代码会导致编译错误
// println!("{}", s); // 编译错误: use of moved value: `s`
在闭包定义中使用了 move 关键字,它使得闭包捕获外部变量 s 的所有权而不是借用它。
闭包应用
在很多方法中,支持闭包作为参数,比如 map、filter、sort 等。
let muns = [1, 2, 3, 4, 5];
// 使用 `filter` 方法过滤数组中的偶数
let even_muns = muns.iter().filter(|&x| x % 2 == 0);
// 遍历过滤后的数组
for mun in even_muns {
println!("{}", mun);
}
定义解读
在编辑器中点击 filter 方法,你会发现它的定义如下
pub trait Iterator {
type Item;
// ...
fn filter<P>(self, predicate: P) -> Filter<Self, P>
where
Self: Sized,
P: FnMut(&Self::Item) -> bool,
{
Filter::new(self, predicate)
}
// ...
}
-
fn filter<P>(self, predicate: P) -> Filter<Self, P>表示filter是一个实例方法,它接受参数P,返回一个Filter(迭代器类型, 便于链式调用)。 -
where关键字用于为泛型类型参数指定更多的约束Self: Sized说明Self实例类型的大小是已知的P: FnMut(&Self::Item) -> bool说明predicate: P参数是一个可变的闭包,它的参数是Self::Item(迭代中Item要素) 的 引用 ,返回值是一个布尔值。
-
Filter::new(self, predicate)表示函数体返回Filter迭代器的新实例由self和predicate构建而成
使用解读
这是使用的闭包是 |&x| x % 2 == 0 , 你可能会疑惑,为什么是 |&x| 而不是 |x| ?
这是因为 muns.iter() 返回值是 Iterator<Item = &i32> , 即 Iterator::Item = &i32, Self::Item = &i32 .
由于在闭包的参数 &Self::Item 为 Item 的不可变引用。 |x| 实际为 &&i32 (&i32 的引用), 这里使用 |&x| 结构了一层引用。
你在编辑器的类型提示中,你可以更明确这一行为
结
- 闭包提供了捕获外部的变量能力
- 闭包提供了简洁、灵活且强大的函数定义方式
- 类型约束(
Fn、FnMut、FnOnce)提高了代码的安全性和性能