10 trait 生命周期边界脚注

原文链接: https://tfpk.github.io/lifetimekata/chapter_10.html

在第 7 章中,我们讨论了占位生命周期('_)。我们说过 有三种使用方式:

  • 简化 impl 块
  • 在消费/返回需要生命周期的类型时
  • 编写包含引用的 trait 对象。

在第一种情况下,我们看到匿名生命周期只是简化了 我们需要写的内容。

在第二种情况下,我们看到 Rust 建议我们使用它,但 我们并不必须使用——生命周期省略会达到我们想要的效果。

看起来生命周期省略本应满足我们需求、 但实际上除非使用 '_ 否则做不到的情况,就是 trait 对象。 本章将说明 trait 对象与生命周期如何协同工作。

我们先搭建一个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
trait Bool {
    fn truthiness(&self) -> bool;
}

struct True();
impl Bool for True {
    fn truthiness(&self) -> bool {
        true
    }
}

struct False();
impl Bool for False {
    fn truthiness(&self) -> bool {
        false
    }
}

fn get_bool(b: bool) -> Box<dyn Bool> {
    if b == true {
        Box::new(True())
    } else {
        Box::new(False())
    }
}

fn main() {
    let my_bool = true;
    let my_dyn = get_bool(my_bool);

    println!("{}", my_dyn.truthiness());
}

明确一下,这里我们创建了两个结构体,分别表示 true 和 false。它们都实现了 Bool trait,该 trait 有 truthiness 函数,返回 true 或 false。

get_bool 函数根据传入的是 true 还是 false,返回一个装箱的 Bool trait 对象。

重要的是要意识到,由于 trait 对象可能包含引用(也可能不包含,或包含任意数量的引用), 所有 trait 对象都有生命周期。 即使该 trait 的所有实现者都不包含引用,这一点也成立。{{footnote:https://doc.rust-lang.org/reference/types/trait-object.html#trait-object-lifetime-bounds}}

因此,既然我们需要为 trait 对象关联一个生命周期,我们可能会 认为可以依赖生命周期省略。但生命周期省略 对我们的 get_bool 函数会怎么工作?没有输入引用,那么 应该给 trait 对象什么输出生命周期?生命周期省略在这里帮不上 我们。

于是,在 RFC 599 和 RFC 1156 中,trait 对象生命周期的规则被修改了。 这些规则很复杂,最好参考参考文档中的说明, 但就 get_bool 而言,这意味着为 dyn Bool 推断出的生命周期是 'static。

现在我们稍微修改一下例子,让结构体包含一个 对 bool 的引用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
trait Bool {
    fn truthiness(&self) -> bool;
}

// 改动 1:在此处添加 &'a bool
struct True<'a>(&'a bool);
impl<'a> Bool for True<'a> {
    fn truthiness(&self) -> bool {
        true
    }
}

// 改动 2:在此处添加 &'a bool
struct False<'a>(&'a bool);
impl<'a> Bool for False<'a> {
    fn truthiness(&self) -> bool {
        false
    }
}

fn get_bool(b: &bool) -> Box<dyn Bool> {
    if *b == true {
        Box::new(True(b))
    } else {
        Box::new(False(b))
    }
}

// 改动 3:更新 main
fn main() {
    let my_dyn = {
        let my_bool = true;
        get_bool(&my_bool)
        // my_bool 在此处被 drop,因此我们返回的 trait 对象
        // 含有悬垂引用。
    };
    println!("{}", my_dyn.truthiness());
}

现在,我们会得到一个错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
error: lifetime may not live long enough
  --> src/main.rs:22:5
   |
21 |   fn get_bool(b: &bool) -> Box<dyn Bool> {
   |                  - let's call the lifetime of this reference `'1`
22 | /     if *b == true {
23 | |         Box::new(True(b))
24 | |     } else {
25 | |         Box::new(False(b))
26 | |     }
   | |_____^ returning this value requires that `'1` must outlive `'static`
   |
help: to declare that the trait object captures data from argument `b`, you can add an explicit `'_` lifetime bound
   |
21 | fn get_bool(b: &bool) -> Box<dyn Bool + '_> {
   |                                       ++++

error: could not compile __ due to previous error

尽管按照生命周期省略,get_bool 最终应得到类似 fn get_bool<'elided>(b: &'elided bool) -> Box<dyn Bool + 'elided> 的签名,但实际上并非如此。trait 对象的特殊规则意味着 实际签名是:fn get_bool<'elided>(b: &'elided bool) -> Box<dyn Bool + 'static>。那个 'static 边界是不正确的。

因此,我们需要 '_ 边界(正如这条错误信息告诉我们的),以告知 Rust 应使用 正常的生命周期省略规则,而不是 trait 对象的 特殊规则。

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