owned this note
owned this note
Published
Linked with GitHub
# RFC2229: Capture by move
[#31](https://github.com/rust-lang/project-rfc-2229/issues/31)
```rust=
struct S {
x: String,
y: String
}
fn x(s: &mut S) {
let mut c = move || {
// a `&mut` access to `(*s).x`
// ^ passed through the deref of an `&mut` value
s.x.truncate(0);
};
c();
}
```
```rust=
struct S {
x: &mut String,
y: String
}
fn x(s: S) {
let mut c = move || {
// a `&mut` access to `*(s.x)`
// ^
// we would want to capture `&mut *s.x`
s.x.truncate(0);
};
c();
}
```
Interesting corner cases:
* `&mut` access to deref of shared borrow
* guaranteed error
*
```rust
let mut s = String::new();
let t = (&mut s, 10);
let c = || {
// We see that a capture in its entirety is being
// modfied
// i.e. we don't build a place on top of it
// Therefore the capture is mutable if the root variable of the capture is mutable
*t.0 = format!("Some other string");
}
```
### Is this only relevant to move closures?
```rust=
let c = || s.x.truncate(0); // `&mut` access to `(*s).x`, so you get a mutable ref mode for the upvar
let c = move || s.x.truncate(0); // without this, you would move `s` because you stop at the deref
```
```rust=
let c = || {
s.x.truncate(0); // without this, you would move `s` because you stop at the deref
drop(s.x); // (error)
};
```
```rust=
struct S { x: String, y: String }
fn foo(s: &mut S) {
// Closure will:
// * move `s` because we don't move through derefs, so we can't move `s.y
let c = || {
s.x.truncate(0); // without this, you would move `s` because you stop at the deref
drop(s.y); // (error)
};
}
```
```rust=
struct S { x: &mut String, y: &mut String }
fn foo(s: S) {
// Closure will:
// * mut-borrow `*s.x`
// * move `s.y`
// but this doesn't require our special code.
let c = || {
s.x.truncate(0); // without this, you would move `s` because you stop at the deref
drop(s.y); // (error)
};
}
```
```rust=
struct S { x: String, y: &mut String }
fn foo(s: Box<S>) {
// Closure will:
// * move `s`
let c = || {
s.x.truncate(0); // without this, you would move `s` because you stop at the deref
drop(s.y); // (error)
};
}
```
```rust=
struct S { x: String, y: &mut String }
fn foo(s: S) {
// Closure will:
// * move `s`
let c = || {
s.x.truncate(0); // without this, you would move `s` because you stop at the deref
drop(s.x.something);
};
}
```
what is the reborrowing change:
* if you are in a move closure,
* and you have mut-ref access to a place P that involves a deref of an `&mut` reference,
* you will have a mut-ref upvar for P
* if you are in a move closure,
* and you have ref access to a place P that involves a deref of an `&` reference,
* you will have a ref upvar for P
* two phases for the analysis
* compute a set `S` of accesses `{(Mode, Place)}` where:
* `Mode` is `by-value`, `ref`, or `mut-ref`
* and `Place` is a place
* min-analysis that takes `S` and produces a set of `S' = {(Mode, Place)}` of captures
* first part, computing the set `S` of accesses, before reborrows:
* if we have a move closure C:
* for any access (_, P) in C:
* we create an access in S like `(Move, P)`
* if we have a non-move closure C:
* for any access (M, P) in C:
* we create an access in S like `(M, P)`
* first part, computing the set `S` of accesses, after reborrows:
* if we have a move closure C:
* for any access (M, P) in C:
* if M = mut-ref and P contains a deref of an `&mut`, add `(MutRef, P)` to `S`
* else if M = ref and P contains a deref of an `&`, add `(Ref, P)` to `S`
* else create an access in S like `(Move, P)`
* if we have a non-move closure C:
* for any access (M, P) in C:
* we create an access in S like `(M, P)`
* second part, min-capture-analysis will reconcile accesses to overlapping paths:
* for moves, it will truncate at derefs
* in general if you have an access to `P` and an access to some suffix `P.Q`, we prefer `P`
* pick the strictest mode (mode, mut-ref, ref)
### Examples
```rust=
struct S { x: &mut String, y: &mut String }
fn foo(s: S) {
// Closure will:
// * move `s`
let c = move || {
s.x.truncate(0); // without this, you would move `s` because you stop at the deref
drop(s.x.something);
};
// The set S is (MutRef, *s.x), (Move, (*s.x).something)
// Min-capture-analysis is going to truncate the move to `s.x`
// we can drop the `(MutRef, *s.x)` access as redundant
// So we wind up capturing `s.x` by move
}
```
```rust=
struct S { x: &mut String, y: &mut String }
fn foo(s: S) {
// Closure will:
// * move `s`
let c = move || {
s.x.truncate(0); // without this, you would move `s` because you stop at the deref
drop(s.y.something);
};
// The set S is (MutRef, *s.x), (Move, (*s.y).something)
// Min-capture-analysis is going to truncate the move to `s.y`
// So we wind up capturing `*s.x` by mut ref, s.y by move
}
```
```rust=
struct S { x: String, y: String }
fn foo(s: &mut S) {
// Closure will:
// * move `s`
let c = move || {
s.x.truncate(0); // without this, you would move `s` because you stop at the deref
drop(s.y);
};
// The set S is (MutRef, (*s).x), (Move, (*s).y)
// Min-capture-analysis is going to truncate the move to `s`, mut-ref is redundant
// So we wind up capturing `s` by move
}
```
## 2021-02-03
[playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=58f89cabb53fc11237c7a5affc191182)
```rust
struct S(i32);
// E0
fn foo() {
let b = Box::new(S(10));
let c = #[rustc_capture_analysis] move || {
let _b = b.0; // consume *b.0, Copy
};
c();
}
```
* Treat a Consume of a Copy type as "mode = ref"
* Algorithm above doesn't apply because there is no deref of a `&T`, only a deref of a `Box`
* Niko's hypothesis
* previous example E0 wants to capture `(*b).0` by ref -- no, this is false
* we don't want to do this because it would limit the closure to not outlive the local variable `b`
* Behavior: just capture `b` by move
If you wanted to not move `b`, would have to remove `move` or do:
```rust
struct S(i32);
// E1
fn foo() {
let b = Box::new(S(10));
let b1 = &b;
let c = #[rustc_capture_analysis] move || {
let _b = b1.0; // consume *b.0, Copy
};
c();
}
```