Feb 21, 2025
2 min read
Rust,

Rust 1.85.0 带来的 RPIT 生存周期捕获规则是什么?

Rust 1.85.0 引入了 RPIT(Return Position Impl Trait)生命周期捕获的新规则。在 Rust 2024 中,所有作用域内的泛型参数(包括生命周期)默认被隐式捕获,无需显式声明。开发者可通过 `use<...>` 语法精确控制捕获的参数,避免意外捕获。这简化了代码并减少了错误,增强了表达力。新规则使 RPIT 的使用更加直观和安全。

Rust 2024 中有一个比较大的发动是关于 RPIT(Return Position Impl Trait) 生存周期的捕获规则。在Rust 2024版本中,主要的变化是当没有使用use<..>绑定时,所有在作用域内的泛型参数(包括生命周期参数)会被隐式捕获。而在此之前,Rust 2021及更早版本中,生命周期的捕获需要显式出现在RPIT的签名中。

什么是 RPIT?

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

上面的 impl Trait 在返回位置时被称为 RPIT,允许返回具体类型但隐藏细节。

旧版本的问题

在 Rust 2021 及更早版本中,生命周期捕获规则不一致,函数签名中是否显式出现生命周期会影响隐式捕获。

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 下面这个,在 2024 版本之前,是通不过的。
fn foo<'a>(x: &'a i32) -> impl Sized {
    x // 隐式捕获 'a,但可能导致意外的生命周期依赖
}

为了正确捕获生命周期,开发者需要使用 Captures trait 或 outlives 技巧(如 T: 'a 约束)。

新规则的改进

Rust 2024 新规则是:所有在作用域内的泛型参数(包括生命周期)默认被隐式捕获,并通过 use<...> 语法选择需要捕获的参数,避免意外捕获。例如下面的示例会在 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)
}

更简洁的对比:

// Rust 2021: 需显式捕获生命周期
fn old<'a>(x: &'a i32) -> impl Sized + 'a { x }

// Rust 2024: 自动隐式捕获生命周期
fn new(x: &i32) -> impl Sized { x }

我们可以使用  use<...> 显式声明捕获:

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

当返回值不依赖入参的生命周期时,也可以不捕获:

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

这样,就做到了所有 RPIT 上下文统一捕获所有在作用域的泛型参数(除非用 use<> 显式禁止)。

可以看到,新的规则增强代码表达力,通过 use<..> 语法实现精确的泛型参数捕获控制,减少隐式行为带来的意外错误。

新发布的 Rust 1.85.0 已经默认开启 edition=2024