一. 简述

Rust在设计中受到了函数式编程很大的影响。常见的函数式风格编程通常包括将函数当作参数、将函数作为其他函数的返回值或将函数赋给变量已备之后执行等。

二. 闭包

Rust中的闭包是一种可以存入变量或作为参数传递给其他函数的匿名函数。我们可以在一个地方创建闭包,然后在不同的上下文环境中调用该闭包完成运算。闭包可以从定义它的作用域中捕获值。

Rust中的闭包,也叫做lambda表达式或者lambda,是一类能够捕获周围作用域中变量的函数。

下面我们看一个闭包和普通函数的区别:

pub fn fun1(i: i32) -> i32 { i + 1 } // 定义加一的函数

#[cfg(test)]

mod tests {

use super::*;

#[test]

fn function_test() {

let fun2 = |i: i32| -> i32 { i + 1 }; // 加一的闭包

let fun3 = |i| { i + 1 }; // 这个rust可以自动类型推断

assert_eq!(2, fun1(1));

assert_eq!(2, fun2(1));

assert_eq!(2, fun3(1));

}

}

2.1. 捕获上下文

闭包本质上很灵活,闭包可以捕获上下文环境,即可移动,又可借用。闭包可以通过以下方式捕获变量:

通过引用: &T 通过可变引用: &mut T 通过值: T

闭包优先通过引用来捕获变量,并且仅在需要时使用其他方法。

2.1.1. 通过引用

下面我们可以先看一个捕获上下文:引用

#[test]

fn function_test() {

let color = String::from("green");

let print = || println!("color: {}", color); // 这个闭包打印 `color`。它立即借用(通过引用,`&`)`color`并将该借用和闭包本身存储到print变量中。`color`会一直保持被借用状态知道`print`离开作用域

print();

}

2.1.2. 通过可变引用

接着我们看一下通过可变引用捕获变量:

#[test]

fn function_test() {

let mut count = 0;

let mut inc = || {

count += 1;

println!("`count`: {}", count);

};

inc();

}

上面的闭包的例子使count的值增加,当前闭包需要拿到&mut count,在闭包inc的前面添加mut,这是因为闭包利民啊存储着一个&mut变量。调用闭包时,该变量的变化就意味着闭包内部发生了变化。因此闭包需要是可变的。下面我们看一个错误的情况,这样也可以证明inc闭包使用的可变引用!

#[test]

fn function_test() {

let mut count = 0;

let mut inc = || {

count += 1;

println!("`count`: {}", count);

};

inc();

let count_2 = &count; // @1 error cannot borrow `count` as immutable because it is also borrowed as mutable

inc();

}

此时只要在@1之后不再使用inc闭包,我们就可以可变引用或者不可变引用了,如下:

#[test]

fn function_test() {

let mut count = 0;

let mut inc = || {

count += 1;

println!("`count`: {}", count);

};

inc();

let count_2 = &count;

assert_eq!(&1, count_2); // OK

// let count_3 = &mut count;

// *count_3 = 10;

// assert_eq!(&10, count_3); // OK

}

2.1.3. 通过值

#[test]

fn fun_test() {

let x = Box::new(3);

let consume = || {

println!("movable: {:?}", x);

mem::drop(x);

};

consume();

}

2.1.4. move

在竖线之前使用move会强制闭包取得被捕获变量的所有权:

#[test]

fn move_test() {

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

let contains = move | needle | haystack.contains(needle);

assert_eq!(true, contains(&1));

assert_eq!(false, contains(&4));

assert_eq!(3, haystack.len()); // 此时测试失败,是因为借用检查不允许在变量被移动走之后再继续使用它

}

2.2. 作为输入参数

虽然Rust无需类型说明就能在大多数完成变量捕获,但是编写函数时,这种模糊写法是不允许的。当以闭包作为输入参数时,必须指出闭包的完整类型,它是通过使用以下trait中的一种来指定的。其受限制程度按以下顺序递减,闭包大体分为三种类型:

Fn:表示不会方式为通过引用(&T:只是获取了不可变的引用变量)的闭包; FnMut:表示捕获方法为通过可变引用(&mut T:只获取可变引用的变量)的闭包; FnOnce:表示捕获方法为通过值(T:拿到变量的所有权而非借用)的闭包;

下面我们通过例子看一下

2.2.1. FnOnce

这种类型的闭包会获取变量的所有权,生命周期只能在当前作用域,之后就会释放了。

#[derive(Debug)]

struct Example<'a> {

data: &'a str

}

impl<'a> Drop for Example<'a> {

fn drop(&mut self) {

println!("释放Example");

}

}

fn fn_once(func: F) where F: FnOnce() {

println!("fn_once 开始执行之前");

func();

// func(); @1

println!("fn_once 开始执行之后");

}

#[cfg(test)]

mod tests {

use super::*;

#[test]

fn fn_once_test() {

let example = Example { data: "hello" };

let f = || println!("fn once 调用: {:?}", example);

fn_once(f);

println!("执行完毕");

}

}

此时执行测试:

fn_once 开始执行之前

fn once 调用: Example { data: "hello" }

fn_once 开始执行之后

执行完毕

释放Example

如果把@1的代码放开编译器就会报错,原因我们可以从FnOnce的定义中找到,参数类型是self,在闭包执行第一次之后之前捕获的变量就释放了,无法执行第二次了。

pub trait FnOnce {

type Output;

extern "rust-call" fn call_once(self, args: Args) -> Self::Output;

}

2.2.2. FnMut

我们将上面的例子增加一个fn_mut方法和单元测试

fn fn_mut(num: u32, mut func: F) where F: FnMut(u32) {

println!("fn_once 开始执行之前");

func(num);

func(num + 1);

println!("fn_once 开始执行之后");

}

#[test]

fn fn_mut_test() {

let mut example = Example { data: "hello" };

let f = |num: u32| {

example.data = if num > 1 { "hello world" } else { "world" };

println!("fn_mut 调用: {:?}", example);

};

fn_mut(1, f);

println!("执行完毕");

}

执行单元测试如下:

fn_mut 开始执行之前

fn_mut 调用: Example { data: "world" }

fn_mut 调用: Example { data: "hello world" }

fn_mut 开始执行之后

执行完毕

释放Example

接着我们在看一下FnMut的定义,很明显我们看到参数类型是:&mut self,这种类型的闭包是可变借用,会改变变量,但不会释放该变量。

pub trait FnMut: FnOnce {

extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;

}

2.2.3. Fn

最后看一下Fn的源码,参数类型是&self,此类型的闭包是不变借用,不会改变变量,也不会释放该变量。

pub trait Fn: FnMut {

extern "rust-call" fn call(&self, args: Args) -> Self::Output;

}

接着我们在上面的例子加上一个方法和单元测试:

fn fn_fn(func: F) where F: Fn() {

println!("fn_once 开始执行之前");

func();

func();

println!("fn_once 开始执行之后");

}

#[test]

fn fn_test() {

let example = Example { data: "hello" };

let f = || {

println!("fn once 调用: {:?}", example);

};

fn_fn(f);

println!("执行完毕");

}

三. 迭代器

在设计模式的迭代器模式允许你依次为序列中每一个元素执行某些任务。迭代器会着这个过程中负责便利每一个元素并决定序列合适结束。只要使用了迭代器,我们就可以避免手动去实现这些逻辑。

在Rust中,迭代器时惰性的。当我们创建迭代器后,除非你主动调用方法来消耗并使用迭代器,否则它们不会产生任何的实际效果。Rust中的集合提供了三个公共方法创建迭代器:iter()、iter_mut()和into_iter(),分别用于迭代&T(引用)、&mut T(可变引用)和T(值)。

Rust的for循环器时时迭代器语法糖。

3.1. 迭代器的使用

这里我们先使用vec!创建一个动态数组,使用迭代器遍历:

#[test]

fn iter_test() {

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

let v1_iter = v1.iter();

for item in v1_iter {

println!("{}", item);

}

}

上面的代码时迭代器最常用的使用方式。Rust中所有的迭代器都实现了定义于标准库中Iterator trarit。

pub trait Iterator {

type Item;

fn next(&mut self) -> Option;

// 省略其他默认实现

}

这里的type Item和Self::Item,它们定义了trait的关联类型,后续章节会填坑。

Iterator需要我们实现next方法,它会在每次被调用时返回一个包裹在Some中的迭代器元素,并在迭代结束的时候返回None。

#[test]

fn iter_test() {

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

let mut v1_iter = v1.iter(); // 此处v1_iter必须是可变的

assert_eq!(v1_iter.next(), Some(&1));

assert_eq!(v1_iter.next(), Some(&2));

assert_eq!(v1_iter.next(), Some(&3));

assert_eq!(v1_iter.next(), None);

}

这里每次调用next方法改变了迭代器内部记录的序列位置的状态。在for循环中没有要求v1_iter可变,是因为循环取得了v1_iter的所有权并在内部使得它可变了。

这里iter()拿到的是一个不可变引用的迭代器,我们通过next取得的值实际上是指向动态数组中各个元素的不可变引用,如果我们需要一个取得v1所有权并返回元素本省的迭代器,此时我们就可以使用into_iter();如果我们需要可变引用的迭代器,那么我们可以使用iter_mut()方法。

3.2. 消费和适配

标准库中提供了很多默认实现的方法,其中那些调用next的方法也被称为消耗适配器,因为它们同样消耗了迭代器本身;还定义了了另外的一些被称为迭代器适配器,这些可以使将已有的迭代器转换称为其他不同类型的迭代器,我们可以链式调用多个迭代器适配器完成了一些复杂的操作。但是需要注意所有迭代器都是惰性的,所有你必须调用一个消耗适配器的方法才能从迭代器适配器中获取结果。

3.2.1. 消耗适配器

消耗迭代器之后无法继续被随后的代码使用。例子:

#[test]

fn iter_consume_test() {

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

let total: i32 = v1.iter().sum();

assert_eq!(15, total);

}

3.2.2. 迭代适配器

我们通过map方法生成的新的迭代器并返回的结果收集到一个动态数组中。

#[test]

fn iter_consume_test() {

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

let x1: Vec = v1.iter().map(|x| x + 1).collect();

println!("{:?}", v1); // [1, 2, 3, 4, 5]

println!("{:?}", x1); // [2, 3, 4, 5, 6]

}

下面我们在看一个复杂的例子:

#[derive(PartialEq, Debug)]

struct Shoe<'a> {

size: u32,

style: &'a str,

}

fn shoes_in_my_size(shoes: Vec, shoe_size: u32) -> Vec {

shoes.into_iter() // 创建一个可以获取数组所有权的迭代器

.filter(|s| s.size == shoe_size) // 过滤值适配成一个新的迭代器

.collect() //

}

#[test]

fn filters_by_size() {

let shoes = vec![

Shoe { size: 10, style: "sneaker" },

Shoe { size: 13, style: "sandal" },

Shoe { size: 10, style: "boot" },

];

let in_my_size = shoes_in_my_size(shoes, 10);

assert_eq!(in_my_size,

vec![

Shoe { size: 10, style: "sneaker" },

Shoe { size: 10, style: "boot" },

]

)

}

3.3. 自定义迭代器

下面我们看看如果自定义实现我们的迭代器,其实很简单我们只需要提供一个next方法的定义即可实现Iterator。一旦完成该方法,它就可以使用一起拥有默认实现的Iterator提供的方法。我们先定义一个结构体:

#[derive(Debug)]

struct Counter {

count: u32,

max: u32,

}

impl Counter {

fn new(max: u32) -> Counter {

Counter { count: 0, max }

}

}

接着我们实现Iterator的next方法:

impl Iterator for Counter {

type Item = u32;

fn next(&mut self) -> Option {

self.count += 1;

if self.count <= self.max {

Some(self.count)

} else {

None

}

}

}

此时我们就可以拥有了一个迭代器,下面单元测试:

#[test]

fn calling_next_directly() {

let mut counter = Counter::new(3);

assert_eq!(counter.next(), Some(1));

assert_eq!(counter.next(), Some(2));

assert_eq!(counter.next(), Some(3));

assert_eq!(counter.next(), None);

}

下面我们进行一些复杂的操作:将一个Counter实例产生的值与另一个Counter跳过首元素的值一一配对,接着将配对的两个值相乘,最后再对乘积能被3整除的那些数字求和。

#[test]

fn using_other_iterator_trait_methods() {

let sum: u32 = Counter::new(3)

.zip(Counter::new(5).skip(1))

.map(|(a, b)| a * b)

.filter(|x| x % 3 == 0)

.sum();

assert_eq!(18, sum);

}

这里zip方法只会产生三对值,它在两个迭代器中任意一个返回None时结束迭代,所有不会出现(None, 4)。

更多的关于自定义迭代器的创建可以参看:手把手带你实现链表。

3.4. 迭代器性能

Rust在迭代器和闭包中做很多优化,最终产出的代码极为高效。所有我们完全无所畏惧的使用迭代器和闭包!它们既能够让代码在观感上保持高层次的抽象,又不会因此带来任何运行时性能损耗。

3.5. 常用方法汇总

下面我们例子一些在迭代器中常用的方法:

方法功能all/anyall测试迭代器的每个元素是否与谓词匹配;any相反collect将迭代器转换为集合copied创建一个迭代器,该迭代器将复制其所有元素count消耗迭代器,计算迭代次数并返回它enumerate创建一个迭代器,该迭代器给出当前迭代次数以及下一个值(i, val);其中 i 是当前迭代索引,val 是迭代器返回的值eq/ne/lt/gt/le/ge两个迭代器元素的比较filter创建一个迭代器,该迭代器使用闭包确定是否应产生元素find搜索满足谓词的迭代器的元素for_each在迭代器的每个元素上调用一个闭包;等效于for循环inspect调试代码的时候使用last消耗迭代器,返回最后一个元素map通过其参数将一个迭代器转换为另一个迭代器: 实现 FnMutmax/``min返回迭代器最大/小元素nth返回迭代器第几个元素position/rposition在迭代器中搜索元素,并返回其索引,rposition是从右侧开始搜索product遍历整个迭代器,将所有元素相乘rev反转fold累加skip创建一个跳过前 n 个元素的迭代器sum对迭代器元素求和take创建一个迭代器,它产生第一个 n 元素,如果底层迭代器提前结束,则产生更少的元素zip/unzip将两个迭代器压缩为成对的单个迭代器,unzip相反

更多的使用方法可以去看中文的标准文档:https://rustwiki.org/zh-CN/std/iter/trait.Iterator.html#

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: