Rust Edition Guide

repository·master·Indexed 43 days ago

https://github.com/rust-lang/edition-guide

A 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`.

Tokens
27.6K
Snippets
112
Records
154
Agent score
54%

What's inside Rust Edition Guide

  1. Overview of Rust 2024 Edition

    master
    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.
  2. Overview of the Rust 2018 Edition

    master
    The 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.
  3. Overview of the Rust 2021 Edition

    master
    The 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.
  4. Understand the Default Cargo Feature Resolver in Rust 2021

    master

    In Rust 2021, setting edition = "2021" in your Cargo.toml automatically 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 resolver field in the [workspace] definition to opt-in to the new resolver.

  5. Prevent alignment of unrelated trailing comments

    master

    In the 2024 edition, rustfmt no 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();
    }
  6. Understand disjoint capture in closures

    master

    In 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 + 1 captures the entire variable a. This prevents other parts of the code from moving or mutating other fields of a while the closure exists.
    • Rust 2021: The same closure || a.x + 1 captures only a.x. This allows other fields of a (like a.y) to be moved or mutated independently.

    Potential Side Effects

    Because closures capture less data, two main behaviors might change:

    1. Drop Order: Destructors (the Drop trait) may run at different times because the closure no longer holds a reference to the entire parent structure.
    2. Trait Implementations: A closure's implementation of auto-traits like Send, Sync, UnwindSafe, or Clone depends 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();
  7. How `IntoIterator` for `Box<[T]>` behaves in different editions

    master

    In Rust 2024, Box<[T]> implements IntoIterator, allowing you to iterate over elements by-value. This changes the behavior of the .into_iter() method call compared to previous editions.

    Behavior Comparison

    SyntaxBehavior in Editions < 2024Behavior 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_slice directly to get owned values, which was previously an error because for loops do not automatically dereference boxed slices.
    • Method call change: In Edition 2024, calling .into_iter() on a boxed slice now resolves to the IntoIterator implementation (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
    }
  8. Understand Cargo target discovery changes in Rust 2018

    master

    In the Rust 2018 edition, Cargo's behavior regarding target discovery and path inference has changed:

    1. Target Discovery: Defining a specific target in your Cargo.toml manifest no longer prevents Cargo from automatically discovering other targets. This allows for more flexible project structures where both explicit and implicit targets coexist.
    2. Target Path Inference: Cargo no longer automatically infers target paths using the src/{target_name}.rs pattern for targets that do not have an explicit path field defined in Cargo.toml.
  9. Understand the Never Type (`!`) fallback change

    master

    As 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 like absurd<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 ().

  10. How disjoint capture affects Drop order

    master

    When 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 tuple t are dropped at the same time when the scope ends. If a field was moved into the closure, the remaining fields in t are not dropped because t itself was moved.

    Rust 2021 Behavior

    If a closure captures only t.0, then t.0 is moved into the closure and dropped when the closure is dropped. However, t.1 remains in the original scope and is dropped when t goes out of scope. This change in destruction timing can affect programs that rely on specific side effects in Drop implementations.

    # 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.