owned this note
owned this note
Published
Linked with GitHub
# Closures in rust
## at the language level
```rust
let x = 22;
let y = 44;
let c = || x + y;
```
desugars to (at a high level)
```rust
struct ClosureStruct<'a, 'b> {
x: &'a i32,
y: &'b i32,
}
impl Fn<()> for ClosureStruct<'_, '_> {
}
let x = 22;
let y = 44;
let c = ClosureStruct { x: &x, y: &y };
```
the compiler actually makes a struct that looks like this
```rust
struct ClosureStruct<CK, CS, (X, Y)> {
x: X,
y: Y,
}
impl Fn<()> for ClosureStruct<'_, '_> {
}
let x = 22;
let y = 44;
let c: ClosureStruct<i8, fn(), (&i32, &i32)> = ClosureStruct { x: &x, y: &y
// ^^^^^^^^^^^^^^^^ ClosureSubsts
```
* [ClosureSubsts](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.ClosureSubsts.html)
* [Ty](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.Ty.html) represents the types of things
* [TyKind::Closure](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html#variant.Closure)
* substs -- [as_closure](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/subst/type.InternalSubsts.html#method.as_closure)
---
```rust
fn foo() {
let tuple = (1, 2, 3);
let closure = || (tuple.0, tuple.1);
let c: ClosureStruct<i8, fn(), (&i32, &i32)> = ClosureStruct { x: &x, y: &y
let closure2 = ||... // same typeck tables
}
```
* handling closures:
* before type check, we do name resolution
* we have that information is the query [`upvars`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.upvars)
* when we type check a function
* when we see a closure expression, we invoke [`check_expr_closure`] and figure out its type
* we know how many upvars there will be
* but we don't their exact types
* to know the exact types, we have to do an analysis to figure out how they are captured (the upvar analysis)
* in example below, this tells us that `v` is captured by `&mut` reference, and also that the closure `c` implements `FnMut` and `FnOnce` but not `Fn`
* in the compiler, we have a period of "uncertainty" which we handle by inference variables
* inference variable: start out "unbound" and become "bound"
* for a closure type some of the values in its substitutions are variables:
* `ClosureStruct<?K, ?S, (?U0, ..., ?Un)>`
* today, the only thing we know is how many upvars there are ([code that creates the substitution](https://github.com/rust-lang/rust/blob/769d12eec1d0bc3708841dfc149d3ec98b04bec6/src/librustc_typeck/check/closure.rs#L90-L119))
* those inference remain unbound until the upvar inference runs
* and it assigns them a value
```rust
let mut v = vec![1, 2, 3];
let c = || v.push(22); // have to look at the signature of `push`, which is `fn(&mut self)`
```
* `Fn` -- can be called many times with a shared reference, because you only read things
* FnMut -- can be called with a mutable reference, because you mutate things
* FnOnce -- can be called at most once, because I move things
[`check_expr_closure`]: https://github.com/rust-lang/rust/blob/769d12eec1d0bc3708841dfc149d3ec98b04bec6/src/librustc_typeck/check/closure.rs#L38
# why can't we figure out how many elements are in the tuple by syntactic analysis?
```rust
let v: &mut (u32, (u32, u32));
let c = || drop((*v, v.1.0, (*v).1.1));
// we don't know that v.1.0 is based on `*v`
// ideally we would only capture `*v` here
struct HasDrop { v: Vec<u32> }
impl Drop for HasDrop { }
let x = HasDrop { v: vec![22] };
let f = x.v; // can't do this in Rust doay
let c = move || x.v.len(); // captures `x`
// we want to capture `x` and not `x.v` because `x: HasDrop`
// and `HasDrop: Drop`.
```
We could however get an "upper bound" on the number of things that we're captured from syntactic analysis:
* in the first case, we would guess 3 captures, but wind up with only one, and unify the other two variables with `()`
# Places
Given something like
`(*v).b.c`
this would be
* base: local variable `v`
* projections: [
Deref,
Field(b), // currently "other"
Field(c\), // currently "other"
]
refactor [`UpvarListMap`]
[`UpvarListMap`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.UpvarListMap.html
```rust
fn foo() { // the function `foo` has a `DefId` (D`)
let x = 22;
// HIR "high-level intermediate representation":
//
// LetStatement -- `D:0` (each of these nodes has a HirId)
// destinationpattern:
// x -- `D:1`
// source expression
// 22 -- `D:2`
}
```
# Why would drop change?
```rust
{
let tuple = (vec![1], vec![2]);
{
let c = || {
let tuple = tuple; // an edition boundary could do this
drop(tuple.0)
};
} // today: c is dropped here, and it drops tuple.
} // after this change: tuple.1 is dropped is here.
```
# Aman's question
```rust
struct Point
{
x: i32,
y: i32,
}
fn empty(_x: &())
{
}
fn print_point(p: &Point) -> fn(&())
{
println!("{}", p.x);
return empty;
}
fn print(x: &i32) -> fn(&())
{
println!("{}", x);
return empty;
}
fn printer<F, X> (print_function: F, printable: &X)
where F: Fn(&X)
{
print_function(printable);
}
fn main() {
let mut p = Point{x: 1, y: 2};
let unit = ();
let empty = || { print(&p.x) };
printer(empty(), &unit);
// Do we just capture p.x?
//
// No -- we don't cross function boundaries.
let empty = || { print_point(&p) };
printer(empty(), &unit);
let mut_y = &mut p.y;
*mut_y = 2;
}
```