Feb 21, 2025
3 min read
Rust,

What Are the RPIT Lifetime Capture Rules Introduced in Rust 1.85.0?

Rust 1.85.0 introduces new rules for RPIT (Return Position Impl Trait) lifetime capture. In Rust 2024, all generic parameters (including lifetimes) within scope are implicitly captured by default, without explicit declaration. Developers can use the `use<...>` syntax to precisely control which parameters are captured, avoiding unintended captures. This simplifies code and reduces errors, enhancing expressiveness. The new rules make RPIT usage more intuitive and safer.

One of the significant changes in Rust 2024 is the new rules for RPIT (Return Position Impl Trait) lifetime capture. In Rust 2024, when use<..> binding is not used, all generic parameters (including lifetime parameters) within scope are implicitly captured. In contrast, in Rust 2021 and earlier versions, lifetimes had to be explicitly included in the RPIT signature.

What Is RPIT?

fn get_iter() -> impl Iterator<Item = i32> { 
    vec![1, 2, 3].into_iter() 
}

The above impl Trait at the return position is called RPIT, allowing the return of a concrete type while hiding implementation details.

Problems with Older Versions

In Rust 2021 and earlier versions, lifetime capture rules were inconsistent, and whether lifetimes appeared explicitly in function signatures affected implicit captures.

trait Captures<U:?Sized> {}
impl<T: ?Sized, U: ?Sized> Captures<U> for T {}

fn f<'a, T>(x: &'a i32, y: T) -> impl Sized + Captures<&'a T> {
    (x, y)
}

// or the following would not compile before version 2024.
fn foo<'a>(x: &'a i32) -> impl Sized {
    x // Implicitly captures 'a, but could lead to unexpected lifetime dependencies
}

To correctly capture lifetimes, developers needed to use the Captures trait or outlives techniques (such as T: 'a constraints).

Improvements in the New Rules

In Rust 2024, the new rule is that all generic parameters (including lifetimes) within scope are implicitly captured by default, and the use<...> syntax can be used to specify which parameters should be captured, avoiding unintended captures. For example, the following code now compiles in Rust 2024:

trait Captures<U:?Sized> {}
impl<T: ?Sized, U: ?Sized> Captures<U> for T {}

fn f<'a, T>(x: &'a i32, y: T) -> impl Sized {
    (x, y)
}

A more concise comparison:

// Rust 2021: Explicit lifetime capture required
fn old<'a>(x: &'a i32) -> impl Sized + 'a { x }

// Rust 2024: Automatically captures lifetimes implicitly
fn new(x: &i32) -> impl Sized { x }

We can use use<...> to explicitly declare captures:

fn foo<'a>(x: &'a i32) -> impl Sized + use<'a> { 
    x 
}

When the return value does not depend on input lifetimes, capturing can be avoided:

fn foo<'a>(x: &'a i32) -> impl Sized + use<> { 
    123 
}

This ensures that all RPIT contexts uniformly capture all generic parameters within scope (unless explicitly prohibited with use<>).

The new rules enhance code expressiveness by providing precise control over generic parameter capture through the use<..> syntax, reducing unexpected errors from implicit behavior.

The newly released Rust 1.85.0 defaults to edition=2024.