我以前以为闭包就是 当前作用域的一个临时函数。作者说闭包可以方便的函数式编程。闭包
作者给闭包的定义:闭包是将函数,或者说代码和其环境一起存储的一种数据结构。(闭包也是一种数据结构吗?) 闭包引用的上下文中的自由变量,会被捕获到闭包的结构中,成为闭包类型的一部分。
闭包会根据内部的使用情况,捕获环境中的自由变量。在Rust中,闭包可以用这种方式来表达
| 参数 | {
...
闭包代码实现
}
看下面的例子:
fn main() {
let a = "Hello";
let b = "Tyr";
let c = |msg| {
println!("{} {} : {}", a, b, msg);
};
c("How are you?");
}
上图闭包c 捕获了上下文里的a和b,然后通过引用来使用 a/b 这两个变量。
闭包还可以用 move 关键字 ,转移变量的使用权。
比如在多线程的情况下,会经常使用到: 如:
use std::thread;
fn main() {
let s = String::from("hello world");
let handle = thread::spawn(move || {
println!("moved: {:?}", s);
});
handle.join().unwrap();
}
这是变量s的所有权就从当前作用域移动到闭包的作用域里了。
闭包是一种匿名类型,一旦声明,就会产生一个新的类型,但这个类型无法被其它地方使用。这个类型就像一个结构体,会包含所有捕获的变量。
所以前面说闭包是一种特殊的数据结构?
我有点迷糊,做个实验理解一下:
use std::{collections::HashMap, mem::size_of_val};
fn main() {
// 长度为 0
let c1 = || println!("hello world!");
// 和参数无关,长度也为 0
let c2 = |i: i32| println!("hello: {}", i);
let name = String::from("tyr");
let name1 = name.clone();
let mut table = HashMap::new();
table.insert("hello", "world");
// 如果捕获一个引用,长度为 8
let c3 = || println!("hello: {}", name);
// 捕获移动的数据 name1(长度 24) + table(长度 48),closure 长度 72
let c4 = move || println!("hello: {}, {:?}", name1, table);
let name2 = name.clone();
// 和局部变量无关,捕获了一个 String name2,closure 长度 24
let c5 = move || {
let x = 1;
let name3 = String::from("lindsey");
println!("hello: {}, {:?}, {:?}", x, name2, name3);
};
println!(
"c1: {}, c2: {}, c3: {}, c4: {}, c5: {}, main: {}",
size_of_val(&c1),
size_of_val(&c2),
size_of_val(&c3),
size_of_val(&c4),
size_of_val(&c5),
size_of_val(&main),
)
}
结果为
c1: 0, c2: 0, c3: 8, c4: 72, c5: 24, main: 0
这里共有5个闭包:
结论: 从 c2 可以发现,闭包大小和参数无关。从 c3 发现:不带move时,闭包捕获的变量的引用。从 c4 c5 发现,带了move后,捕获的就是变量本身了。从 c5 发现,闭包大小和局部变量无关。
那和什么有关呢?只跟捕获的变量有关。
而 Rust 为每个闭包生成一个新的类型,又使得调用闭包时可以直接和代码对应,省去了使用函数指针再转一道手的额外消耗。
Rust的设计让我们
明天我们继续学习 FnOnce / FnMut / Fn 这三种闭包类型。