4.5.3.2 高级准则

译文 · 基于 Learning Rust

高级准则

原文链接: https://quinedot.github.io/rust-learning/dyn-elision-advanced.html

本节介绍如何为自己的泛型数据类型引导省略行为,并指出与上一节基本准则相比的一些例外。

引导自有类型的行为

声明带生命周期参数 'a 和 trait 参数 T: ?Sized 的自定义类型时,包含显式 T: 'a 约束会使省略行为与引用 &'a T 及 Ref<'a, T> 等 std 类型相同:

1
2
3
// 当 `T` 被替换为省略生命周期的 `dyn Trait` 时,在函数体外
// 省略的生命周期默认为 `'a`
struct ExplicitOutlives<'a, T: 'a + ?Sized>(&'a T);

若类型没有生命周期参数,或类型参数与生命周期参数之间没有约束,省略的 dyn Trait 生命周期默认为 'static,如 Box<T>。即使存在隐式 T: 'a 约束也如此。 例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
trait Trait {}
impl Trait for () {}

// 由于 `&'a T` 字段存在*隐式* `T: 'a` 约束(RFC 2093)
struct InferredOutlivesOnly<'a, T: ?Sized>(&'a T);

// 但此函数期望 `InferredOutlivesOnly<'a, dyn Trait + 'static>`
fn example<'a>(ioo: InferredOutlivesOnly<'a, dyn Trait>) {}

// 因此无法编译
fn attempt<'a>(ioo: InferredOutlivesOnly<'a, dyn Trait + 'a>) {
    example(ioo);
}

若在 struct 定义中显式写出 T: 'a,示例将可编译。

若 T: 'a 是类型的推断约束且 T: ?Sized,建议包含显式 T: 'a 约束。

尽管极其微妙,这确实是有意设计。

歧义约束

若类型定义中有多个生命周期约束,即使其中一个为 'static(或更一般地,即使一个生命周期已知长于另一个),约束仍被视为歧义。此类结构体罕见,若有,通常必须显式写出 dyn Trait 生命周期:

1
2
3
4
5
6
7
# trait Trait {}
# impl Trait for () {}
struct S<'a, 'b: 'a, T: 'a + 'b + ?Sized>(&'a &'b T);

// error[E0228]: the lifetime bound for this object type cannot be deduced
// from context; please supply an explicit bound
const C: S<dyn Trait> = S(&&());

但在函数体内,生命周期仍被推断;且推断独立于生命周期类型的任何注解:

1
2
3
4
5
6
7
8
9
# trait Trait {}
# impl Trait for () {}
struct Weird<'a, 'b, T: 'a + 'b + ?Sized>(&'a T, &'b T);

fn example<'a, 'b>() {
    // `dyn Trait + 'a` 或 `dyn Trait + 'b` 都是错误,
    // 因此 `dyn Trait` 生命周期必须独立于 `'a` 和 `'b` 推断
    let _: Weird<'a, 'b, dyn Trait> = Weird(&(), &());
}

(这与参考手册文档相反,且讽刺地比非歧义类型更灵活。在此例中,生命周期将类似之前提到的生命周期交集推断。)

与类型别名的交互

使用类型别名时,type 别名上生命周期参数与类型参数之间的约束决定 dyn Trait 生命周期省略如何行为,覆盖被别名类型的约束(无论更强或更弱)。

1
2
3
4
5
6
7
8
# trait Trait {}
// 没有 `T: 'a` 约束时,此别名的默认 trait 对象生命周期是 `'static`
type MyRef<'a, T> = &'a T;

// 因此可编译
fn foo(mr: MyRef<'_, dyn Trait>) -> &(dyn Trait + 'static) {
   mr
}
1
2
3
4
5
6
7
8
# trait Trait {}
// 有 `T: 'a` 约束时,此别名的默认 trait 对象生命周期是生命周期参数
type MyOtherRef<'a, T: 'a> = MyRef<'a, T>;

// 因此不能编译
fn bar(mr: MyOtherRef<'_, dyn Trait>) -> &(dyn Trait + 'static) {
   mr
}

见 issue 100270。 未记录。

关联类型和 GAT

dyn Trait 生命周期省略在此上下文适用。但需注意:

  • 关联类型和 GAT 上的约束似乎无效
  • 不允许省略非 dyn Trait 生命周期

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# trait Trait {}
trait Assoc {
    type T: ?Sized;
}

impl Assoc for () {
    // dyn Trait + 'static
    type T = dyn Trait;
}

impl<'a> Assoc for &'a str {
    // &'a (dyn Trait + 'a)
    type T = &'a dyn Trait;
    // 引用生命周期被省略,这是编译错误
    // type T = &dyn Trait;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# trait Trait {}
# impl Trait for () {}
trait BoundedAssoc<'x> {
    type BA: 'x + ?Sized;
}

// 仍是 `dyn Trait + 'static`
impl<'x> BoundedAssoc<'x> for () {
    type BA = dyn Trait;
}

// 失败,因为 `'a` 可能不是 `'static`
fn bib<'a>(obj: Box<dyn Trait + 'a>) {
    let obj: Box< <() as BoundedAssoc<'a>>::BA > = obj;
}

函数体内推断的例外

函数体内省略的 dyn Trait 生命周期被推断,也有例外。若有类似引用的类型,且用命名生命周期注解非 dyn Trait 的生命周期,则省略的 dyn Trait 生命周期与注解的生命周期相同(类似函数体外行为):

1
2
3
4
5
6
7
8
trait Trait {}
impl Trait for () {}

fn example<'a>(arg: &'a ()) {
    let dt: &'a dyn Trait = arg;
    // 失败
    let _: &(dyn Trait + 'static) = dt;
}

根据参考手册,&dyn Trait 应总是如此行为。但若外层生命周期被省略或对 '_ 使用外层生命周期,dyn Trait 生命周期独立于引用生命周期推断:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# trait Trait {}
# impl Trait for () {}
fn example() {
    let local = ();

    // 外层引用生命周期不能是 `'static`...
    let obj: &dyn Trait = &local;

    // 但 `dyn Trait` 生命周期是!
    let _: &(dyn Trait + 'static) = obj;
}

任何地方都没有记录,且与参考手册冲突。在此实现, 无团队输入或 FCP。🤷

但由于此行为,在函数体内注解生命周期很少见,因此遇到问题的概率较低。

尚未涵盖的内容

据我们所知,这涵盖了trait 本身没有生命周期约束时 dyn Trait 生命周期省略的行为。trait 本身的非 'static 生命周期约束导致更细微的行为;我们在下一节涵盖部分。

高级准则摘要

总之,忽略 trait 上的生命周期约束时,dyn Trait 生命周期省略有三类常见情况:

  • 对没有(显式)生命周期约束的类型参数为 'static
    • 例如 Box<dyn Trait>(Box<dyn Trait + 'static>)
    • 例如 struct Unbounded<'a, T: ?Sized>(&'a T)
  • 对具有单一(显式)生命周期约束的类型参数为另一生命周期参数
    • 例如 &'a dyn Trait(&'a dyn Trait + 'a)
    • 例如 Ref<'a, dyn Trait>(Ref<'a, dyn Trait + 'a>)
    • 例如 struct Bounded<'a, T: 'a + ?Sized>(&'a T)
  • 因多个约束而歧义(罕见)
    • 例如 struct Weird<'a, T: 'a + 'static>(&'a T);

各上下文中的行为:

staticimpl[G]ATfn infn outfn body
Box<dyn Trait>'static'static'static'static'staticInferred
&dyn TraitRefRefE0637RefRefInferred
&'a dyn TraitRefRefRefRefRefRef
Ambig.E0228E0228E0228E0228E0228Inferred

附注:

  • type 别名约束优先于被别名类型约束
  • 关联类型和 GAT 约束不影响默认

对比,「普通」省略规则如下:

staticimpl[G]ATfn infn outfn body
Box<dyn Tr + '_>'staticFreshE0637FreshElisionInferred
&(dyn Trait + '_)'staticFreshE0637FreshElisionInferred
&'a (dyn Trait + '_)'staticFreshE0637FreshElisionInferred
Ambig. with '_'staticFreshE0637FreshElisionInferred
最后修改 August 23, 2026: 更新 (499855b16)