blob: 44f692b16b2891ca00b0c0cde7a3daec81ccdda9 [file] [log] [blame]
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Closure capture inference - Rust Compiler Development Guide</title>
<!-- Custom HTML head -->
<meta name="description" content="A guide to developing the Rust compiler (rustc)">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
</script>
<!-- Start loading toc.js asap -->
<script src="toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Rust Compiler Development Guide</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/rust-lang/rustc-dev-guide" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/rust-lang/rustc-dev-guide/edit/master/src/closure.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="closure-capture-inference"><a class="header" href="#closure-capture-inference">Closure Capture Inference</a></h1>
<p>This section describes how rustc handles closures. Closures in Rust are
effectively "desugared" into structs that contain the values they use (or
references to the values they use) from their creator's stack frame. rustc has
the job of figuring out which values a closure uses and how, so it can decide
whether to capture a given variable by shared reference, mutable reference, or
by move. rustc also has to figure out which of the closure traits (<a href="https://doc.rust-lang.org/std/ops/trait.Fn.html"><code>Fn</code></a>,
<a href="https://doc.rust-lang.org/std/ops/trait.FnMut.html"><code>FnMut</code></a>, or <a href="https://doc.rust-lang.org/std/ops/trait.FnOnce.html"><code>FnOnce</code></a>) a closure is capable of
implementing.</p>
<p>Let's start with a few examples:</p>
<h3 id="example-1"><a class="header" href="#example-1">Example 1</a></h3>
<p>To start, let's take a look at how the closure in the following example is desugared:</p>
<pre><pre class="playground"><code class="language-rust">fn closure(f: impl Fn()) {
f();
}
fn main() {
let x: i32 = 10;
closure(|| println!("Hi {}", x)); // The closure just reads x.
println!("Value of x after return {}", x);
}</code></pre></pre>
<p>Let's say the above is the content of a file called <code>immut.rs</code>. If we compile
<code>immut.rs</code> using the following command. The <a href="./mir/passes.html"><code>-Z dump-mir=all</code></a> flag will cause
<code>rustc</code> to generate and dump the <a href="./mir/index.html">MIR</a> to a directory called <code>mir_dump</code>.</p>
<pre><code class="language-console">&gt; rustc +stage1 immut.rs -Z dump-mir=all
</code></pre>
<p>After we run this command, we will see a newly generated directory in our
current working directory called <code>mir_dump</code>, which will contain several files.
If we look at file <code>rustc.main.-------.mir_map.0.mir</code>, we will find, among
other things, it also contains this line:</p>
<pre><code class="language-rust ignore">_4 = &amp;_1;
_3 = [closure@immut.rs:7:13: 7:36] { x: move _4 };</code></pre>
<p>Note that in the MIR examples in this chapter, <code>_1</code> is <code>x</code>.</p>
<p>Here in first line <code>_4 = &amp;_1;</code>, the <code>mir_dump</code> tells us that <code>x</code> was borrowed
as an immutable reference. This is what we would hope as our closure just
reads <code>x</code>.</p>
<h3 id="example-2"><a class="header" href="#example-2">Example 2</a></h3>
<p>Here is another example:</p>
<pre><pre class="playground"><code class="language-rust">fn closure(mut f: impl FnMut()) {
f();
}
fn main() {
let mut x: i32 = 10;
closure(|| {
x += 10; // The closure mutates the value of x
println!("Hi {}", x)
});
println!("Value of x after return {}", x);
}</code></pre></pre>
<pre><code class="language-rust ignore">_4 = &amp;mut _1;
_3 = [closure@mut.rs:7:13: 10:6] { x: move _4 };</code></pre>
<p>This time along, in the line <code>_4 = &amp;mut _1;</code>, we see that the borrow is changed to mutable borrow.
Fair enough! The closure increments <code>x</code> by 10.</p>
<h3 id="example-3"><a class="header" href="#example-3">Example 3</a></h3>
<p>One more example:</p>
<pre><pre class="playground"><code class="language-rust">fn closure(f: impl FnOnce()) {
f();
}
fn main() {
let x = vec![21];
closure(|| {
drop(x); // Makes x unusable after the fact.
});
// println!("Value of x after return {:?}", x);
}</code></pre></pre>
<pre><code class="language-rust ignore">_6 = [closure@move.rs:7:13: 9:6] { x: move _1 }; // bb16[3]: scope 1 at move.rs:7:13: 9:6</code></pre>
<p>Here, <code>x</code> is directly moved into the closure and the access to it will not be permitted after the
closure.</p>
<h2 id="inferences-in-the-compiler"><a class="header" href="#inferences-in-the-compiler">Inferences in the compiler</a></h2>
<p>Now let's dive into rustc code and see how all these inferences are done by the compiler.</p>
<p>Let's start with defining a term that we will be using quite a bit in the rest of the discussion -
<em>upvar</em>. An <strong>upvar</strong> is a variable that is local to the function where the closure is defined. So,
in the above examples, <strong>x</strong> will be an upvar to the closure. They are also sometimes referred to as
the <em>free variables</em> meaning they are not bound to the context of the closure.
<a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_passes/upvars/index.html"><code>compiler/rustc_passes/src/upvars.rs</code></a> defines a query called <em>upvars_mentioned</em>
for this purpose.</p>
<p>Other than lazy invocation, one other thing that distinguishes a closure from a
normal function is that it can use the upvars. It borrows these upvars from its surrounding
context; therefore the compiler has to determine the upvar's borrow type. The compiler starts with
assigning an immutable borrow type and lowers the restriction (that is, changes it from
<strong>immutable</strong> to <strong>mutable</strong> to <strong>move</strong>) as needed, based on the usage. In the Example 1 above, the
closure only uses the variable for printing but does not modify it in any way and therefore, in the
<code>mir_dump</code>, we find the borrow type for the upvar <code>x</code> to be immutable. In example 2, however, the
closure modifies <code>x</code> and increments it by some value. Because of this mutation, the compiler, which
started off assigning <code>x</code> as an immutable reference type, has to adjust it as a mutable reference.
Likewise in the third example, the closure drops the vector and therefore this requires the variable
<code>x</code> to be moved into the closure. Depending on the borrow kind, the closure has to implement the
appropriate trait: <code>Fn</code> trait for immutable borrow, <code>FnMut</code> for mutable borrow,
and <code>FnOnce</code> for move semantics.</p>
<p>Most of the code related to the closure is in the
<a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/upvar/index.html"><code>compiler/rustc_hir_typeck/src/upvar.rs</code></a> file and the data structures are
declared in the file <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/index.html"><code>compiler/rustc_middle/src/ty/mod.rs</code></a>.</p>
<p>Before we go any further, let's discuss how we can examine the flow of control through the rustc
codebase. For closures specifically, set the <code>RUSTC_LOG</code> env variable as below and collect the
output in a file:</p>
<pre><code class="language-console">&gt; RUSTC_LOG=rustc_hir_typeck::upvar rustc +stage1 -Z dump-mir=all \
&lt;.rs file to compile&gt; 2&gt; &lt;file where the output will be dumped&gt;
</code></pre>
<p>This uses the stage1 compiler and enables <code>debug!</code> logging for the
<code>rustc_hir_typeck::upvar</code> module.</p>
<p>The other option is to step through the code using lldb or gdb.</p>
<ol>
<li><code>rust-lldb build/host/stage1/bin/rustc test.rs</code></li>
<li>In lldb:
<ol>
<li><code>b upvar.rs:134</code> // Setting the breakpoint on a certain line in the upvar.rs file</li>
<li><code>r</code> // Run the program until it hits the breakpoint</li>
</ol>
</li>
</ol>
<p>Let's start with <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/upvar/index.html"><code>upvar.rs</code></a>. This file has something called
the <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/expr_use_visitor/struct.ExprUseVisitor.html"><code>euv::ExprUseVisitor</code></a> which walks the source of the closure and
invokes a callback for each upvar that is borrowed, mutated, or moved.</p>
<pre><pre class="playground"><code class="language-rust">fn main() {
let mut x = vec![21];
let _cl = || {
let y = x[0]; // 1.
x[0] += 1; // 2.
};
}</code></pre></pre>
<p>In the above example, our visitor will be called twice, for the lines marked 1 and 2, once for a
shared borrow and another one for a mutable borrow. It will also tell us what was borrowed.</p>
<p>The callbacks are defined by implementing the <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/expr_use_visitor/trait.Delegate.html"><code>Delegate</code></a> trait. The
<a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/upvar/struct.InferBorrowKind.html"><code>InferBorrowKind</code></a> type implements <code>Delegate</code> and keeps a map that
records for each upvar which mode of capture was required. The modes of capture
can be <code>ByValue</code> (moved) or <code>ByRef</code> (borrowed). For <code>ByRef</code> borrows, the possible
<a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.BorrowKind.html"><code>BorrowKind</code></a>s are <code>ImmBorrow</code>, <code>UniqueImmBorrow</code>, <code>MutBorrow</code> as defined in the
<a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/index.html"><code>compiler/rustc_middle/src/ty/mod.rs</code></a>.</p>
<p><code>Delegate</code> defines a few different methods (the different callbacks):
<strong>consume</strong> for <em>move</em> of a variable, <strong>borrow</strong> for a <em>borrow</em> of some kind
(shared or mutable), and <strong>mutate</strong> when we see an <em>assignment</em> of something.</p>
<p>All of these callbacks have a common argument <em>cmt</em> which stands for Category,
Mutability and Type and is defined in
<a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/expr_use_visitor/index.html"><code>compiler/rustc_hir_typeck/src/expr_use_visitor.rs</code></a>. Borrowing from the code
comments, "<code>cmt</code> is a complete categorization of a value indicating where it
originated and how it is located, as well as the mutability of the memory in
which the value is stored". Based on the callback (consume, borrow etc.), we
will call the relevant <code>adjust_upvar_borrow_kind_for_&lt;something&gt;</code> and pass the
<code>cmt</code> along. Once the borrow type is adjusted, we store it in the table, which
basically says what borrows were made for each closure.</p>
<pre><code class="language-rust ignore">self.tables
.borrow_mut()
.upvar_capture_map
.extend(delegate.adjust_upvar_captures);</code></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="borrow_check/two_phase_borrows.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="coroutine-closures.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="borrow_check/two_phase_borrows.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="coroutine-closures.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script src="mermaid.min.js"></script>
<script src="mermaid-init.js"></script>
</div>
</body>
</html>