RUST - 闭包&迭代器

260 阅读5分钟

闭包

闭包:一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值

Rust 中的闭包(closure),也叫做 lambda 表达式或者 lambda,是一类能够捕获周围作用域中变量的函数。不像普通函数,闭包可以对参数和返回类型进行推断,大多数时候都不需要写出来。以下定义都是合法的:

|| 42;
|x| x + 1;
|x:i32| x + 1;
|x:i32| -> i32 { x + 1 };

它们的语法和能力使它们在临时(on the fly)使用时相当方便。调用一个闭包和调用一个函数完全相同,不过调用闭包时,输入和返回类型两者都可以自动推导,而输入变量名必须指明。

其他的特点包括:

  • 声明时使用 || 替代 () 将输入参数括起来。
  • 函数体定界符({})对于单个表达式是可选的,其他情况必须加上。
  • 有能力捕获外部环境的变量。
fn main() {
// 通过闭包和函数分别实现自增。
// 译注:下面这行是使用函数的实现
fn function (i: i32) -> i32 { i + 1 }


// 闭包是匿名的,这里我们将它们绑定到引用。
// 类型标注和函数的一样,不过类型标注和使用 `{}` 来围住函数体都是可选的。
// 这些匿名函数(nameless function)被赋值给合适地命名的变量。
let closure_annotated = |i: i32| -> i32 { i + 1 };
let closure_inferred = |i | i + 1 ;


// 译注:将闭包绑定到引用的说法可能不准。
// 据[语言参考](https://doc.rust-lang.org/beta/reference/types.html#closure-types)
// 闭包表达式产生的类型就是 “闭包类型”,不属于引用类型,而且确实无法对上面两个
// `closure_xxx` 变量解引用。


let i = 1;
// 调用函数和闭包。
println!("function: {}", function(i));
println!("closure_annotated: {}", closure_annotated(i));
println!("closure_inferred: {}", closure_inferred(i));


// 没有参数的闭包,返回一个 `i32` 类型。
// 返回类型是自动推导的。
let || 1;
println!("closure returning one: {}", one())

详细了解:闭包

迭代器

如果某一个类型如果实现了IntoIterator特征,它可以通过 into_iteriter 等方法变成一个迭代器。

let arr = [1, 2, 3];
for v in arr {
println!("{}",v);
}

以上代码中数组自身实现了 IntoIterator 特征,Rust 通过 for 语法糖,自动把实现了该特征的数组类型转换为迭代器(你也可以为自己的集合类型实现此特征),最终让我们可以直接对一个数组进行迭代。当然我们也可以显式的将一个数组转化为迭代器

let arr = [1, 2, 3];
for v in arr.into_iter() {
println!("{}", v);
}

上述代码中除了可以使用into_iter外也可以使用iter 和iter_mut ,区别如下:

  • into_iter 会夺走所有权
  • iter 是借用
  • iter_mut 是可变借用
fn main() {
let values = vec![1, 2, 3];


for v in values.into_iter() {
println!("{}", v)
}


// 下面的代码将报错,因为 values 的所有权在上面 `for` 循环中已经被转移走
// println!("{:?}",values);


let values = vec![1, 2, 3];
let _values_iter = values.iter();


// 不会报错,因为 values_iter 只是借用了 values 中的元素
println!("{:?}", values);


let mut values = vec![1, 2, 3];
// 对 values 中的元素进行可变借用
let mut values_iter_mut = values.iter_mut();


// 取出第一个元素,并修改为0
if let Some(v) = values_iter_mut.next() {
*v = 0;
}


// 输出[0, 2, 3]
println!("{:?}", valu

惰性初始化

在 Rust 中,迭代器是惰性的,意味着如果你不使用它,那么它将不会发生任何事:


let v1 = vec![1, 2, 3];


let v1_iter = v1.iter();


for val in v1_iter {
println!("{}", val);

在 for 循环之前,我们只是简单的创建了一个迭代器 v1_iter,此时不会发生任何迭代行为,只有在 for 循环开始后,迭代器才会开始迭代其中的元素,最后打印出来。

这种惰性初始化的方式确保了创建迭代器不会有任何额外的性能损耗,其中的元素也不会被消耗,只有使用到该迭代器的时候,一切才开始。

next 方法

对于 for 如何遍历迭代器,还有一个问题,它如何取出迭代器中的元素?

先来看一个特征:


pub trait Iterator {
type Item;


fn next(&mut self) -> Option<Self::Item>;


// 省略其余有默认实现的方法

呦,该特征竟然和迭代器 iterator 同名,难不成。。。没错,它们就是有一腿。迭代器之所以成为迭代器,就是因为实现了 Iterator 特征,要实现该特征,最主要的就是实现其中的 next 方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item

因此,之前问题的答案已经很明显:for 循环通过不停调用迭代器上的 next 方法,来获取迭代器中的元素。

既然 for 可以调用 next 方法,是不是意味着我们也可以?来试试:


fn main() {
let arr = [1, 2, 3];
let mut arr_iter = arr.into_iter();


assert_eq!(arr_iter.next(), Some(1));
assert_eq!(arr_iter.next(), Some(2));
assert_eq!(arr_iter.next(), Some(3));
assert_eq!(arr_iter.next(), None);

果不其然,将 arr 转换成迭代器后,通过调用其上的 next 方法,我们获取了 arr 中的元素,有两点需要注意:

  • next 方法返回的是 Option 类型,当有值时返回 Some(i32),无值时返回 None
  • 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了 Some(1)Some(2)Some(3)
  • 手动迭代必须将迭代器声明为 mut 可变,因为调用 next 会改变迭代器其中的状态数据(当前遍历的位置等),而 for 循环去迭代则无需标注 mut,因为它会帮我们自动完成

总之,next 方法对迭代器的遍历是消耗性的,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 None

Iterator 和 IntoIterator 的区别

这两个其实还蛮容易搞混的,但我们只需要记住,Iterator 就是迭代器特征,只有实现了它才能称为迭代器,才能调用 next

而 IntoIterator 强调的是某一个类型如果实现了该特征,它可以通过 into_iteriter 等方法变成一个迭代器。