4.2.12 陷阱:过早伸手拿 `dyn Trait`

12-陷阱:过早伸手拿 dyn Trait — Comprehensive Rust

译文 · 基于 Comprehensive Rust

原文链接: https://google.github.io/comprehensive-rust/idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/pitfalls.html

4.2.12 陷阱:过早伸手拿 dyn 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
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
use std::any::Any;

pub trait AddDyn: Any {
    fn add_dyn(&self, rhs: &dyn AddDyn) -> Box<dyn AddDyn>;
}

impl AddDyn for i32 {
    fn add_dyn(&self, rhs: &dyn AddDyn) -> Box<dyn AddDyn> {
        if let Some(downcast) = (rhs as &dyn Any).downcast_ref::<Self>() {
            Box::new(self + downcast)
        } else {
            Box::new(*self)
        }
    }
}

fn main() {
    let i: &dyn AddDyn = &42;
    let j: &dyn AddDyn = &64;
    let k: Box<dyn AddDyn> = i.add_dyn(j);
    dbg!((k.as_ref() as &dyn Any).is::<i32>());
    dbg!((k.as_ref() as &dyn Any).downcast_ref::<i32>());
}
  • 有 OOP 背景时,尽早伸手拿这个动态分发工具是可以理解的。

  • 但这不是首选做法:trait 对象会让我们用灵活性去换掉开发者与编译器原本对类型的了解。

  • 上例把事情推到荒谬的地步:若加法也绑在动态分发流程上,几乎什么都做不成。

    但动态分发在许多编程语言中往往是隐藏的;这里则更显式。

    在 i32 的 AddDyn 实现中,我们首先需要尝试把 rhs 向下转型为与 i32 相同的类型,若不是则静默失败。

    然后需要把新值分配到堆上,因为若要继续留在动态分发的世界里,就必须如此。

    两个值相加之后,若想查看结果,又必须再次向下转型成「真正」的类型才能打印——因为到目前为止操作所绑定的 trait 约束就是这样。

  • 向学员提问:为什么不能在 main 里直接加 Display 约束,好原样打印?

    答案:因为 add_dyn 只返回 dyn AddDyn,从参数类型到返回类型之间我们丢失了类型实现哪些 trait 的信息。即便输入实现了 Display,返回类型也并不实现。

  • 这会导致性能更差、更难理解的代码。

最后修改 August 11, 2026: 更新 (70a5af133)