Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 76 additions & 8 deletions src/rust-2021/disjoint-capture-in-closures.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Summary

- `|| a.x + 1` now captures only `a.x` instead of `a`.
- This can subtly change the drop order of things.
- This can subtly change the drop order of things and whether auto traits are applied to closures or not.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- This can subtly change the drop order of things and whether auto traits are applied to closures or not.
- This can cause subtle changes to the timing when variables are dropped the drop order as well as to whether the closure implements auto traits like `Send`.
- In most cases, these changes are not harmful, but the migration lint will nonetheless avoid them by inserting statements like `let _ = &x` to force an entire variable to be captured.


## Details

Expand All @@ -29,12 +29,80 @@ let c = || println!("{}", a.y); // Error: Tries to capture all of `a`
c();
```

Starting in Rust 2021, closures will only capture the fields that they use.
Starting in Rust 2021, closures will only capture the fields that they use, eliminating common borrow check errors.
So, the above example will compile fine in Rust 2021.

This new behavior is only activated in the new edition,
since it can change the order in which fields are dropped.
As for all edition changes, an automatic migration is available,
which will update your closures for which this matters.
It can insert `let _ = &a;` inside the closure to force the entire
struct to be captured as before.
Disjoint capture was proposed as part of [RFC 2229](https://github.com/rust-lang/rfcs/blob/master/text/2229-capture-disjoint-fields.md), and we suggest reading the RFC to better understand motivations behind the feature.

## Changes to semantics because of disjoint capture
Disjoint captures introduces a minor breaking change to the language. This means that there might be observable changes or valid Rust 2018 code that fails to compile once you move to Rust Edition 2021.

When running `cargo fix --edition`, Cargo will update the closures in your code to help you migrate to Rust 2021, as described below in [Migrations](#migrations).
### Wild Card Patterns
Closures now only capture data that needs to be read, which means the following closures will not capture `x`

```rust
let x = 10;
let c = || {
let _ = x;
};

let c = || match x {
_ => println!("Hello World!")
};
```

### Drop Order
Since only a part of a variable might be captured instead of the entire variable, when different fields or elements (in case of tuple) get drop, the drop order might be affected.

```rust
# fn move_value<T>(_: T){}
{
let t = (vec![0], vec![0]);

{
let c = || {
move_value(t.0); // t.0 is moved here
};
} // c and t.0 dropped here
} // t.1 dropped here

```


### Auto Traits
Structs or tuples that implement auto traits and are passed along in a closure may no longer guarantee that the closure can also implement those auto traits.

For instance, a common way to allow passing around raw pointers between threads is to wrap them in a struct and then implement `Send`/`Sync` auto trait for the wrapper. The closure that is passed to `thread::spawn` uses the specific fields within the wrapper but the entire wrapper is captured regardless. Since the wrapper is `Send`/`Sync`, the code is considered safe and therefore compiles successfully.

With disjoint captures, only the specific field mentioned in the closure gets captured, which wasn't originally `Send`/`Sync` defeating the purpose of the wrapper.


```rust
use std::thread;

struct Ptr(*mut i32);
unsafe impl Send for Ptr {}


let mut x = 5;
let px = Ptr(&mut x as *mut i32);

let c = thread::spawn(move || {
unsafe {
*(px.0) += 10;
}
}); // Closure captured px.0 which is not Send
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it would be nice to have a discussion in these sections about what the idiomatic code would look like in 2021. Are people expected to write let _ = &px; under normal circumstances? Or is there another way this example could be cleanly written in 2021?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the expectation is that they would write let _ = &px;.

Any thoughts @arora-aman?

Copy link

@arora-aman arora-aman Jul 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't usually expect users needing to use let _ = &px in a usual case.

However we should probably add a section saying that this feature/edition 2021 might increase the sizes of their closure and if they are working in a memory critical codebase they can use -Zprofile-closures to get an idea of the effect on the size of closures. The profile dumped by -Zprofile-closures contains the size of the closures before and after the edition/feature along with where in the sourcebase the closure is located. In the case where a closure isn't benefiting from disjoint capture they can force the variables to be completely captured similar to technique used for migration within the closure body.

let _ = &var;
let _ = (&var1, &var2, &var3);

## Migrations

This new behavior is only activated starting in the 2021 edition,
since it can change the order in which fields are dropped and can impact auto trait usage with closures.

When running `cargo fix --edition`, the closures in your code may be updated so that they will retain the old behavior on both the 2018 and 2021 editions.
It does this by enabling the `disjoint_capture_migration` lint which adds statements like `let _ = &a;` inside the closure to force the entire variable to be captured as before.

After migrating, it is recommended to inspect the changes and see if they are necessary.
After changing the edition to 2021, you can try to remove the new statements and test that the closure works as expected.
You should review these closures, and ensure that the changes described above will not cause any problems.