核心概念
闭包
Basics of Closure
闭包是一种可以捕获其环境中变量的匿名函数。
闭包的语法相对简洁灵活,同时也具有强大的功能。闭包在Rust中被广泛用于函数式编程、并发编程以及简化代码等方面。
How to use Closure
定义闭包的语法类似:
- 在
||内定义参数 - 可选地指定参数/返回类型
- 在
{}内定义闭包体
你可以将闭包分配给一个变量
- 然后使用该变量,就像它是一个函数名,来调用闭包
1#[derive(Debug)]2struct User {3 name: String,4 score: u64,5}67// sort_by_key8fn sort_score(users: &mut Vec<User>) {9 users.sort_by_key(sort_helper);10}1112fn sort_helper(u: &User) -> u64 {13 u.score14}1516fn sort_score_closure(users: &mut Vec<User>) {17 users.sort_by_key(|u| u.score);18}1920fn main() {21 let a = User{22 name: "U1".to_owned(),23 score: 100,24 };25 let b = User{26 name: "U2".to_owned(),27 score: 80,28 };29 let c = User{30 name: "U3".to_owned(),31 score: 40,32 };33 let d = User{34 name: "U4".to_owned(),35 score: 90,36 };37 let mut users = vec![a, b, c, d];38 sort_score(&mut users);39 println!("{:?}", users); // [User { name: "U3", score: 40 }, User { name: "U2", score: 80 }, User { name: "U4", score: 90 }, User { name: "U1", score: 100 }]4041 let a = User{42 name: "U1".to_owned(),43 score: 100,44 };45 let b = User{46 name: "U2".to_owned(),47 score: 80,48 };49 let c = User{50 name: "U3".to_owned(),51 score: 40,52 };53 let d = User{54 name: "U4".to_owned(),55 score: 90,56 };57 let mut users = vec![a, b, c, d];58 sort_score_closure(&mut users);59 println!("{:?}", users); // [User { name: "U3", score: 40 }, User { name: "U2", score: 80 }, User { name: "U4", score: 90 }, User { name: "U1", score: 100 }]60}
Obtain Parameter by Reference & by Value
闭包在Rust中的实现可以近似地理解为一个实现了FnOnce、FnMut和Fn其中一个trait的匿名结构体,这个匿名结构体保存捕获的环境中的变量。通过调用trait的方法来执行闭包体中的代码。
Obtain Outer Parameter
由Rust编译器决定用那种方式获取外部参数
- 不可变引用
Fn - 可变引用
FnMut - 转移所有权
(Move) FnOnce
闭包实现这三个trait的规则如下:
- 所有的闭包都实现了
FnOnce。 - 如果闭包的方法移出了所捕获的变量的所有权,则只会实现
FnOnce。 - 如果闭包的方法没有移出所捕获的变量的所有权,并且对变量进行了修改,即通过可变借用使用所捕获的变量,则会实现
FnMut。 - 如果闭包的方法没有移出所捕获的变量的所有权,并且没有对变量进行修改,即通过不可变借用使用所捕获的变量,则会实现
Fn。
Move
Rust编译器判断captures by value,比方说在闭包手动drop该参数。
- 实现
Copy Trait的对象,move时发生值拷贝。 - 未实现
Copy Trait的对象,move关键字强制将其所有权转移到闭包。
例子一:
#[derive(Debug, Copy, Clone)]struct FooCopy {value: i32,}impl FooCopy {fn new(value: i32) -> Self {Self { value }}fn get(&self) -> i32 {self.value}fn increase(&mut self) {self.value += 1;}}fn is_FnMut<F: FnMut()>(c: &F) {}fn is_Copy<F: Copy>(c: &F) {}fn main() {let mut foo_copy = FooCopy::new(0);let mut c_with_move = move || {for _ in 0..5 {foo_copy.increase();}println!("foo_copy in closure(with move): {}", foo_copy.get());};c_with_move(); // foo_copy in closure(with move): 5println!("foo_copy out of closure(with move): {}\n", foo_copy.get()); // foo_copy out of closure(with move): 0let mut c_without_move = || {for _ in 0..5 {foo_copy.increase();}println!("foo_copy in closure(without move): {}", foo_copy.get());};is_FnMut(&c_with_move);is_Copy(&c_with_move);is_FnMut(&c_without_move);//is_Copy(&c_without_move); // Errorc_without_move(); // foo_copy in closure(without move): 5println!("foo_copy out of closure(without move): {}\n", foo_copy.get()); // foo_copy out of closure(without move): 5}
例子中Copy语义的变量foo_copy在使用关键字move将其Copy至闭包c_with_move内后,对环境中的变量不再有影响。此时闭包的匿名结构体中保存的变量为mut FooCopy,在闭包中使用的increase()方法通过可变借用来进行操作,所以实现了FnMut + Copy trait。
在不使用关键字move时,闭包c_without_move对环境中的变量foo_copy进行了可变借用。此时闭包的匿名结构体内中保存的变量为&mut FooCopy,所以会对环境中的变量进行修改,其同样实现了FnMut trait,但不会实现Copy trait。
例子二:
fn main() {// Fn不可变引用获取外部参数let s1 = String::from("111111111");let s2 = String::from("222222222");let fn_func = |s| {println!("{s1}");println!("I am {s}");};fn_func("yz".to_owned()); // Fn不可变引用,所有权仍然保留fn_func("原子".to_owned());println!("{s1} {s2}"); // 111111111 222222222// FnMut可变引用获取外部参数,匿名函数中的外部参数存在修改let mut s1 = String::from("111111111");let mut s2 = String::from("222222222");let mut fn_func = |s| {s1.push_str("😊");s2.push_str("😊");println!("{s1}");println!("I am {s}");};fn_func("yz".to_owned()); // FnMut可变引用,所有权仍然保留fn_func("原子".to_owned());println!("{s1} {s2}"); // 111111111😊😊 222222222😊😊// 所有权转移let s1 = String::from("1111");let fn_Once_func = || {println!("{s1}");std::mem::drop(s1);};fn_Once_func(); // 1111// println!("{s1}");// 使用关键字move,捕获闭包外的环境变量所有权移至闭包内let s1 = String::from("1111");let move_fn = move || {println!("{s1}");};move_fn(); // 1111// println!("{s1}");}
How does Closure Work
- Rust编译器将闭包放入一个结构体
- 结构体会声明一个
call function,而闭包就是函数,call function会包含闭包的所有代码 - 结构体会生产一些属性去捕获闭包外的参数
- 结构体会实现一些特质
FnFnMutFnOnce
Relationship among Fn, FnMut & FnOnce
先来看看标准库中三者的定义:
// FnOnce#[lang = "fn_once"]#[must_use = "closures are lazy and do nothing unless called"]pub trait FnOnce<Args> {type Output;extern "rust-call" fn call_once(self, args: Args) -> Self::Output;}// FnMut#[lang = "fn_mut"]#[must_use = "closures are lazy and do nothing unless called"]pub trait FnMut<Args>: FnOnce<Args> {extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;}// Fn#[lang = "fn"]#[must_use = "closures are lazy and do nothing unless called"]pub trait Fn<Args>: FnMut<Args> {extern "rust-call" fn call(&self, args: Args) -> Self::Output;}
从这三个trait的声明可以看出,Fn是FnMut的子trait,FnMut是FnOnce的子trait。也就是说实现了Fn的闭包一定实现了FnMut,同样,实现了FnMut的闭包一定实现了FnOnce。

fn apply_closure<F: Fn(i32, i32) -> i32>(closure: F, x: i32, y: i32) -> i32 {closure(x, y)}fn main() {let x = 5;let add_closure = |a, b| {println!("x is: {}", x); // x is: 5a + b + x};let result = apply_closure(add_closure, 5, 6);println!("{}", result); // 16}
Closure Types FnOnce, FnMut & Fn as Examples of Function Parameters
fn closure_fn<F>(func: F)whereF: Fn(),{func();func();}fn closure_fn_mut<F>(mut func: F)whereF: FnMut(),{func();func();}fn closure_fn_once<F>(func: F)whereF: FnOnce(),{func();}fn main() {// 不可变引用只能传一种let s1 = String::from("11111");closure_fn(|| println!("{}", s1));// 可变引用let s1 = String::from("11111");closure_fn_mut(|| println!("{}", s1));let mut s2 = String::from("22222");closure_fn_mut(|| {s2.push_str("😊");println!("{}", s2);});// 所有权转移let s1 = String::from("11111");closure_fn_once(|| println!("{}", s1));let mut s2 = String::from("22222");closure_fn_once(|| {s2.push_str("😊");println!("{}", s2);});let s3 = "ff".to_owned();closure_fn_once(move || println!("{s3}")); // ff}