4.7.2 为 Box 实现 Trait
译文 · 基于 Learning Rust
为 Box 实现 Trait
原文链接: https://quinedot.github.io/rust-learning/dyn-trait-box-impl.html
让我们看看如何实现 Trait for Box<dyn Trait + '_>。首先要注意的是,大多数方法都会涉及调用 box 内 dyn Trait 的方法,但若直接使用 self.method(),我们会立刻与正在编写的方法(<Box<dyn Trait>>::method)递归!
在这些情况下,我们需要调用 <dyn Trait>::method 而非 <Box<dyn Trait>>::method,以避免无限递归。
既然已强调这一点,让我们直接开始:
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
| trait Trait {
fn look(&self);
fn boop(&mut self);
fn bye(self) where Self: Sized;
}
impl Trait for Box<dyn Trait + '_> {
fn look(&self) {
// 我们不想这样做!
// self.look()
// 那会递归调用*本*函数!
// 我们需要调用 `<dyn Trait as Trait>::look`。
// 以下形式均可,取决于你希望多明确。
// 非常明确
// <dyn Trait as Trait>::look(&**self)
// 感谢函数参数的自动解引用
// <dyn Trait>::look(self)
// 非常简洁,属于「见多了就懂」的形式。
// 第一次解引用针对引用(`&Self`),第二次针对 `Box<_>`。
(**self).look()
}
fn boop(&mut self) {
// 与 `&self` 情况类似
(**self).boop()
}
fn bye(self) {
// 呃……见下文
}
}
|
哦对了,最后一个。还记得我们之前说的吗?
dyn Trait 没有此方法,但 Box<dyn Trait + '_> 有。
编译器不会在这里猜测该怎么做(而且若需要返回值也无法猜测)。我们无法将 dyn Trait 移出 Box,因为它是 unsized。我们也无法从 dyn Trait 向下转型;即使能,这里也很少有帮助,因为我们既要施加 'static 约束,又要知道实现我们 trait 的每种类型以便对每种尝试向下转型(或采用其他更高效的向下转型方案)。
唉,难怪 Box<dyn Trait> 不会自动实现 Trait。
假设我们想在擦除类型上调用 Trait::bye,是否无路可走?不,有变通办法:
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
| // 超 trait 约束
trait Trait: BoxedBye {
fn bye(self);
}
trait BoxedBye {
// 与 `self: Self` 不同,这*不*隐含 `Self: Sized`,
// 因而*会*对 `dyn BoxedBye + '_`……以及自动对 `dyn Trait + '_` 可用。
fn boxed_bye(self: Box<Self>);
}
// 我们为所有 `Sized` 的 `Trait` 实现者实现此 trait,
// 通过拆箱并调用 `Trait::bye`
impl<T: Trait> BoxedBye for T {
fn boxed_bye(self: Box<Self>) {
<Self as Trait>::bye(*self)
}
}
impl Trait for Box<dyn Trait + '_> {
fn bye(self) {
// 这次传递 `self` 而非 `*self`
<dyn Trait as BoxedBye>::boxed_bye(self);
}
}
|
通过添加超 trait 约束,编译器将为 dyn Trait + '_ 提供 BoxedBye 的实现。该实现将调用 Box<Erased> 的 BoxedBye 实现,其中 Erased 是擦除的基类型。这正是我们的 blanket 实现,它拆箱 Erased 并调用 Erased 的 Trait::bye。
<dyn Trait as BoxedBye>::boxed_bye 的签名中,接收者类型为 Box<dyn Trait + '_>,与 <Box<dyn Trait + '_> as Trait>::bye 的签名完全相同。因此我们得以完成 Trait for Box<dyn Trait + '_> 的实现。
在 Box<dyn Trait + '_> 上调用 Trait::bye 时的调用流程如下:
1
2
3
4
| <Box<dyn Trait>>::bye (_: Box<dyn Trait>) -- 直接传递 -->
< dyn Trait >::boxed_bye(_: Box<dyn Trait>) -- 通过 vtable -->
< Erased >::boxed_bye(_: Box< Erased >) -- 通过拆箱 -->
< Erased >::bye (_: Erased ) :)
|
任何 Sized 的 Trait 实现者都会免费获得我们对 BoxedBye 超 trait 的 blanket 实现,无需做任何特殊处理。
最后一点,我们写的是
1
2
3
4
| # trait Trait {}
impl Trait for Box<dyn Trait + '_> {
// 这里: ^^^^
# }
|
我们不需要要求 'static,因此更灵活。 也很容易忘记。