函数和闭包

函数

常规函数

特点

  • 函数都拥有显示的类型签名;
  • 函数可分为三种类型:自由函数关联函数方法
  • 函数自身也是一种类型(函数项类型)

自由函数

最普通的函数,他的参数、返回值等都一目了然。

1
2
3
4
5
6
7
8
// 自由函数
fn sum(a: i32, b: i32) -> i32 {
a + b
}

fn main(){
assert_eq!(3, sum(1, 2));
}

关联函数 / 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct A(i32, i32);

impl A {
// 关联函数
fn sum(a: i32, b: i32) -> i32 {
a + b
}
// 方法
fn math(&self) -> i32 {
Self::sum(self.0, self.1)
}
}

fn main(){
let a = A(1, 2);
assert_eq!(3, A::sum(1, 2));
assert_eq!(3, a.math());
}

函数项类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct A (i32, i32);

impl A {
fn sum(a: i32, b: i32) -> i32 {
a + b
}
fn math(&self) -> i32 {
// 此处存在一个自动解引用 Self::sum((*self).0, (*self).1)
Self::sum(self.0, self.1)
}
}

pub fn main() {
let a = A(1, 2);
// add 是 函数项类型 也就是函数自己本身的类型(Fn item 类型)
let add = A::sum;
let add_math = A::math; // 同上
assert_eq!(add(1, 2), A::sum(1, 2));
assert_eq!(add_math(&a),a.math());
// 大小为 0 函数项类型是零大小类型(指那些没有占用任何内存空间的类型)
println!("{:?}", std::mem::size_of_val(&add)); // 大小为 0
println!("{:?}", std::mem::size_of_val(&add_math)); // 大小为 0
println!("{:?}", std::mem::size_of_val(&A)); // 大小为 0
println!("{:?}", std::mem::size_of_val(&a)); // 大小为 8 (4(i32)+ 4(i32)= 8)
}

函数指针类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type RGB = (i16, i16, i16);

fn color(c: &str) -> RGB {
(1, 1, 1)
}

// C 函数指针类型
fn shwo(c: fn(&str) -> RGB) {
println!("{:?}", c("black")); // (1, 1, 1)
println!("c: {:?}", std::mem::size_of_val(&c)); // 大小为 8 函数指针类型
}

fn main(){
// 定义一个函数指针类型
let rgb: fn(&str) -> RGB;
// 将函数项赋值给函数指针
rgb = color;
shwo(rgb);
println!("rgb: {:?}", std::mem::size_of_val(&rgb)); // 函数项大小为 0 函数项是零大小类型

}

结论:

  • 函数项类型可以通过显式指定函数类型转换为一个函数指针类型;
  • 在写代码的时候,尽可能地去使用 函数项类型,不到万不得已不要使用函数指针类型,这样有助于享受零大小类型的优化;

闭包

函数无法捕获环境变量,这时就需要使用闭包。

1
2
3
4
5
6
7
8
9
10
11
fn counter(i:i32) -> fn(i32) -> i32 {
fn inc(n: i32)-> i32 {
n+i //error[E0434]: can't capture dynamic environment in a fn item
}
inc
}

fn main() {
let f = counter(2);
assert_eq!(3f(1));
}

闭包与函数的异同

闭包可以捕获环境中的自由变量

1
2
3
4
5
6
7
8
fn counter(i: i32) -> impl FnMut(i32) -> i32 {
move | n | n + i // move 关键字将环境变量 i 的所有权移动到闭包
}

pub fn main() {
let mut f = counter(2);
assert_eq!(3, f(1))
}

闭包可以与函数指针互通

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type RGB = (i16, i16, i16);

fn color(c: &str) -> RGB {
(1, 1, 1)
}

fn shwo(c: fn(&str) -> RGB) {
println!("{:?}", c("black"));
}

fn main() {
let rgb = color;
shwo(rgb); // (1, 1, 1)
// 定义了实现`Fn(&str) -> RGB`trait 的闭包类型
let c = | s: &str | { (1, 2, 3) }; // 闭包
shwo(c); // (1, 2, 3)
}

Rust闭包的实现原理

rust中的闭包并没有引入特殊的语法,而是一种编译器的语法糖。

闭包的使用场景

  1. 未捕获环境变量(所有权)
  2. 捕获但修改环境变量(可变引用)
  3. 捕获但未修改环境变量(不可变引用)

未捕获环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 闭包
fn main() {
// 未捕获环境变量
let c1 = || println!("hello");
c1();
}

// 等价于
// #![feature(...)] 是一种属性,用于启用语言或标准库中的实验性功能
#![feature(unboxed_closures, fn_traits)]
struct Closure<T> {
env_var: T,
}

/**
### 标准库中 FnOnce trait 的定义
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
*/
impl<T> FnOnce<()> for Closure<T> {
type Output = ();
extern "rust-call" fn call_once(self, args: ()) -> () {
println!("hello");
}
}

fn main() {
let c = Closure { env_var: ()}; // 闭包未捕获任何变量用()来表示
c.call_once(()); // c 这个结构体示例就会被消耗掉
}

当你创建闭包的时候,编译器会解析你的闭包,然后生成一个匿名结构体Closure,其中的字段env_var用于存储捕获的自由变量;在上面的场景中闭包未捕获任何变量,所以main函数中创建Closure的字段值用()来表示;

接下来在为这个匿名的结构体Closure实现FnOnce<()>这个trait;其中Arges这个参数是闭包自身的参数,即闭包调用时传入的参数;

当闭包被调用的时候等价于调用匿名结构体实现的call_once这个方法;该方法会消耗掉该结构体的实例;

编译器将FnOnce类型的闭包看成是函数指针

1
2
3
4
5
6
7
8
9
fn main() {
let c1 = || { "c1"; };
let c2 = || { "c2"; };
let v = [c1, c2]; // Ok

let i = "c3";
let c3 = || { i };
let v = [c1, c2, c3] // Error expected fn pointer, found closure 他把c1, c2当成了函数指针,c3当成了闭包
}

捕获可修改环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 闭包
fn main() {
// 可修改环境变量
let mut arr = [1, 2, 3];
let mut c2 = | i | {
arr[0] = i;
println!("{:?}", arr);
}
c2(0);
}

// 等价于
// #![feature(...)] 是一种属性,用于启用语言或标准库中的实验性功能
#![feature(unboxed_closures, fn_traits)]
struct Closure {
env_var: [i32, 3], // 捕获的环境变量
}

// 至少需要一个能消耗自身的一个方法,用于在多次调用之间保持并修改状态。
impl FnOnce<(i32, )> for Closure {
type Output = ();
extern "rust-call" fn call_once(mut self, args: (i32, )) -> () {
self.env_var[0] = args.0;
println!("{:?}", self.env_var);
}
}

/**
### 标准库中 FnMut trait 的定义
pub trait FnMut<Args>: FnOnce<Args> {
// type Output; 因为 FnMut 实现自 FnOnce 所以此处不需要再重复定义了
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
*/
impl FnMut<(i32, )> for Closure {
extern "rust-call" fn call_mut(&mut self, args: (i32, )) -> () {
self.env_var[0] = args.0;
println!("{:?}", self.env_var);
}
}

fn main() {
let arr = [1, 2, 3];
let mut c = Closuse { env_var: arr };
// c.call_once((0, )); 同样这里也会消耗 c
c.call_mut((0, )); // 这里会修改 c
}

此处根据rust-call的约定 args的参数类型必须是元组类型,这是Rust API的约定;

当闭包被调用的时候等价于调用匿名结构体实现的call_mut这个方法;该方法会修改该结构体的实例;

捕获但未修改环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 闭包
fn main() {
let anwser = 42;
let c3 = || {
println!(
"The answer to the Ultimate Question of Life, The Universe, and Everything is {}",
anwser
)
};
c3();
}

// 等价于
// #![feature(...)] 是一种属性,用于启用语言或标准库中的实验性功能
#![feature(unboxed_closures, fn_traits)]
struct Closure {
env_var: i32, // 捕获的环境变量
}

// 至少需要一个能消耗自身的一个方法,用于在多次调用之间保持并修改状态。
impl FnOnce<( )> for Closure {
type Output = ();
extern "rust-call" fn call_once(mut self, args: ()) -> () {
println!(
"The answer to the Ultimate Question of Life, The Universe, and Everything is {}",
self.env_var
)
}
}

impl FnMut<()> for Closure {
extern "rust-call" fn call_mut(&mut self, args: ()) -> () {
println!(
"The answer to the Ultimate Question of Life, The Universe, and Everything is {}",
self.env_var
)
}
}

/**
### 标准库中 Fn trait 的定义
pub trait Fn<Args>: FnMut<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
*/
impl fn<()> for Closure {
extern "rust-call" fn call(&self, args: ()) -> () {
println!(
"The answer to the Ultimate Question of Life, The Universe, and Everything is {}",
self.env_var
)
}

}

fn main() {
let anwser = 42;
let mut c = Closure {env_var: anwser};
//c.call_once((0, ));
//c.call_mut((0, ));
c.call();
}

可以看出要实现Fn必须先实现FnMut,要实现FnMut必须先实现FnOnce

实现总结

这里指的是编译器生成的默认结构体

  • 如果没有捕获任何变量,则实现FnOnce
  • 如果有捕获变量,且对捕获变量进行了修改,则实现FnMut
  • 如果有捕获变量,但未对变量进行修改,则实现Fn

两条特殊情况

  • 编译器会将FnOnce当成fn(T)函数指针去看待。
  • Fn/FnMut/FnOnce这三者trait的关系是依次继承,他们正好对应所有权语义三件套
  • Fn()FnMut()的子特型,而FnMut()FnOnce()的子特型,

逃逸闭包与非逃逸闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#![feature(unboxed_closures, fn_traits)]
// 此处 impl FnMut(i32) -> [i32; 3] 称为 impl trait
// 语法表示 任意一个实现了 FnMut(i32) -> [i32; 3] 的类型
fn c_mut() -> impl FnMut(i32) -> [i32; 3] {
let mut arr = [0, 1, 2];
// i 是 FnMut(i32) 中传入的
// 闭包会复制一份 arr 到闭包中,arr 实现了 Copy
// 从而实现将局部变量移出到其他位置
move |i| {arr[0] = i; arr}
}

fn main() {
let i = 42;
let mut arr_clouse = c_mut();

// println!("{:?}", arr_clouse.call_once((i, )));
println!("{:?}", arr_clouse(i));
}

返回不在函数调用过程中被销毁的闭包称为逃逸闭包,否则就是非逃逸闭包

1
2
3
4
5
6
7
8
9
10
#![feature(unboxed_closures, fn_traits)]
fn c_mut2() -> impl for<'a>FnMut(&'a str) -> String {
let mut s = "hello".to_string();
move |i| {s += i; s}
}

fn main() {
let i = "world"
let mut = arr_clouse = c_mut2(); // Error
}

此处,s是一个动态大小的类型,当move移动时,移动的是指向推上的指针;根据Rust内存管理机制,s销毁后,他所指向的堆上的数据也会被销毁掉,这是move中的s就变成了悬垂指针,这是rust所不允许的。

唯一不可变引用

捕获方式中有一种被称为唯一不可变借用的特殊类型的借用捕获,这种借用不能在语言的其他任何地方使用,也不能显式地写出。唯一不可变借用发生在修改可变引用的引用对象(referent)时,如下面的示例所示:

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let mut a = [1, 2, 3];
let x = &mut a;
{
// 这里 x 是不可变借用
let mut c = || {(*x)[0] = 0;};
let y = &x; // Error
c();
}
let z = &x; // Ok
}

在这种情况下,不能去可变借用 x,因为 x 没有标注 mut。但与此同时,如果不可变借用 x,那对其赋值又会非法,因为 & &mut 引用可能不是唯一的,因此此引用不能安全地用于修改值。所以这里闭包使用了唯一不可变借用:它采用了不可变的方式借用了 x,但是又像可变借用一样,当然前提是此借用必须是唯一的。在上面的例子中,解开 y 那行上的注释将产生错误,因为这将违反闭包对 x 的借用的唯一性;z 的声明是有效的,因为闭包的生存期在块结束时已过期,从而已经释放了对 x 的借用。

闭包自身所实现的trait

  • Sized(默认实现)
  • Copy / Clone(取决于环境变量是否实现)
    • 如果环境变量实现了 Copy / Clone,闭包如果以可变借用方式捕获环境变量,并对其进行修改,则闭包自身不会实现 Copy / Clone;(防止出现多个闭包的可变借用)
    • 如果环境自身是 Move 语义,则闭包内捕获环境变量的操作涉及修改环境变量或者消耗环境变量,则闭包自身不会实现 Copy / Clone;(防止出现多个闭包的可变借用)
  • Sync / Send
    • 如果所有捕获变量均实现了 Sync,则闭包实现 Sync;
    • 如果环境变量都不是[唯一不可变引用]方式捕获的,并且都实现了 Sync,则闭包实现 Send;
    • 如果环境变量是以[唯一不可变引用]、[可变引用]、Copy 或 Move 所有权捕获的,那闭包实现 Send;