首页 [Rust] 当实例被移动时,究竟发生了什么?
文章
取消

[Rust] 当实例被移动时,究竟发生了什么?

前言

本文并不是移动语义的教程,并且本文假设你已经看过 the Book,已经了解了 Rust 中所有权的概念。

本文包含汇编和 MIR,但是并非需要了解汇编和 MIR 才能看懂。只要跟随本文的思路,即使以前不懂汇编和 MIR,也可以理解本文所表达的意图。

从汇编看移动

先简简单单 wrapper 一个 i32:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use std::fmt::Debug;

#[derive(Debug, Clone, Copy)]
struct WrapperCopy(i32);

#[derive(Debug)]
struct WrapperMove(i32);

fn take<T: Debug>(t: T) {
    println!("{:?}", t);
}

fn main() {
    let mut a = WrapperCopy(12);
    a.0 -= 1;
    take(a); // 我们仍然可以在 take 之后继续使用 a,因为我们的 WrapperCopy 有 derive Copy

    let mut b = WrapperMove(12);
    b.0 -= 1;
    take(b); // 我们不能在 take 之后继续使用 a,因为我们的 WrapperMove 没有 derive Copy
}

上述代码很简单,take(a) 是拷贝语义,而 take(b) 是移动语义,我相信应该没有异议吧?那么,在不开启任何优化的情况下,他们生成的汇编是什么样的呢?

以下是 playground 的输出:

1
2
3
4
5
6
7
8
9
10
11
12
playground::main:
    pushq   %rax
    movl    $12, (%rsp)
    movl    $11, (%rsp)
    movl    $11, %edi
    callq   playground::take
    movl    $12, 4(%rsp)
    movl    $11, 4(%rsp)
    movl    $11, %edi
    callq   playground::take
    popq    %rax
    retq

你会发现一个很有趣的事实,二者生成的汇编是完全一样的

再深入看,你会发现它一共就这四行:

  • movl $12, (%rsp) 往堆栈指针处写入立即数 12;
  • movl $11, (%rsp) 往堆栈指针处写入立即数 11;
  • movl $11, %edi 往目标索引寄存器写入立即数 11(传参);
  • callq playground::take 调用 take 方法。

等等,我们的 WrapperCopyWrapperMove 哪去了?

当然是丢掉了。注意这不是优化掉了而是丢掉了!必须要清楚一点:汇编没有结构体,结构体是 Rust 抽象出来的纯上层概念。最终在编译成汇编时,所有这种上层概念都将被丢弃,包括但不限于:结构体,生命周期,Trait,所有权语义,移动与拷贝语义等。

因此,当我们在讨论移动语义和拷贝语义时,必须要明白我们说的都是 Rust 抽象出来的上层概念,所谓移动是对所有权这一抽象语义的操作,是 Rust 编译器为了保证内存安全性而做出的一种约束:它约束你不能在一个变量被移动后继续使用他,如果你违反了这个约束,它将拒绝编译你的代码;这种约束力建立在 Rust 的语义上,而不是建立在汇编层。

可能很多人觉得我在讲废话,说了一大堆显而易见的事情。但是就像很多 C 语言老手都不明白为什么新人觉得指针难一样,大部分 Rust 老手也无法理解新人为什么在纠结移动语义。我之所以花费大量口舌来说明移动语义与汇编无关,是因为这类事情无时无刻不在 Rust 社区中发生。甚至于有一位大可爱,问出了「Rust 为什么不在汇编级搞所有权,说不定性能暴打 C」这一震撼我老母一百年的问题。

移动语义与 Drop 语义

回到原本的话题。既然移动语义和拷贝语义在汇编层是没有区别的,那么为什么还需要移动语义呢?这就不得不提到另一个与移动紧密相关的语义——Drop 语义

我们来看一下下面这个例子:

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

fn take(_x: WrapperMove) {
    println!("Enter take method");
}

impl Drop for WrapperMove {
    fn drop(&mut self) {
        println!("Drop WrapperMove instance");
    }
}

fn main() {
    let b = WrapperMove(12);
    take(b);
    println!("After take method");
}

我们来瞅一眼它的 MIR 长啥样:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
fn take(_1: WrapperMove) -> () {
    debug _x => _1;                      // in scope 0 at src/main.rs:3:9: 3:11
    let mut _0: ();                      // return place in scope 0 at src/main.rs:3:26: 3:26
    let _2: ();                          // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:62
    let mut _3: std::fmt::Arguments<'_>; // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
    let mut _4: &[&str];                 // in scope 0 at src/main.rs:4:14: 4:33
    let mut _5: &[&str; 1];              // in scope 0 at src/main.rs:4:14: 4:33

    bb0: {
        _5 = const _;                    // scope 0 at src/main.rs:4:14: 4:33
                                         // mir::Constant
                                         // + span: src/main.rs:4:14: 4:33
                                         // + literal: Const { ty: &[&str; 1], val: Unevaluated(take, [], Some(promoted[0])) }
        _4 = _5 as &[&str] (Pointer(Unsize)); // scope 0 at src/main.rs:4:14: 4:33
        _3 = Arguments::<'_>::new_const(move _4) -> [return: bb1, unwind: bb4]; // scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
                                         // mir::Constant
                                         // + span: /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
                                         // + user_ty: UserType(0)
                                         // + literal: Const { ty: fn(&[&'static str]) -> Arguments<'_> {Arguments::<'_>::new_const}, val: Value(<ZST>) }
    }

    bb1: {
        _2 = _print(move _3) -> [return: bb2, unwind: bb4]; // scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:62
                                         // mir::Constant
                                         // + span: /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:27
                                         // + literal: Const { ty: for<'a> fn(Arguments<'a>) {_print}, val: Value(<ZST>) }
    }

    bb2: {
        drop(_1) -> bb3;                 // scope 0 at src/main.rs:5:1: 5:2
    }

    bb3: {
        return;                          // scope 0 at src/main.rs:5:2: 5:2
    }

    bb4 (cleanup): {
        drop(_1) -> bb5;                 // scope 0 at src/main.rs:5:1: 5:2
    }

    bb5 (cleanup): {
        resume;                          // scope 0 at src/main.rs:3:1: 5:2
    }
}

fn <impl at src/main.rs:7:1: 7:26>::drop(_1: &mut WrapperMove) -> () {
    debug self => _1;                    // in scope 0 at src/main.rs:8:13: 8:22
    let mut _0: ();                      // return place in scope 0 at src/main.rs:8:24: 8:24
    let _2: ();                          // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:62
    let mut _3: std::fmt::Arguments<'_>; // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
    let mut _4: &[&str];                 // in scope 0 at src/main.rs:9:18: 9:45
    let mut _5: &[&str; 1];              // in scope 0 at src/main.rs:9:18: 9:45

    bb0: {
        _5 = const _;                    // scope 0 at src/main.rs:9:18: 9:45
                                         // mir::Constant
                                         // + span: src/main.rs:9:18: 9:45
                                         // + literal: Const { ty: &[&str; 1], val: Unevaluated(<WrapperMove as Drop>::drop, [], Some(promoted[0])) }
        _4 = _5 as &[&str] (Pointer(Unsize)); // scope 0 at src/main.rs:9:18: 9:45
        _3 = Arguments::<'_>::new_const(move _4) -> bb1; // scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
                                         // mir::Constant
                                         // + span: /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
                                         // + user_ty: UserType(0)
                                         // + literal: Const { ty: fn(&[&'static str]) -> Arguments<'_> {Arguments::<'_>::new_const}, val: Value(<ZST>) }
    }

    bb1: {
        _2 = _print(move _3) -> bb2;     // scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:62
                                         // mir::Constant
                                         // + span: /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:27
                                         // + literal: Const { ty: for<'a> fn(Arguments<'a>) {_print}, val: Value(<ZST>) }
    }

    bb2: {
        return;                          // scope 0 at src/main.rs:10:6: 10:6
    }
}

fn main() -> () {
    let mut _0: ();                      // return place in scope 0 at src/main.rs:13:11: 13:11
    let _1: WrapperMove;                 // in scope 0 at src/main.rs:14:9: 14:10
    let _2: ();                          // in scope 0 at src/main.rs:15:5: 15:12
    let _3: ();                          // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:62
    let mut _4: std::fmt::Arguments<'_>; // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
    let mut _5: &[&str];                 // in scope 0 at src/main.rs:16:14: 16:33
    scope 1 {
        debug b => _1;                   // in scope 1 at src/main.rs:14:9: 14:10
        let mut _6: &[&str; 1];          // in scope 1 at src/main.rs:16:14: 16:33
    }

    bb0: {
        _1 = const WrapperMove(12_i32);  // scope 0 at src/main.rs:14:13: 14:28
                                         // mir::Constant
                                         // + span: no-location
                                         // + literal: Const { ty: WrapperMove, val: Value(Scalar(0x0000000c)) }
        _2 = take(move _1) -> bb1;       // scope 1 at src/main.rs:15:5: 15:12
                                         // mir::Constant
                                         // + span: src/main.rs:15:5: 15:9
                                         // + literal: Const { ty: fn(WrapperMove) {take}, val: Value(<ZST>) }
    }

    bb1: {
        _6 = const _;                    // scope 1 at src/main.rs:16:14: 16:33
                                         // mir::Constant
                                         // + span: src/main.rs:16:14: 16:33
                                         // + literal: Const { ty: &[&str; 1], val: Unevaluated(main, [], Some(promoted[0])) }
        _5 = _6 as &[&str] (Pointer(Unsize)); // scope 1 at src/main.rs:16:14: 16:33
        _4 = Arguments::<'_>::new_const(move _5) -> bb2; // scope 1 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
                                         // mir::Constant
                                         // + span: /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
                                         // + user_ty: UserType(0)
                                         // + literal: Const { ty: fn(&[&'static str]) -> Arguments<'_> {Arguments::<'_>::new_const}, val: Value(<ZST>) }
    }

    bb2: {
        _3 = _print(move _4) -> bb3;     // scope 1 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:62
                                         // mir::Constant
                                         // + span: /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:27
                                         // + literal: Const { ty: for<'a> fn(Arguments<'a>) {_print}, val: Value(<ZST>) }
    }

    bb3: {
        return;                          // scope 0 at src/main.rs:17:2: 17:2
    }
}

从上述 MIR 中不难看出,take 方法拿到的 WrapperMove 会在方法结束时触发 Drop 语义,而 Drop 语义的实质就是drop 方法插入到了 take 方法的末尾

而在 main 方法中,虽然我们创建了一个 WrapperMove 的实例,但是由于它具有移动语义,并且该实例被移动到了 take 方法中,因此,Rust 编译器并不会在 main 方法的末尾调用它的 drop 方法。

换而言之,移动语义可以被认为是 Rust 编译器做出了下面这一保证:在实例的生命周期结束时,调用且仅调用一次该实例的 drop 方法。

我们知道,Rust 的智能指针都是移动语义。让我们来假设一下,如果 Box 是拷贝语义,会发生什么?首先,依赖 Boxdrop 方法释放堆内存肯定是不行了(当然,编译器也不允许给拷贝语义的对象实现 Drop,但是我们暂且先不管),因为首先 Box::new 内部会创建最原始的 Box 实例,然后返回它的拷贝,而这个最原始的 Box 实例会在 Box::new 方法结束后被释放,如果我们还选择在 drop 中释放堆内存,那么堆内存会在 Box::new 结束后也被释放。不仅是 Box::new,每一次 Box 作为参数传递,都意味着一次堆内存的释放。既然不能自动释放了,那么能不能学习 C 语言,自己搞个 free 呢?嗯。。。也不是不行吧,但是这有违背 Rust 内存安全的理念。C 语言的 malloc - free 引出了无数的内存泄露、野指针、双重释放的问题,这是 Rust 想要解决的,自然不能再走 C 语言的老路。

那么移动语义是怎么保证 Box 的内存安全的呢?

  • 首先,正如上文提到,Rust 编译器保证在 Box 的生命周期结束时,调用且仅调用一次 drop 方法来释放堆内存,这可以避免内存泄露,也可以避免双重释放;
  • 第二,Box 内的指针是非公开字段,这意味着你无法在 Safe Rust 中通过直接修改指针值的方法让 Box 变成野指针;
  • 第三,Rust 编译器会约束你不能在 Box 被移动后继续使用这个 Box,这避免了 Box 在被释放之后仍被使用的可能性,如果你违反这一约束,编译器将拒绝编译(注:虽然你可以拿到裸指针的值,但是由于解引用裸指针必须通过 unsafe 的方法,因此我们不在 Safe Rust 的框架下谈论这种情况)。

值得一提的是,内存泄露并不视为内存安全性的一环,因此 Rust 不保证没有内存泄露。但是得益于其优秀的移动语义,大部分由低级错误导致的内存泄露都会得到改善;通常,在 Safe Rust 中主要因为 Rc 嵌套甚至是主动 Box::leak 才导致内存泄露。

非指针情况下的移动语义

那有人就要问了,既然智能指针采用移动语义来保证堆内存安全,那么在不包含指针的情况下,移动语义还有什么用呢?

有一种惯用法叫做 RAII(Resource Acquisition Is Initialization),这意味着资源的有效期与持有资源的对象的生命周期严格绑定,即构造对象时完成资源的分配,析构对象时完成资源的释放,这可以保证资源不会泄露。在 Rust 中,“析构对象”自然联想到 Drop 语义,而“资源”这一概念最容易联想到的就是堆内存。然而,资源远不止是堆内存。

我们都知道,Linux 中使用文件描述符(File Descriptor)来描述一个被打开的文件,而对于 Rust 而言,文件描述符就是一个 i32 的资源。然而,由于 i32 具有拷贝语义,这意味着我们将面临和裸指针相同的问题:它可以被拷贝得到处都是,我们很难找到一个绝对安全的办法来释放它(即调用 close(fd))。这种情况下,我们就可以写一个具有移动语义的结构体将其包装起来:

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
mod file {
    pub struct FileDescriptor {
        fd: i32,
    }

    impl FileDescriptor {
        pub fn new(file_path: &str) -> Self {
            // 调用 FFI open 函数获得文件描述符
        }
    }

    impl Drop for FileDescriptor {
        fn drop(&mut self) {
            // 调用 FFI close 函数关闭文件
        }
    }
}

fn do_something(file: file::FileDescriptor) {
    // FileDescriptor 在方法结束时释放,Rust 编译器会在此处插入 `drop` 方法以关闭文件
}

fn main() {
    let f = file::FileDescriptor::new("/path/to/file");
    // let fd = f.fd;   <-- 由于 fd 是 FileDescriptor 的非公开成员,无法访问
    // 我们把 f 移动给 do_something 方法
    do_something(f);
    // 编译器约束我们不能再使用 f,因为如果 f 在 do_something 中被释放,后续的访问都是非法的
    // 同样,由于 f 被移走,编译器不会在 main 方法结尾插入 `drop` 方法
}

最后还要强调一下,如果你去翻看汇编的话,FileDescriptor 这层包装是不存在的,汇编层就是一个整数在传来传去。但是通过移动语义的约束,Rust 编译器可以保证调用 close 的地方一定是在最后一次使用该文件描述符之后。

除了文件描述符,另一个比较有名的 RAII 例子就是互斥量(Mutex)

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
use std::{
    sync::{Arc, Mutex, MutexGuard},
    thread,
};

fn add_one(mut mutex_guard: MutexGuard<i32>) {
    *mutex_guard += 1;
    println!("value in main: {}", *mutex_guard);
    // 无需手动释放锁,Rust 编译器会在此处插入 MutexGuard 的 `drop` 方法以释放互斥锁
}

fn main() {
    let value = Arc::new(Mutex::new(12));
    let value_in_thread = Arc::clone(&value);
    let join_handle = thread::spawn(move || {
        // 对互斥量加锁
        let mut mutex_guard: MutexGuard<i32> = value_in_thread.lock().unwrap();
        *mutex_guard += 1;
        println!("value in thread: {}", *mutex_guard);
        // 无需手动释放锁,Rust 编译器会在此处插入 MutexGuard 的 `drop` 方法以释放互斥锁
    });

    let mutex_guard: MutexGuard<i32> = value.lock().unwrap();
    // 将 mutex_guard 移入 add_one 方法中
    add_one(mutex_guard);
    // 由于 add_one 方法会释放 mutex_guard,因此互斥锁也会被释放,后续可以继续请求加锁
    let mutex_guard: MutexGuard<i32> = value.lock().unwrap();
    add_one(mutex_guard);
    join_handle.join().unwrap();
}

使用 RAII 还有一个尤为突出的优点:即使函数或线程异常退出,资源对象的 drop 方法也会被调用,以确保资源正确被释放。

我们来看看在上面的例子中的闭包体是如何保证这一点的:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
fn main::{closure#0}(_1: [closure@src/main.rs:15:37: 15:44]) -> () {
    debug value_in_thread => (_1.0: std::sync::Arc<std::sync::Mutex<i32>>); // in scope 0 at src/main.rs:14:9: 14:24
    let mut _0: ();                      // return place in scope 0 at src/main.rs:15:45: 15:45
    let mut _2: std::sync::MutexGuard<'_, i32>; // in scope 0 at src/main.rs:17:13: 17:28
    let mut _3: std::result::Result<std::sync::MutexGuard<'_, i32>, std::sync::PoisonError<std::sync::MutexGuard<'_, i32>>>; // in scope 0 at src/main.rs:17:48: 17:70
    let mut _4: &std::sync::Mutex<i32>;  // in scope 0 at src/main.rs:17:48: 17:70
    let _5: &std::sync::Mutex<i32>;      // in scope 0 at src/main.rs:17:48: 17:70
    let mut _6: &std::sync::Arc<std::sync::Mutex<i32>>; // in scope 0 at src/main.rs:17:48: 17:70
    let mut _7: &mut i32;                // in scope 0 at src/main.rs:18:9: 18:21
    let mut _8: &mut std::sync::MutexGuard<'_, i32>; // in scope 0 at src/main.rs:18:10: 18:21
    let mut _9: (i32, bool);             // in scope 0 at src/main.rs:18:9: 18:26
    let _10: ();                         // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:62
    let mut _11: std::fmt::Arguments<'_>; // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
    let mut _12: &[&str];                // in scope 0 at src/main.rs:19:18: 19:39
    let mut _13: &[core::fmt::ArgumentV1<'_>]; // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
    let _14: &[core::fmt::ArgumentV1<'_>; 1]; // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
    let _15: [core::fmt::ArgumentV1<'_>; 1]; // in scope 0 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
    let mut _16: core::fmt::ArgumentV1<'_>; // in scope 0 at src/main.rs:19:41: 19:53
    let _17: &i32;                       // in scope 0 at src/main.rs:19:41: 19:53
    let _18: &i32;                       // in scope 0 at src/main.rs:19:41: 19:53
    let mut _19: &std::sync::MutexGuard<'_, i32>; // in scope 0 at src/main.rs:19:42: 19:53
    scope 1 {
        debug mutex_guard => _2;         // in scope 1 at src/main.rs:17:13: 17:28
        let mut _20: &[&str; 2];         // in scope 1 at src/main.rs:19:18: 19:39
    }

    bb0: {
        _6 = &(_1.0: std::sync::Arc<std::sync::Mutex<i32>>); // scope 0 at src/main.rs:17:48: 17:70
        _5 = <Arc<Mutex<i32>> as Deref>::deref(move _6) -> [return: bb1, unwind: bb13]; // scope 0 at src/main.rs:17:48: 17:70
                                         // mir::Constant
                                         // + span: src/main.rs:17:48: 17:70
                                         // + literal: Const { ty: for<'a> fn(&'a Arc<Mutex<i32>>) -> &'a <Arc<Mutex<i32>> as Deref>::Target {<Arc<Mutex<i32>> as Deref>::deref}, val: Value(<ZST>) }
    }

    bb1: {
        _4 = _5;                         // scope 0 at src/main.rs:17:48: 17:70
        _3 = Mutex::<i32>::lock(move _4) -> [return: bb2, unwind: bb13]; // scope 0 at src/main.rs:17:48: 17:70
                                         // mir::Constant
                                         // + span: src/main.rs:17:64: 17:68
                                         // + literal: Const { ty: for<'a> fn(&'a Mutex<i32>) -> Result<MutexGuard<'a, i32>, PoisonError<MutexGuard<'a, i32>>> {Mutex::<i32>::lock}, val: Value(<ZST>) }
    }

    bb2: {
        _2 = Result::<MutexGuard<'_, i32>, PoisonError<MutexGuard<'_, i32>>>::unwrap(move _3) -> [return: bb3, unwind: bb13]; // scope 0 at src/main.rs:17:48: 17:79
                                         // mir::Constant
                                         // + span: src/main.rs:17:71: 17:77
                                         // + literal: Const { ty: fn(Result<MutexGuard<'_, i32>, PoisonError<MutexGuard<'_, i32>>>) -> MutexGuard<'_, i32> {Result::<MutexGuard<'_, i32>, PoisonError<MutexGuard<'_, i32>>>::unwrap}, val: Value(<ZST>) }
    }

    bb3: {
        _8 = &mut _2;                    // scope 1 at src/main.rs:18:10: 18:21
        _7 = <MutexGuard<'_, i32> as DerefMut>::deref_mut(move _8) -> [return: bb4, unwind: bb12]; // scope 1 at src/main.rs:18:9: 18:21
                                         // mir::Constant
                                         // + span: src/main.rs:18:9: 18:21
                                         // + literal: Const { ty: for<'a> fn(&'a mut MutexGuard<'_, i32>) -> &'a mut <MutexGuard<'_, i32> as Deref>::Target {<MutexGuard<'_, i32> as DerefMut>::deref_mut}, val: Value(<ZST>) }
    }

    bb4: {
        _9 = CheckedAdd((*_7), const 1_i32); // scope 1 at src/main.rs:18:9: 18:26
        assert(!move (_9.1: bool), "attempt to compute `{} + {}`, which would overflow", (*_7), const 1_i32) -> [success: bb5, unwind: bb12]; // scope 1 at src/main.rs:18:9: 18:26
    }

    bb5: {
        (*_7) = move (_9.0: i32);        // scope 1 at src/main.rs:18:9: 18:26
        _20 = const _;                   // scope 1 at src/main.rs:19:18: 19:39
                                         // mir::Constant
                                         // + span: src/main.rs:19:18: 19:39
                                         // + literal: Const { ty: &[&str; 2], val: Unevaluated(main::{closure#0}, [<closure_kind>, <closure_signature>, <upvars>], Some(promoted[0])) }
        _12 = _20 as &[&str] (Pointer(Unsize)); // scope 1 at src/main.rs:19:18: 19:39
        _19 = &_2;                       // scope 1 at src/main.rs:19:42: 19:53
        _18 = <MutexGuard<'_, i32> as Deref>::deref(move _19) -> [return: bb6, unwind: bb12]; // scope 1 at src/main.rs:19:41: 19:53
                                         // mir::Constant
                                         // + span: src/main.rs:19:41: 19:53
                                         // + literal: Const { ty: for<'a> fn(&'a MutexGuard<'_, i32>) -> &'a <MutexGuard<'_, i32> as Deref>::Target {<MutexGuard<'_, i32> as Deref>::deref}, val: Value(<ZST>) }
    }

    bb6: {
        _17 = _18;                       // scope 1 at src/main.rs:19:41: 19:53
        _16 = core::fmt::ArgumentV1::<'_>::new_display::<i32>(_17) -> [return: bb7, unwind: bb12]; // scope 1 at src/main.rs:19:41: 19:53
                                         // mir::Constant
                                         // + span: src/main.rs:19:41: 19:53
                                         // + user_ty: UserType(3)
                                         // + literal: Const { ty: for<'b> fn(&'b i32) -> core::fmt::ArgumentV1<'b> {core::fmt::ArgumentV1::<'_>::new_display::<i32>}, val: Value(<ZST>) }
    }

    bb7: {
        _15 = [move _16];                // scope 1 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
        _14 = &_15;                      // scope 1 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
        _13 = _14 as &[core::fmt::ArgumentV1<'_>] (Pointer(Unsize)); // scope 1 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
        _11 = Arguments::<'_>::new_v1(move _12, move _13) -> [return: bb8, unwind: bb12]; // scope 1 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
                                         // mir::Constant
                                         // + span: /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:28: 137:61
                                         // + user_ty: UserType(2)
                                         // + literal: Const { ty: fn(&[&'static str], &[core::fmt::ArgumentV1<'_>]) -> Arguments<'_> {Arguments::<'_>::new_v1}, val: Value(<ZST>) }
    }

    bb8: {
        _10 = _print(move _11) -> [return: bb9, unwind: bb12]; // scope 1 at /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:62
                                         // mir::Constant
                                         // + span: /rustc/db0cbc48d4aaa300713a95d9b317a365a474490c/library/std/src/macros.rs:137:9: 137:27
                                         // + literal: Const { ty: for<'a> fn(Arguments<'a>) {_print}, val: Value(<ZST>) }
    }

    bb9: {
        drop(_2) -> [return: bb10, unwind: bb13]; // scope 0 at src/main.rs:21:5: 21:6
    }

    bb10: {
        drop(_1) -> bb11;                // scope 0 at src/main.rs:21:5: 21:6
    }

    bb11: {
        return;                          // scope 0 at src/main.rs:21:6: 21:6
    }

    bb12 (cleanup): {
        drop(_2) -> bb13;                // scope 0 at src/main.rs:21:5: 21:6
    }

    bb13 (cleanup): {
        drop(_1) -> bb14;                // scope 0 at src/main.rs:21:5: 21:6
    }

    bb14 (cleanup): {
        resume;                          // scope 0 at src/main.rs:15:37: 21:6
    }
}

注意到,在上述 MIR 中,_1 变量是闭包自身的匿名结构体,_2 变量是 MutexGuard,因此,116-118 行的:

1
2
3
bb12 (cleanup): {
    drop(_2) -> bb13;                // scope 0 at src/main.rs:21:5: 21:6
}

就是在清理 MutexGuard,而 -> bb13 就意味着执行 bb12 结束后,跳转到 bb13 执行,而 bb13 容易看出是在释放闭包自身。

然后再看到闭包体本身,你会发现有很多行代码,都有着 -> [return: bbx, unwind: bb12] 的结构(例如,52 行,71 行),这意味着,如果这行代码执行出现了异常,那么就会转跳到 bb12 释放 MutexGuard,如果没有发生异常,则跳转到 bbx 处继续执行代码。

另一方面,bb9 则是没有发生异常时,方法正常返回前释放 MutexGuard 的流程。

本文由作者按照 CC BY 4.0 进行授权

为什么我反对炒作中文编程

[C++] std::function 是如何实现 lambda 递归的