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 实现噪音,直接强制转换而非通过方法调用。对这个示例而言无疑是改进!
我们稍后将会看到,在某些情况下,由于方法链的存在,拥有该方法可能更易用。