Rust Edition Guide
repository·master·Indexed 43 days ago
https://github.com/rust-lang/edition-guideA resource explaining Rust editions—major milestones in the language's evolution. It provides guidance on how language changes are managed, how to create projects with specific editions using `cargo new --edition`, and detailed instructions for migrating existing projects using `cargo fix --edition` and `cargo fix --edition-idioms`.
What's inside Rust Edition Guide
- The Rust 2024 edition is a major release of the Rust programming language. It is associated with RFC #3501 and was officially released in Rust version 1.85.0. Editions in Rust allow for introducing new language features and breaking changes in a way that is opt-in per crate, ensuring stability for existing codebases while allowing the language to evolve.
Overview of the Rust 2018 Edition
masterThe Rust 2018 edition was introduced to coordinate a suite of features focused on productivity. While most features released alongside it are backwards compatible and available in all editions, certain significant changes—most notably changes to the module system—required the edition mechanism to prevent breaking existing code. The edition system was formally proposed in RFC 2052.Overview of the Rust 2021 Edition
masterThe Rust 2021 Edition introduces changes designed to provide new language capabilities and improve consistency. It was officially released with Rust version 1.56.0. This edition includes various breaking and non-breaking changes that may require code migration. For technical details regarding the design, refer to RFC #3085.Overview of Rustfmt formatting fixes in Rust 2024
masterThe Rust 2024 edition includes several updates torustfmtto improve code consistency and fix edge cases in various formatting scenarios. These changes affect how comments, strings, generics, function signatures, and pattern matching are laid out.Understand the Default Cargo Feature Resolver in Rust 2021
masterIn Rust 2021, setting
edition = "2021"in yourCargo.tomlautomatically enables the version 2 feature resolver (resolver = "2").Unlike the previous resolver, the version 2 resolver does not merge all requested features for crates that are depended on in multiple ways (e.g., once as a normal dependency and once as a build-dependency). This prevents feature leakage but can lead to build failures if dependencies assume certain features are globally enabled.
Key Rules for the New Resolver:
- Features enabled on platform-specific dependencies for targets not currently being built are ignored.
- Build-dependencies and proc-macros do not share features with normal dependencies.
- Dev-dependencies do not activate features unless building a target that needs them (like tests or examples).
Note for Virtual Workspaces: The resolver is a global setting for a workspace and is only honored for the top-level package. If you use a virtual workspace, you must explicitly set the
resolverfield in the[workspace]definition to opt-in to the new resolver.Prevent alignment of unrelated trailing comments
masterIn the 2024 edition,
rustfmtno longer assumes that a comment on a line following an item with a trailing comment should be indented to match that trailing comment. This prevents unrelated comments from being incorrectly aligned with previous trailing comments.// Style edition 2024: pub const IFF_MULTICAST: ::c_int = 0x0000000800; // Supports multicast // Multicast using broadcst. add. fn foo() { let f = bar(); // Donec consequat mi... // Id turpis. Nam posuere lectus vitae nibh... let b = baz(); }Understand disjoint capture in closures
masterIn Rust 2021, closures use more precise capture logic known as disjoint capture. Instead of capturing an entire variable when only one field is used, the closure captures only the specific fields it references.
Comparison
- Rust 2018 and earlier: A closure like
|| a.x + 1captures the entire variablea. This prevents other parts of the code from moving or mutating other fields ofawhile the closure exists. - Rust 2021: The same closure
|| a.x + 1captures onlya.x. This allows other fields ofa(likea.y) to be moved or mutated independently.
Potential Side Effects
Because closures capture less data, two main behaviors might change:
- Drop Order: Destructors (the
Droptrait) may run at different times because the closure no longer holds a reference to the entire parent structure. - Trait Implementations: A closure's implementation of auto-traits like
Send,Sync,UnwindSafe, orClonedepends on the traits of the captured values. Capturing only a subset of fields might cause a closure to lose a trait it previously had.
let a = SomeStruct::new(); drop(a.x); // Move out of one field println!("{}", a.y); // In Rust 2021, this is OK because the closure only captures a.y let c = || println!("{}", a.y); c();- Rust 2018 and earlier: A closure like
How `IntoIterator` for `Box<[T]>` behaves in different editions
masterIn Rust 2024,
Box<[T]>implementsIntoIterator, allowing you to iterate over elements by-value. This changes the behavior of the.into_iter()method call compared to previous editions.Behavior Comparison
Syntax Behavior in Editions < 2024 Behavior in Edition 2024 for x in my_boxed_sliceError (requires .into_iter())Yields values ( T)my_boxed_slice.into_iter()Yields references ( &T)Yields values ( T)Key Changes
- By-value iteration: You can now use
for x in my_boxed_slicedirectly to get owned values, which was previously an error becauseforloops do not automatically dereference boxed slices. - Method call change: In Edition 2024, calling
.into_iter()on a boxed slice now resolves to theIntoIteratorimplementation (yielding values) instead of resolving to a reference iterator (&[T]).
// In Edition 2024, this yields values (u32) let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice(); for x in my_boxed_slice.into_iter() { // x is type u32 }- By-value iteration: You can now use
Understand Cargo target discovery changes in Rust 2018
masterIn the Rust 2018 edition, Cargo's behavior regarding target discovery and path inference has changed:
- Target Discovery: Defining a specific target in your
Cargo.tomlmanifest no longer prevents Cargo from automatically discovering other targets. This allows for more flexible project structures where both explicit and implicit targets coexist. - Target Path Inference: Cargo no longer automatically infers target paths using the
src/{target_name}.rspattern for targets that do not have an explicitpathfield defined inCargo.toml.
- Target Discovery: Defining a specific target in your
Ensure semicolons for return/break/continue in match arms
masterWhen a
return,break, orcontinuestatement is used inside a block within amatcharm, the 2024 edition ensures it is correctly terminated with a semicolon.// Style edition 2024: match 0 { 0 => { return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; } _ => "", };Understand the Never Type (`!`) fallback change
masterAs of Rust 1.100, the behavior of "never-to-any" coercions has changed across all editions. When the compiler encounters a value of type
!(never) at a coercion site and cannot infer the target type, it now falls back to the never type (!) instead of the unit type (()).How it works
When you use a value of type
!where any type is expected (e.g.,let x: u8 = panic!();), the compiler essentially treats it as a call to a function likeabsurd<T>(x: !) -> T { x }.- Old Behavior: If the type couldn't be inferred, it fell back to
(). - New Behavior: If the type cannot be inferred, it falls back to
!.
This change makes type coercion more intuitive by keeping
!as!when no specific type is required, but it may break code that relied on the spontaneous coercion of!to().- Old Behavior: If the type couldn't be inferred, it fell back to
How disjoint capture affects Drop order
masterWhen a closure takes ownership of only a part of a variable, the timing of when values are dropped changes between editions.
Rust 2018 Behavior
If a closure captures the entire tuple
t, both the closure and the tupletare dropped at the same time when the scope ends. If a field was moved into the closure, the remaining fields intare not dropped becausetitself was moved.Rust 2021 Behavior
If a closure captures only
t.0, thent.0is moved into the closure and dropped when the closure is dropped. However,t.1remains in the original scope and is dropped whentgoes out of scope. This change in destruction timing can affect programs that rely on specific side effects inDropimplementations.# fn move_value<T>(_: T){} { let t = (vec![0], vec![0]); { let c = || { // In Rust 2018, captures all of `t`. // In Rust 2021, captures only `t.0` move_value(t.0); }; } // In Rust 2021, `c` and `t.0` are dropped here. } // In Rust 2021, `t.1` is dropped here.