👈 11 格式化输出

闭包

用闭包简化代码

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

let x = 1;
let sum = |y| x + y;
assert_eq!(3, sum(2));

sum 是一个闭包,它有一个入参 y,同时捕获了作用域中 x 的值,调用 sum(2) 意味着将 21 相加,返回 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 智能指针