Rust 2021 makes closure capture more precise
Rust 1.56 ships the 2021 edition with field-level closure capture and other opt-in changes. Cargo also gains an explicit minimum-toolchain declaration without changing dependency resolution.
A closure that uses one field of a structure should usually need that field, not every other field alongside it. Earlier Rust editions capture the entire identifier even when the body mentions only one named field. Rust 2021 makes capture more precise, allowing some useful code to compile while keeping the old behavior available to crates that have not migrated.
The October 21 announcement releases Rust 1.56 and stabilizes the 2021 edition. The edition includes several compatibility-sensitive changes, with closure capture as one of its practical examples. It also adds Cargo's rust-version field, which gives the tool an explicit requirement to check before compilation.
I value Rust's ability to explain access through ownership and borrowing. A capture that corresponds more closely to the code a closure actually uses makes that explanation easier to follow. The edition mechanism matters because a seemingly local improvement can still change observable behavior in existing programs.
A crate chooses its source semantics
The May edition plan explains why editions exist. Stable Rust commits to preserving supported code, but some desirable changes conflict with names or meanings already in use. A new keyword, for example, could invalidate an existing variable with that name if the compiler changed every crate at once.
An edition is a per-crate choice. Existing crates retain their selected edition until they migrate. A newer compiler can therefore support newer source rules without imposing them on every dependency. The choice appears in Cargo.toml, where a package can opt into Rust 2021.
[package]
name = "capture-example"
version = "0.1.0"
edition = "2021"
rust-version = "1.56"
This fragment selects the edition and records a minimum compiler version for the example package. The two fields describe different requirements. The edition selects source behavior, while rust-version says which compiler the package supports. They happen to name the same release boundary in this example because Rust 1.56 introduces this edition.
The essential compatibility rule is that crates using different editions can interoperate. An edition does not create a separate ecosystem requiring every dependency to migrate together. The May plan explains that code from different editions reaches the same compiler representation, constraining which changes an edition can introduce.
This is a useful distinction from a language fork. A library can adopt new source rules while its users retain another edition. The choice still needs a compiler supporting the selected edition, but it does not require every crate in the build to use identical source syntax.
Disjoint capture follows named fields
The release announcement demonstrates a structure where one field has been moved out. Another field remains available for direct use. Before Rust 2021, a closure referring to the second field still attempts to capture the whole structure, which conflicts with the earlier move.
The new edition captures the field the closure uses. This example follows the announcement's partial-move scenario and uses owned strings to make the independent fields visible.
struct Record {
payload: String,
label: String,
}
let record = Record {
payload: String::from("data"),
label: String::from("ready"),
};
drop(record.payload);
let show_label = || println!("{}", record.label);
show_label();
The payload has been moved into drop. The label remains usable, and the closure only needs that label. In earlier editions, code could work around the broad capture by explicitly binding a reference to the field before creating the closure. Rust 2021 lets the capture analysis express that narrower requirement directly.
The benefit is not merely a shorter workaround. The closure's dependency becomes more consistent with its body. A reader sees a reference to label and can reason about the field involved without assuming that the closure also needs ownership or access to payload.
I prefer this behavior because it reduces an incidental restriction without weakening the access rule. The closure still cannot use a moved value. It gains access to the separate field that remains valid, which is the value the code actually requests.
Drop behavior makes this an edition change
More precise capture can change when fields are dropped. The release announcement identifies that effect as the reason to put the behavior behind the edition opt-in. A program can observe destruction order through the work performed by destructors, so the difference is more than a compiler diagnostic improvement.
Migration tooling can preserve the earlier capture where necessary. The announcement gives insertion of a reference to the whole structure inside the closure as an example. That forces the broad capture and retains the behavior that the migration needs to preserve.
This explains an initially surprising property of automated migration. Its first job is compatibility, so it may introduce code that retains an older behavior even though the new edition permits a more precise one. Preserving semantics and adopting every new idiom are different operations.
The release recommends cargo fix and the edition guide for migration, while acknowledging that some corner cases may still need manual changes. An edition upgrade therefore has tooling support without a promise that every possible crate transforms perfectly without review.
Array method calls complete an earlier transition
Rust 1.53 added IntoIterator for arrays across editions. Existing-edition array.into_iter calls retained reference iteration through a compatibility exception. Rust 2021 removes that exception, so the method now iterates over owned items.
The May plan explains why the implementation itself could not simply exist in one edition. Trait implementations must participate in mixed-edition builds. The solution put the implementation everywhere while making method-call resolution edition-dependent at the source boundary where the compatibility problem occurs.
A caller moving to Rust 2021 therefore needs to interpret item ownership correctly. A method call that previously yielded references can now yield values. Fully qualified trait calls and for-loop behavior already had their own route to the owned implementation before the edition change.
This is another example of separating a capability from a familiar spelling. The trait implementation arrived first, while the edition determines when a compatibility-sensitive method call receives the newer interpretation. The release history is necessary to explain why both statements can be true.
Prelude additions affect method resolution too
Rust 2021 adds TryInto, TryFrom, and FromIterator to the prelude. These traits become available without an explicit import. The May plan explains why that convenience needs edition treatment. A newly imported trait can introduce a method name competing with a trait a crate already uses.
Manual imports take precedence over prelude items in some ordinary naming cases, but trait method ambiguity can still arise. A call relying on a locally defined conversion trait may become ambiguous when the standard trait is also in scope. Opting into the edition gives migration tooling a point at which to address that source compatibility.
Cargo's second feature resolver also becomes the edition default. It preserves distinctions between some dependency roles, including host-side build dependencies and normal target dependencies. Selecting Rust 2021 therefore carries a documented build-configuration default as well as syntax and method-resolution changes.
Panic formatting becomes consistent
The edition also makes panic macros require a format string in the same way as println. Earlier behavior treated a single argument differently from multiple arguments. A single string containing formatting braces could be used as the payload without interpreting the braces, while a call with additional arguments performed formatting. The new rule removes that dependence on argument count.
The May plan describes panic_any as the route for a payload other than a formatted string. It also aligns the core and standard-library panic macros. These changes matter when a macro invocation looks familiar but carries a historical interpretation. The edition provides a migration boundary for making that interpretation consistent. Existing-edition code keeps its earlier rules until it opts in.
The minimum compiler field checks a requirement
Cargo now accepts rust-version in the package section and exits early when the active Rust version fails the declared minimum. The release announcement explicitly says the field does not currently influence the dependency resolver. It improves the error users see when a compiler is too old.
That limit is operationally important. Declaring the minimum does not mean Cargo will search for a dependency combination compatible with an older compiler. It states a requirement and checks it. A clearer early failure prevents later errors about unavailable syntax or APIs from being the first explanation a user receives.
The resulting release has two complementary forms of explicit choice. The edition determines which compatible source evolution a crate adopts, and the minimum-version field states a toolchain requirement. Disjoint closure capture shows why both compatibility and improved expressiveness matter. A crate can gain a more precise relationship between a closure and its fields while retaining a controlled migration path for behavior involving destruction.