]>
Commit | Line | Data |
---|---|---|
dc9dc135 XL |
1 | # Closure Expansion in rustc |
2 | ||
3 | This section describes how rustc handles closures. Closures in Rust are | |
4 | effectively "desugared" into structs that contain the values they use (or | |
5 | references to the values they use) from their creator's stack frame. rustc has | |
6 | the job of figuring out which values a closure uses and how, so it can decide | |
7 | whether to capture a given variable by shared reference, mutable reference, or | |
8 | by move. rustc also has to figure out which the closure traits ([`Fn`][fn], | |
9 | [`FnMut`][fn_mut], or [`FnOnce`][fn_once]) a closure is capable of | |
10 | implementing. | |
11 | ||
12 | [fn]: https://doc.rust-lang.org/std/ops/trait.Fn.html | |
13 | [fn_mut]:https://doc.rust-lang.org/std/ops/trait.FnMut.html | |
14 | [fn_once]: https://doc.rust-lang.org/std/ops/trait.FnOnce.html | |
15 | ||
16 | Let's start with a few examples: | |
17 | ||
18 | ### Example 1 | |
19 | ||
20 | To start, let's take a look at how the closure in the following example is desugared: | |
21 | ||
22 | ```rust | |
23 | fn closure(f: impl Fn()) { | |
24 | f(); | |
25 | } | |
26 | ||
27 | fn main() { | |
28 | let x: i32 = 10; | |
29 | closure(|| println!("Hi {}", x)); // The closure just reads x. | |
30 | println!("Value of x after return {}", x); | |
31 | } | |
32 | ``` | |
33 | ||
34 | Let's say the above is the content of a file called `immut.rs`. If we compile | |
35 | `immut.rs` using the following command. The [`-Zdump-mir=all`][dump-mir] flag will cause | |
36 | `rustc` to generate and dump the [MIR][mir] to a directory called `mir_dump`. | |
37 | ```console | |
38 | > rustc +stage1 immut.rs -Zdump-mir=all | |
39 | ``` | |
40 | ||
41 | [mir]: ./mir/index.md | |
42 | [dump-mir]: ./mir/passes.md | |
43 | ||
44 | After we run this command, we will see a newly generated directory in our | |
45 | current working directory called `mir_dump`, which will contain several files. | |
46 | If we look at file `rustc.main.-------.mir_map.0.mir`, we will find, among | |
47 | other things, it also contains this line: | |
48 | ||
49 | ```rust,ignore | |
50 | _4 = &_1; | |
51 | _3 = [closure@immut.rs:7:13: 7:36] { x: move _4 }; | |
52 | ``` | |
53 | ||
54 | Note that in the MIR examples in this chapter, `_1` is `x`. | |
55 | ||
56 | Here in first line `_4 = &_1;`, the `mir_dump` tells us that `x` was borrowed | |
57 | as an immutable reference. This is what we would hope as our closure just | |
58 | reads `x`. | |
59 | ||
60 | ### Example 2 | |
61 | ||
62 | Here is another example: | |
63 | ||
64 | ```rust | |
65 | fn closure(mut f: impl FnMut()) { | |
66 | f(); | |
67 | } | |
68 | ||
69 | fn main() { | |
70 | let mut x: i32 = 10; | |
71 | closure(|| { | |
72 | x += 10; // The closure mutates the value of x | |
73 | println!("Hi {}", x) | |
74 | }); | |
75 | println!("Value of x after return {}", x); | |
76 | } | |
77 | ``` | |
78 | ||
79 | ```rust,ignore | |
80 | _4 = &mut _1; | |
81 | _3 = [closure@mut.rs:7:13: 10:6] { x: move _4 }; | |
82 | ``` | |
83 | This time along, in the line `_4 = &mut _1;`, we see that the borrow is changed to mutable borrow. | |
84 | Fair enough! The closure increments `x` by 10. | |
85 | ||
86 | ### Example 3 | |
87 | ||
88 | One more example: | |
89 | ||
90 | ```rust | |
91 | fn closure(f: impl FnOnce()) { | |
92 | f(); | |
93 | } | |
94 | ||
95 | fn main() { | |
96 | let x = vec![21]; | |
97 | closure(|| { | |
98 | drop(x); // Makes x unusable after the fact. | |
99 | }); | |
100 | // println!("Value of x after return {:?}", x); | |
101 | } | |
102 | ``` | |
103 | ||
104 | ```rust,ignore | |
105 | _6 = [closure@move.rs:7:13: 9:6] { x: move _1 }; // bb16[3]: scope 1 at move.rs:7:13: 9:6 | |
106 | ``` | |
107 | Here, `x` is directly moved into the closure and the access to it will not be permitted after the | |
108 | closure. | |
109 | ||
110 | ## Inferences in the compiler | |
111 | ||
112 | Now let's dive into rustc code and see how all these inferences are done by the compiler. | |
113 | ||
114 | Let's start with defining a term that we will be using quite a bit in the rest of the discussion - | |
115 | *upvar*. An **upvar** is a variable that is local to the function where the closure is defined. So, | |
116 | in the above examples, **x** will be an upvar to the closure. They are also sometimes referred to as | |
117 | the *free variables* meaning they are not bound to the context of the closure. | |
118 | [`src/librustc/ty/query/mod.rs`][upvars] defines a query called *upvars* for this purpose. | |
119 | ||
120 | [upvars]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/query/queries/struct.upvars.html | |
121 | ||
122 | Other than lazy invocation, one other thing that the distinguishes a closure from a | |
123 | normal function is that it can use the upvars. It borrows these upvars from its surrounding | |
416331ca | 124 | context; therefore the compiler has to determine the upvar's borrow type. The compiler starts with |
dc9dc135 XL |
125 | assigning an immutable borrow type and lowers the restriction (that is, changes it from |
126 | **immutable** to **mutable** to **move**) as needed, based on the usage. In the Example 1 above, the | |
127 | closure only uses the variable for printing but does not modify it in any way and therefore, in the | |
128 | `mir_dump`, we find the borrow type for the upvar `x` to be immutable. In example 2, however, the | |
129 | closure modifies `x` and increments it by some value. Because of this mutation, the compiler, which | |
130 | started off assigning `x` as an immutable reference type, has to adjust it as a mutable reference. | |
131 | Likewise in the third example, the closure drops the vector and therefore this requires the variable | |
132 | `x` to be moved into the closure. Depending on the borrow kind, the closure has to implement the | |
133 | appropriate trait: `Fn` trait for immutable borrow, `FnMut` for mutable borrow, | |
134 | and `FnOnce` for move semantics. | |
135 | ||
136 | Most of the code related to the closure is in the | |
137 | [`src/librustc_typeck/check/upvar.rs`][upvar] file and the data structures are | |
138 | declared in the file [`src/librustc/ty/mod.rs`][ty]. | |
139 | ||
140 | [upvar]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_typeck/check/upvar/index.html | |
141 | [ty]:https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/index.html | |
142 | ||
416331ca | 143 | Before we go any further, let's discuss how we can examine the flow of control through the rustc |
dc9dc135 XL |
144 | codebase. For closures specifically, set the `RUST_LOG` env variable as below and collect the |
145 | output in a file: | |
146 | ||
147 | ```console | |
148 | > RUST_LOG=rustc_typeck::check::upvar rustc +stage1 -Zdump-mir=all \ | |
149 | <.rs file to compile> 2> <file where the output will be dumped> | |
150 | ``` | |
151 | ||
152 | This uses the stage1 compiler and enables `debug!` logging for the | |
153 | `rustc_typeck::check::upvar` module. | |
154 | ||
155 | The other option is to step through the code using lldb or gdb. | |
156 | ||
157 | 1. `rust-lldb build/x86_64-apple-darwin/stage1/bin/rustc test.rs` | |
158 | 2. In lldb: | |
159 | 1. `b upvar.rs:134` // Setting the breakpoint on a certain line in the upvar.rs file` | |
160 | 2. `r` // Run the program until it hits the breakpoint | |
161 | ||
162 | Let's start with [`upvar.rs`][upvar]. This file has something called | |
60c5eb7d | 163 | the [`euv::ExprUseVisitor`] which walks the source of the closure and |
dc9dc135 XL |
164 | invokes a callbackfor each upvar that is borrowed, mutated, or moved. |
165 | ||
60c5eb7d | 166 | [`euv::ExprUseVisitor`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_typeck/expr_use_visitor/struct.ExprUseVisitor.html |
dc9dc135 XL |
167 | |
168 | ```rust | |
169 | fn main() { | |
170 | let mut x = vec![21]; | |
171 | let _cl = || { | |
172 | let y = x[0]; // 1. | |
173 | x[0] += 1; // 2. | |
174 | }; | |
175 | } | |
176 | ``` | |
177 | ||
178 | In the above example, our visitor will be called twice, for the lines marked 1 and 2, once for a | |
179 | shared borrow and another one for a mutable borrow. It will also tell us what was borrowed. | |
180 | ||
60c5eb7d | 181 | The callbacks are defined by implementing the [`Delegate`] trait. The |
dc9dc135 XL |
182 | [`InferBorrowKind`][ibk] type implements `Delegate` and keeps a map that |
183 | records for each upvar which mode of borrow was required. The modes of borrow | |
184 | can be `ByValue` (moved) or `ByRef` (borrowed). For `ByRef` borrows, it can be | |
185 | `shared`, `shallow`, `unique` or `mut` as defined in the | |
186 | [`src/librustc/mir/mod.rs`][mir_mod]. | |
187 | ||
188 | [mir_mod]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/index.html | |
189 | ||
190 | `Delegate` defines a few different methods (the different callbacks): | |
191 | **consume**: for *move* of a variable, **borrow** for a *borrow* of some kind | |
192 | (shared or mutable), and **mutate** when we see an *assignment* of something. | |
193 | ||
194 | All of these callbacks have a common argument *cmt* which stands for Category, | |
195 | Mutability and Type and is defined in | |
196 | [`src/librustc/middle/mem_categorization.rs`][cmt]. Borrowing from the code | |
197 | comments, "`cmt` is a complete categorization of a value indicating where it | |
198 | originated and how it is located, as well as the mutability of the memory in | |
199 | which the value is stored". Based on the callback (consume, borrow etc.), we | |
200 | will call the relevant *adjust_upvar_borrow_kind_for_<something>* and pass the | |
201 | `cmt` along. Once the borrow type is adjusted, we store it in the table, which | |
202 | basically says what borrows were made for each closure. | |
203 | ||
204 | ```rust,ignore | |
205 | self.tables | |
206 | .borrow_mut() | |
207 | .upvar_capture_map | |
208 | .extend(delegate.adjust_upvar_captures); | |
209 | ``` | |
210 | ||
60c5eb7d | 211 | [`Delegate`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_typeck/expr_use_visitor/trait.Delegate.html |
dc9dc135 | 212 | [ibk]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_typeck/check/upvar/struct.InferBorrowKind.html |
60c5eb7d | 213 | [cmt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_typeck/mem_categorization/index.html |