👈 11 格式化输出
闭包
用闭包简化代码
闭包(Closure):一种匿名函数,可以赋值给变量,也可作为参数传递给其他函数,不同于函数的是,它可以捕获调用者作用域中的值:
let x = 1;
let sum = |y| x + y;
assert_eq!(3, sum(2));
sum
是一个闭包,它有一个入参 y
,同时捕获了作用域中 x
的值,调用 sum(2)
意味着将 2
与 1
相加,返回 3
。
|param1, param2, ...| {
语句 1;
语句 2;
返回表达式
}
|params ...| 返回表达式
let sum = ||...
是将闭包本身(而不是其返回值)赋给变量 sum
,可以像调用函数一样调用 sum()
。
闭包的类型推导
闭包不像函数作为 API 对外提供,因此无需手动标注参数及返回值类型。
实现“输入 x
,返回 x + 1
”的几个函数及闭包:
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;
结构体中的闭包
实现一个简易缓存,用一个闭包获取值,再存入一个变量,缓存对象可用结构体表示:
struct Cacher<T>
where
T: Fn(u32) -> u32, // 特征约束, 表示 T 是一个闭包或函数类型
{
query: T,
value: Option<u32>,
}
impl<T> Cacher<T>
where
T: Fn(u32) -> u32,
{
fn new(query: T) -> Cacher<T> {
Cacher {
query,
value: None,
}
}
// 先查询缓存值 `self.value`, 若不存在, 则调用 `query` 加载
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.query)(arg); // 闭包调用
self.value = Some(v);
v
}
}
}
}
捕获作用域中的值
fn main() {
let delta = 2;
let add = |x| x + delta;
let a = 1;
println!("{}", add(a));
}
delta
并不是 add
的参数,但后者位于它的作用域内,因此可将它捕获,而函数就无法做到:
fn main() {
let delta = 2;
fn add(x: i32) -> i32 {
x + delta // error
}
let a = 1;
println!("{}", add(a));
}
// error[E0434]: can't capture dynamic environment in a fn item
不能对同一闭包使用不同类型进行调用:
let a_closure = |x| x;
let s = a_closure(String::from("hello")); // ok // 确定 x: String
let n = a_closure(5); // error
捕获可变引用的闭包:
let mut list = vec![1, 2, 3];
println!("before defining closure: {:?}", list); // 不可变借用, 结束
let mut borrows_mutably = || list.push(7); // 可变借用, 开始
// 这里不能再有 println("before calling closure: {:?}", list);
borrows_mutably(); // 可变借用, 结束
println!("after calling closure: {:?}", list); // 不可变借用, 结束
move
强制获取所有权
move
强制闭包获取所有权。
新线程可能在主线程剩余部分执行完之前执行完,或者也可能主线程先执行完。如果主线程维护了 list
的所有权但却在新线程之前结束并且丢弃了 list
,则子线程中的不可变引用将失效,此时需要强制将 list
的所有权移动到新线程(也是编译器要求)。
let list = vec![1, 2, 3];
println!("before defining closure: {:?}", list);
thread::spawn(move || println!("from thread: {:?}", list))
.join()
.unwrap();
Fn
trait
闭包自动、渐进地实现 1 ~ 3 个 Fn
trait:
FnOnce
:适用于能被调用一次的闭包,所有闭包都 至少 实现了这个 trait,因为所有闭包都能被调用。一个会将捕获的值移出闭包体的闭包只实现FnOnce
trait,这是因为它只能被调用一次。FnMut
:适用于不会将捕获的值移出闭包体的闭包,但它可能会修改被捕获的值。这类闭包可以被调用多次。Fn
:适用于既不将被捕获的值移出闭包体也不修改被捕获的值的闭包,也包括不从环境中捕获值的闭包。这类闭包可以被调用多次而不改变它们的环境,这在会多次并发调用闭包的场景中十分重要。
迭代器
消费适配器(consuming adaptors):调用 next()
的方法,会消耗迭代器。
let arr = vec![1, 2, 3];
let arr_iter = arr.iter();
let total: i32 = arr_iter.sum();
assert_eq!(total, 6);
// 不能再使用 arr_iter
👉 13 智能指针