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:
- By default, an
-> impl Traitreturn type automatically captures ALL in-scope type and lifetime parameters of the function! - When you want to exclude a parameter's lifetime (so the returned
impl Traitcan outlive a temporary input reference), you use the new precise capturing syntaxuse<'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. |