Rust 2024 Edition

RPIT Lifetime Capture Changes

When a function returns impl Trait (Return Position impl Trait / RPIT), the opaque return type may borrow references from the function's parameters (for example, returning a lazy Map iterator that borrows &'a [T] and &'b str).

In Rust 2021 and earlier, RPIT only captured lifetimes that appeared syntactically inside the trait bounds of the impl Trait return type—requiring awkward + '_ or Captures<'a> workarounds whenever a returned iterator borrowed an input lifetime that wasn't mentioned in the Iterator::Item type!

In the Rust 2024 edition, RPIT follows the 2024 Lifetime Capture Rules:

  1. By default, an -> impl Trait return type automatically captures ALL in-scope type and lifetime parameters of the function!
  2. When you want to exclude a parameter's lifetime (so the returned impl Trait can outlive a temporary input reference), you use the new precise capturing syntax use<'a, T> (-> impl Trait + use<T> or -> impl Trait + use<>).
Function Signature:
  `fn filter_prefix<'a, 'p>(items: &'a [String], prefix: &'p str) -> impl Iterator<Item = &'a str>`

  Rust 2021 Default:
    Only captured `'a` (because `'a` appeared in `Item = &'a str`).
    Failed to compile if the returned iterator closure also read `prefix: &'p str`
    unless you wrote `+ 'p` or a helper trait!

  Rust 2024 Default:
    Automatically captures BOTH `'a` and `'p`! Compiles out of the box!
    And if the iterator does NOT keep `prefix` alive after `filter_prefix` returns,
    write `+ use<'a>` to explicitly release `'p`!

1. Ergonomic Multi-Lifetime Capture Out of the Box in Rust 2024

In Rust 2024, returning a lazy iterator that borrows two independent input references (items and prefix) works naturally without any lifetime gymnastics:

/// In Rust 2024, `-> impl Iterator<Item = &'a str>` automatically captures both
/// the slice lifetime `'a` AND the anonymous lifetime of `prefix: &str`!
pub fn matching_items<'a>(
    items: &'a [String],
    prefix: &str,
) -> impl Iterator<Item = &'a str> {
    items
        .iter()
        .map(String::as_str)
        .filter(move |s| s.starts_with(prefix))
}

fn main() {
    let catalog = vec![
        String::from("rust-core"),
        String::from("python-sdk"),
        String::from("rust-alloc"),
    ];

    let prefix = String::from("rust-");
    let matched: Vec<&str> = matching_items(&catalog, &prefix).collect();

    assert_eq!(matched, vec!["rust-core", "rust-alloc"]);
    println!("Matched items: {matched:?}");
}
Matched items: ["rust-core", "rust-alloc"]

2. Restricting Captured Lifetimes with use<...> Precise Capturing

What if a function takes a reference parameter (config: &Config) only to read a Copy field during setup, and the returned impl Iterator does not actually borrow config?

Because Rust 2024 captures all in-scope lifetimes by default, the compiler would assume the returned iterator still borrows config unless told otherwise. Adding + use<'data> (or + use<> when capturing no lifetimes or generics at all) explicitly specifies the exact set of generic parameters and lifetimes captured by the opaque return type:

pub struct FilterConfig {
    pub min_value: i32,
}

/// Reads `cfg.min_value` (`i32`, which is `Copy`) upfront, and uses `+ use<'data>`
/// to declare that the returned iterator ONLY captures `'data` (and NOT `cfg`'s lifetime)!
pub fn above_threshold<'data>(
    data: &'data [i32],
    cfg: &FilterConfig,
) -> impl Iterator<Item = i32> + use<'data> {
    let threshold = cfg.min_value; // Copied upfront! `cfg` is no longer needed.
    data.iter().copied().filter(move |&x| x >= threshold)
}

fn main() {
    let numbers = [5, 15, 25, 35];

    let iter = {
        let temporary_cfg = FilterConfig { min_value: 20 };
        // `temporary_cfg` is dropped at the end of this inner block,
        // yet `iter` remains valid because `+ use<'data>` excluded `cfg`'s lifetime!
        above_threshold(&numbers, &temporary_cfg)
    };

    let result: Vec<i32> = iter.collect();
    assert_eq!(result, vec![25, 35]);
    println!("Outlived temporary config cleanly via `+ use<'data>`: {result:?}");
}
Outlived temporary config cleanly via `+ use<'data>`: [25, 35]
Return Type Syntax in Rust 2024 Lifetimes Captured When to Use
-> impl Trait All in-scope lifetime and type parameters Default when the returned iterator/future borrows all input references.
-> impl Trait + use<'a, T> Only 'a and T (must list all in-scope type parameters T) When one or more input reference parameters are only used eagerly before returning.
-> impl Trait + use<> None ('static with no generic type parameters) When a function with no type parameters reads a borrowed input eagerly and returns a self-contained value.