1.闭包
1.1 函数式编程
函数式编程风格通常包含将函数作为参数值或其他函数的返回值、将函数赋值给变量以供之后执行等等。
1.2 闭包与函数
不同于函数,闭包允许捕获被定义时所在作用域中的值。
1 2 3 4 5 6 7
| fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 }; let add_one_v4 = |x| x + 1 ;
|
但是可以在需要一个闭包作为参数的函数中使用函数作为参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| pub fn test_param<T, F>(val:T, f: F) -> T where F: FnOnce() { f(); val }
fn test_fun() { println!("I'm function"); }
fn main() { let my_closure = || println!("I'm closure"); test_param(123, my_closure); test_param(456, test_fun); }
|
1.3 捕获变量
1.3.1 不可变引用
定义一个捕获名为 list
的 vector 的不可变引用的闭包,因为只需不可变引用就能打印其值:
1 2 3 4 5 6 7 8
| let list = vec![1, 2, 3]; println!("Before defining closure: {:?}", list);
let only_borrows = || println!("From closure: {:?}", list);
println!("Before calling closure: {:?}", list); only_borrows(); println!("After calling closure: {:?}", list);
|
此时没有问题,因为可以存在多个不可变引用。
1.3.2 可变引用
定义一个捕获名为 list
的 vector 的可变引用的闭包,闭包体让它向 list
vector 增加一个元素:
1 2 3 4 5 6 7 8 9
| let mut list = vec![1, 2, 3]; println!("Before defining closure: {:?}", list);
let mut borrows_mutably = || list.push(7);
println!("After calling closure: {:?}", list); borrows_mutably(); println!("After calling closure: {:?}", list);
|
上面报错的原因是在定义闭包和调用闭包这段过程,push
操作得到的可变引用会始终存在,此时再调用println!
宏获得不可变引用就会出问题。
1.3.3 获取所有权
需要在闭包中指定move
关键字:
1 2 3 4 5 6 7 8 9 10
| use std::thread;
fn main() { let list = vec![1, 2, 3]; println!("Before defining closure: {:?}", list);
thread::spawn(move || println!("From thread: {:?}", list)) .join() .unwrap(); }
|
这种写法在线程过程函数中会很常见,因为子线程和主线程的结束时间不一致,将所有权交给子线程会更安全。
1.4 移入移出被捕获的值
一旦闭包捕获了定义它的环境中一个值的引用或者所有权(也就影响了什么会被移 进 闭包,如有),闭包体中的代码定义了稍后在闭包计算时对引用或值如何操作(也就影响了什么会被移 出 闭包,如有)。闭包体可以做以下任何事:将一个捕获的值移出闭包,修改捕获的值,既不移动也不修改值,或者一开始就不从环境中捕获值。
闭包捕获和处理环境中的值的方式影响闭包实现的 trait。Trait 是函数和结构体指定它们能用的闭包的类型的方式。取决于闭包体如何处理值,闭包自动、渐进地实现一个、两个或三个 Fn
trait。
FnOnce
适用于能被调用一次的闭包,所有闭包都至少实现了这个 trait,因为所有闭包都能被调用。一个会将捕获的值移出闭包体的闭包只实现 FnOnce
trait,这是因为它只能被调用一次。
FnMut
适用于不会将捕获的值移出闭包体的闭包,但它可能会修改被捕获的值。这类闭包可以被调用多次。
Fn
适用于既不将被捕获的值移出闭包体也不修改被捕获的值的闭包,当然也包括不从环境中捕获值的闭包。这类闭包可以被调用多次而不改变它们的环境,这在会多次并发调用闭包的场景中十分重要。
示例:Option<T>
上的 unwrap_or_else
方法的定义
1 2 3 4 5 6 7 8 9 10 11
| impl<T> Option<T> { pub fn unwrap_or_else<F>(self, f: F) -> T where F: FnOnce() -> T { match self { Some(x) => x, None => f(), } } }
|
有些闭包具有特殊的属性,比如在闭包内调用了Vec.push(val)
,来将 val
的所有权转移,此时这个闭包就是 FnOnce
的,因为只能调用一次。
如果闭包内只会修改,但是不会夺取所有权,此时这个闭包就是FnMut
的,可以调用多次。
2.迭代器
2.1 新建迭代器
2.1.1 迭代器可变性
这里的可变性是指迭代器是否可以遍历。
1 2 3 4 5 6 7
| let v1 = vec![1, 2, 3]; let v1_iter = v1.iter();
let v1 = vec![1, 2, 3]; let mut v1_iter = v1.iter();
|
2.1.2 元素可变性
这里的可变性是指这个迭代器对原来的元素的影响程度。
1 2 3 4 5 6 7 8 9 10 11
| let v1 = vec![1, 2, 3]; let v1_iter = v1.iter();
let mut v1 = vec![1, 2, 3]; let mut v1_iter = v1.iter_mut();
let mut v1 = vec![1, 2, 3]; let mut v1_iter = v1.into_iter();
|
2.2 消费迭代器
Rust迭代器默认都是惰性的,即需要手动来修改里面的游标进行消费。每个迭代器类型必须实现next
trait。
1 2 3 4 5 6 7 8 9 10 11
| #[test] fn iterator_demonstration() { let v1 = vec![1, 2, 3];
let mut 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
方法可以消费迭代器,就是逐个向下遍历,返回一个Option<T>
,如果遍历到末尾则返回None。
2.3 迭代适配器
Iterator
trait 中定义了另一类方法,被称为 迭代器适配器(iterator adaptors),他们允许我们将当前迭代器变为不同类型的迭代器。如map
方法:
1 2 3
| let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
|
map
方法在每个元素上调用闭包来处理元素并返回一个新的迭代器。上面代码会报警告,因为所有的迭代器都是惰性的,需要进行消费。
collect
方法可以消费迭代器并将结果收集到一个数据结构中:
1 2 3 4 5
| let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
|
2.4 filter
1 2 3
| fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> { shoes.into_iter().filter(|s| s.size == shoe_size).collect() }
|
可以收集某个容器中满足指定条件的元素序列。