GitHub

核心概念

闭包

Basics of Closure

闭包是一种可以捕获其环境中变量的匿名函数。

闭包的语法相对简洁灵活,同时也具有强大的功能。闭包在Rust中被广泛用于函数式编程、并发编程以及简化代码等方面。

How to use Closure

定义闭包的语法类似:

  • ||内定义参数
  • 可选地指定参数/返回类型
  • {}内定义闭包体

你可以将闭包分配给一个变量

  • 然后使用该变量,就像它是一个函数名,来调用闭包
1#[derive(Debug)]
2struct User {
3 name: String,
4 score: u64,
5}
6
7// sort_by_key
8fn sort_score(users: &mut Vec<User>) {
9 users.sort_by_key(sort_helper);
10}
11
12fn sort_helper(u: &User) -> u64 {
13 u.score
14}
15
16fn sort_score_closure(users: &mut Vec<User>) {
17 users.sort_by_key(|u| u.score);
18}
19
20fn 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 }]
40
41 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中的实现可以近似地理解为一个实现了FnOnceFnMutFn其中一个trait的匿名结构体,这个匿名结构体保存捕获的环境中的变量。通过调用trait的方法来执行闭包体中的代码。

Obtain Outer Parameter

由Rust编译器决定用那种方式获取外部参数

  1. 不可变引用Fn
  2. 可变引用FnMut
  3. 转移所有权(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): 5
println!("foo_copy out of closure(with move): {}\n", foo_copy.get()); // foo_copy out of closure(with move): 0
let 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); // Error
c_without_move(); // foo_copy in closure(without move): 5
println!("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

  1. Rust编译器将闭包放入一个结构体
  2. 结构体会声明一个call function,而闭包就是函数,call function会包含闭包的所有代码
  3. 结构体会生产一些属性去捕获闭包外的参数
  4. 结构体会实现一些特质
    • Fn
    • FnMut
    • FnOnce

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的声明可以看出,FnFnMut的子traitFnMutFnOnce的子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: 5
a + 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)
where
F: Fn(),
{
func();
func();
}
fn closure_fn_mut<F>(mut func: F)
where
F: FnMut(),
{
func();
func();
}
fn closure_fn_once<F>(func: F)
where
F: 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
}
上一篇
KV存储哈希表
下一篇
迭代器