2.3 不要隐藏生命周期

译文 · 基于 Learning Rust

不要隐藏生命周期

原文链接: https://quinedot.github.io/rust-learning/dont-hide.html

当你使用携带生命周期的结构体(无论是自己的还是他人的)时,Rust 编译器有时允许你在提及该结构体时省略生命周期参数:

1
2
3
4
5
6
7
8
9
struct Foo<'a>(&'a str);

impl<'a> Foo<'a> {
    // 不需要生命周期语法 :-(
    //                 vvv
    fn new(s: &str) -> Foo {
        Foo(s)
    }
}

这会让借用关系变得不那么明显,也更难弄清错误从何而来。上述代码过去会被静默接受,但现在有一些 lint(如 mismatched_lifetime_syntaxes)会对最常见的麻烦模式报错。为避免头痛,你甚至可以把这些警告当作错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#![deny(mismatched_lifetime_syntaxes)]
struct Foo<'a>(&'a str);

impl<'a> Foo<'a> {
    // 现在这是错误
    //                 vvv
    fn new(s: &str) -> Foo {
        Foo(s)
    }
}

还有一个默认允许(allow-by-default)的 lint 叫 elided_lifetimes_in_paths,会对被认为较不太可能出问题的代码模式报错。

我在处理他人代码中的借用检查错误时,第一件事就是检查这些 lint。如果你尚未启用这些 lint 却在自己的代码里遇到错误,试试启用它们。对每个报错处,花一点时间停下来想想生命周期发生了什么。有时只有一种可能,你只需做 trivial 的修改来满足 lint:

1
2
-    fn new(s: &str) -> Foo {
+    fn new(s: &str) -> Foo<'_> {

但据我的经验,往往某个报错点就是你正在处理的问题的一部分。(随着 mismatched_lifetime_syntaxes 默认成为警告,这种情况将来可能会变少。)

注意 impl Trait 与 async fn 的捕获

当你看到参数中带生命周期的 async fn 或 -> impl 函数,例如:

1
2
3
fn example(s: &str) -> impl Iterator<Item = Result<String, io::Error>> { ... }

async fn example(v: &mut Vec<String>) -> String { ... }

返回类型默认会包含输入中的所有生命周期,即便没有 '_ 或 & 来表明这一点。(它们也会有效地包含任何类型泛型,而这些泛型本身可能包含生命周期。)

因此,把 -> impl 和 async fn 视为一种信号:输入与输出之间可能存在借用关系,类似于 -> &_ 和 -> Foo<'_> 所表明的那样。

详见本节,了解借用关系如何运作以及如何细化它们。

最后修改 August 23, 2026: 更新 (499855b16)