4.7.1 组合 Trait

译文 · 基于 Learning Rust

组合 Trait

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

Rust 不支持直接将多个主 trait(非自动 trait)组合为一个 dyn Trait1 + Trait2:

1
2
3
4
5
trait Foo { fn foo(&self) {} }
trait Bar { fn bar(&self) {} }

// 失败
let _: Box<dyn Foo + Bar> = todo!();

然而,超 trait 的方法对子 trait 可用。什么是超 trait?超 trait 是在子 trait 定义中对 Self 的 trait 约束,如下所示:

1
2
3
4
5
6
7
8
# trait Foo { fn foo(&self) {} }
# trait Bar { fn bar(&self) {} }
trait Subtrait: Foo
//    ^^^^^^^^^^^^^  超 trait 约束
where
    Self: Bar,
//  ^^^^^^^^^ 另一个
{}

只要存在子 trait 约束,超 trait 约束就被隐含,且超 trait 的方法在子 trait 的实现者上始终可用。

利用这些关系,你可以通过使用 dyn Subtrait 来支持类似 dyn Foo + Bar 的功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# trait Foo { fn foo(&self) {} }
# trait Bar { fn bar(&self) {} }
# impl Foo for () {}
# impl Bar for () {}
trait Subtrait: Foo + Bar {}

// 为所有满足约束的类型提供 blanket 实现……
// ……包括非 `Sized` 类型
impl<T: ?Sized> Subtrait for T where T: Foo + Bar {}

fn main() {
    let quz: &dyn Subtrait = &();
    quz.foo();
    quz.bar();
}

请注意,尽管术语如此,子 trait 与超 trait 之间并不存在子/超类型关系,dyn SubTrait 与 dyn SuperTrait 之间亦然,这些 trait 的实现者之间亦然。Trait 与上下级类型无关。

手动超 trait 向上转型

超 trait 向上转型 在 Rust 1.86 中稳定。在此之前仍可行,但需要自行提供实现。本节说明如何自行提供实现,因为:

  • 若必须支持 Rust 1.86 之前的版本,仍适用
  • 带有 blanket 实现的超 trait 这一通用模式很常见
  • 拥有向上转型方法可能比强制转换更易用

但若兼容性或易用性不是问题,你可以跳过本页其余内容。语言层面的超 trait 向上转型可能已足够满足需求。

进入手动实现!首先,我们可以将向上转型直接内置到 trait 中,如下:

1
2
3
4
trait Foo {
    fn foo(&self) {}
    fn as_dyn_foo(&self) -> &dyn Foo;
}

但我们无法提供默认函数体,因为执行类型擦除转型到 dyn Foo 需要 Self: Sized。我们不希望有此限制,否则该方法在 dyn Supertrait 上不可用(dyn Supertrait 不是 Sized)。让每个实现都提供函数体也会很麻烦。

因此,我们可以将方法分离出来,并通过另一个超 trait 为所有 Sized 类型提供实现:

1
2
3
4
5
trait AsDynFoo {
    fn as_dyn_foo(&self) -> &dyn Foo;
}

trait Foo: AsDynFoo { fn foo(&self) {} }

然后为所有 Sized + Foo 类型提供实现:

1
2
3
4
5
6
7
# trait AsDynFoo { fn as_dyn_foo(&self) -> &dyn Foo; }
# trait Foo: AsDynFoo { fn foo(&self) {} }
impl<T: /* Sized + */ Foo> AsDynFoo for T {
    fn as_dyn_foo(&self) -> &dyn Foo {
        self
    }
}

编译器将为 dyn AsDynFoo 和 dyn Foo 提供实现。

当我们将上述内容与前面的 Subtrait 组合在一起时,就可以使用显式的超 trait 向上转型版本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
trait Foo: AsDynFoo { fn foo(&self) {} }
trait Bar: AsDynBar { fn bar(&self) {} }

impl Foo for () {}
impl Bar for () {}

trait AsDynFoo { fn as_dyn_foo(&self) -> &dyn Foo; }
trait AsDynBar { fn as_dyn_bar(&self) -> &dyn Bar; }
impl<T: Foo> AsDynFoo for T { fn as_dyn_foo(&self) -> &dyn Foo { self } }
impl<T: Bar> AsDynBar for T { fn as_dyn_bar(&self) -> &dyn Bar { self } }

trait Subtrait: Foo + Bar {}
impl<T: ?Sized> Subtrait for T where T: Foo + Bar {}

fn main() {
    let quz: &dyn Subtrait = &();
    quz.foo();
    quz.bar();
    let _: &dyn Foo = quz.as_dyn_foo();
    let _: &dyn Bar = quz.as_dyn_bar();
}

为作对比,看看内置 trait 向上转型有多简洁:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
trait Foo { fn foo(&self) {} }
trait Bar { fn bar(&self) {} }

impl Foo for () {}
impl Bar for () {}

trait Subtrait: Foo + Bar {}
impl<T: ?Sized> Subtrait for T where T: Foo + Bar {}

fn main() {
    let quz: &dyn Subtrait = &();
    quz.foo();
    quz.bar();
    let _: &dyn Foo = quz;
    let _: &dyn Bar = quz;
}

更少的超 trait 与 blanket 实现噪音,直接强制转换而非通过方法调用。对这个示例而言无疑是改进!

我们稍后将会看到,在某些情况下,由于方法链的存在,拥有该方法可能更易用。

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