闭包
我们可以使用闭包(closures)创建行为的抽象:
use std::thread;
use std::time::Duration;
fn generate_workout(intensity: u32, random_number: u32) {
let expensive_closure: fn(u32) -> u32 = |num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num};
if intensity < 25 {
println!("Today, do {} pushups!", expensive_closure(intensity));
println!("Next, do {} situps!", expensive_closure(intensity));
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!("Today, run for {} minutes!", expensive_closure(intensity));
}
}
}
fn main() {
7, 10)
generate_workout(}
闭包的语法如下:
|变量1, 变量2, ...| {
// 中间的语句
// 最后一行 为返回值
}
若只有一行语句/返回值,大括号可以省略:
|num| num * num * num
函数类型这样书写:
fn(输入1, 输入2, ...) -> 返回值
也可以在闭包内标注:
let sum = |x: i32, y: i32| -> i32 {
+ y
x }
高阶函数,配合泛型:
use std::fmt::Display;
use std::thread;
use std::time::Duration;
fn main() {
let creation = || 1000 * 123012;
let x = create_and_print(creation);
let y = create_and_print(creation);
println!("{x}, {y}");
}
fn create_and_print<F, T>(creation: F) -> T
where
: Display,
T: Fn() -> T,
F{
let created = creation();
println!("{}", created);
created}
在结构体中也可以使用高阶函数,下面是一个简单的 Lazy
实现:
struct Image<'img> {
: Vec<u8>,
content}
impl<'img> Image<'img> {
fn new(name: &'img str, content: Vec<u8>) -> Image<'img> {
{ content }
Image }
}
struct Lazy<'lazy, F, T>
where
: Fn() -> T,
F{
: F,
calculation: Option<&'lazy T>,
value}
impl<'lazy, F, T> Lazy<'lazy, F, T>
where
: Fn() -> T,
F{
fn new(calculation: F) -> Lazy<F, T> {
{
Lazy ,
calculation: None,
value}
}
fn value(&mut self) -> &T {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)();
self.value = Some(v);
self.value.unwrap()
}
}
}
}
fn main() {
let mut lazy = Lazy::new(|| Image {
: vec![123, 123, 123, 123, 123, 123, 123, 123, 123, 123],
content});
.value();
lazy}
闭包捕获
闭包可以通过多种方式捕获作用域中的值(也就是环境),所以函数具有几个类型:
FnOnce
:为了消费捕获到的变量,必须获得其所有权,并在定义时移动。Once
意味着不能多次获取相同变量的所有权,所以只能被调用一次。FnMut
:获取可变的借用,可以改变环境Fn
:获取不可变的借用值
由于所有闭包都可以至少被调用一次,所以所有闭包都实现了
FnOnce
。若是没有捕获变量所有权的闭包,同样也实现了
FnMut
。而没有对变量进行可变访问的包,也实现了
Fn
。
如果希望闭包强制获得变量所有权,可以使用 move
关键字:
let x = vec![1, 2, 3];
let equal_to_x = move |z: Vec<i32>| z == x;
// println!("can't use x here: {:?}", x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
上述例子因为闭包取得了 x
的所有权,所以在闭包定义后不可再访问。
大部分时候,当 trait 需要一个闭包时,可以从 Fn
开始,后面根据需求,可以改为 FnMut
或
FnOnce
。
迭代器
迭代器允许我们编写函数式风格的代码。
在 Rust 中,迭代器是惰性的。只有调用
collect()
for_each()
等终端操作符后,所有计算才会真正开始。
迭代器的定义如下:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// ...
}
只使用基本方法只能是枯燥无味的:
fn main() {
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);
}
但有一系列链式方法供我们调用。Rust 称其为消费适配器(consuming adaptors)
fn iterator_sum() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
// 此处不可再使用 v1_iter
assert_eq!(total, 6);
}
每个链式方法都会获取接收者的所有权。所以在调用 sum()
后不可再次使用 v1_iter
。
常用方法
如果你使用过一些具有函数式风格的语言,如 Java 8 新增的 Stream,Kotlin 的一系列集合方法,或者 Scala、Clojure、Haskell 等更加纯函数式的语言。你对这些方法应该不陌生。
这里介绍几种最常见的。
foreach
这是一个终端操作符,用来遍历所有元素。
fn main() {
let y = vec![1, 2, 3];
.iter().for_each(|x| println!("{x}"));
y}
filter
过滤元素。
fn main() {
let vec1 = vec![-100, 1, 2, 3, 4, 5, 6, 7, 8];
vec1.iter()
.filter(|&&i| i >= 10)
.for_each(|i| println!("{i}"));
}
请注意,这里的 i
是引用的引用。Vector
是第一层,而迭代器是第二层。所以有两层引用。(如果 Vector
中存放的是字符串字面量,甚至会有 3 层引用……)
在闭包中使用 &&i
表示在声明时解引用。和
filter(|i| **i >= 10)
实则是一样的
map
将元素映射。
fn main() {
let vec1 = vec!["asdf", "asdfasdf", "🤣"];
vec1.iter()
.map(|&i| i.len())
.for_each(|i| println!("{i}"));
}
enumerate
将元素映射为 (索引, 内容)
的元组。
fn main() {
let vec1 = vec!["asdf", "asdfasdf", "🤣"];
vec1.iter()
.enumerate()
.for_each(|(idx, i)| println!("The content of element[{idx}]: {i}"))
}
其他的方法,可以自行查看标准库中的文档。
自定义迭代器
我们可以为结构体实现 Iterator
trait 来自定义迭代器。
如这样:
impl Counter {
fn new() -> Counter {
{ count: 0 }
Counter }
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
自定义的迭代器仍然可以享受一系列便捷方法:
fn main() {
let sum: u32 = Counter::new()
.zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
assert_eq!(18, sum);
}
零成本抽象
通过函数内联等编译时优化。可以避免创建闭包对象。将迭代器转换成普通的语句流。
在《Rust 程序设计语言》中,作者对循环和迭代器进行了测试。结果表明迭代器并不比循环慢。甚至还要稍微快一点。
以上,就是关于闭包的基本内容了。