bigywater.tech
Rust · 42

dyn Trait 的对象安全

为什么有的 trait 能写成 dyn Trait,有的不行?这篇把「对象安全」(现在官方叫 dyn compatible)的几条规则逐条编译:每一条都有真实的 E0038 报错,再看 where Self: Sized 如何把不兼容的方法单独排除,以及克隆 trait 对象的惯用做法。

E0038
the trait is not dyn compatible
4 种
本篇验证的不兼容情形:泛型方法、返回 Self、无 self 的关联函数、Clone 之类的 Sized supertrait
where Self: Sized
把方法排除出 trait 对象的接口

泛型方法

实测 · 本机执行输出
  源码:
      1 | trait Visitor { fn visit<T: std::fmt::Debug>(&self, t: T); }
      2 | struct P; impl Visitor for P { fn visit<T: std::fmt::Debug>(&self, t: T) { println!("{:?}", t); } }
      3 | fn main() { let v: Box<dyn Visitor> = Box::new(P); }
  编译:失败
    error[E0038]: the trait `Visitor` is not dyn compatible
     --> <实验目录>/src/u42a.rs:3:28
      |
    3 | fn main() { let v: Box<dyn Visitor> = Box::new(P); }
      |                            ^^^^^^^ `Visitor` is not dyn compatible
      |
    note: for a trait to be dyn compatible it needs to allow building a vtable
          for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
     --> <实验目录>/src/u42a.rs:1:20
      |
    1 | trait Visitor { fn visit<T: std::fmt::Debug>(&self, t: T); }
      |       -------      ^^^^^ ...because method `visit` has generic type parameters
      |       |
      |       this trait is not dyn compatible...
      = help: consider moving `visit` to another trait
      = help: only type `P` implements `Visitor`; consider using it directly instead.
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0038`.

返回 Self 与无 self 的函数

实测 · 本机执行输出
  源码:
      1 | trait Dup { fn dup(&self) -> Self; }
      2 | struct P; impl Dup for P { fn dup(&self) -> P { P } }
      3 | fn main() { let v: Box<dyn Dup> = Box::new(P); }
  编译:失败
    error[E0038]: the trait `Dup` is not dyn compatible
     --> <实验目录>/src/u42b.rs:3:28
      |
    3 | fn main() { let v: Box<dyn Dup> = Box::new(P); }
      |                            ^^^ `Dup` is not dyn compatible
      |
    note: for a trait to be dyn compatible it needs to allow building a vtable
          for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
     --> <实验目录>/src/u42b.rs:1:30
      |
    1 | trait Dup { fn dup(&self) -> Self; }
      |       ---                    ^^^^ ...because method `dup` references the `Self` type in its return type
      |       |
      |       this trait is not dyn compatible...
      = help: consider moving `dup` to another trait
      = help: only type `P` implements `Dup`; consider using it directly instead.
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0038`.
  编译:失败
    error[E0038]: the trait `Make` is not dyn compatible
     --> <实验目录>/src/u42c.rs:3:28
      |
    3 | fn main() { let v: Box<dyn Make> = Box::new(P); }
      |                            ^^^^ `Make` is not dyn compatible
      |
    note: for a trait to be dyn compatible it needs to allow building a vtable
          for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
     --> <实验目录>/src/u42c.rs:1:17
      |
    1 | trait Make { fn make() -> Self; fn name(&self) -> String; }
      |       ----      ^^^^ ...because associated function `make` has no `self` parameter
      |       |
      |       this trait is not dyn compatible...
      = help: only type `P` implements `Make`; consider using it directly instead.
    help: consider turning `make` into a method by giving it a `&self` argument
      |
    1 | trait Make { fn make(&self) -> Self; fn name(&self) -> String; }
      |                      +++++
    help: alternatively, consider constraining `make` so it does not apply to trait objects
      |
    1 | trait Make { fn make() -> Self where Self: Sized; fn name(&self) -> String; }
      |                                +++++++++++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0038`.

supertrait 的 Sized

实测 · 本机执行输出
  源码:
      1 | trait Shape: Clone { fn area(&self) -> f64; }
      2 | #[derive(Clone)] struct Sq(f64); impl Shape for Sq { fn area(&self) -> f64 { self.0 } }
      3 | fn main() { let v: Box<dyn Shape> = Box::new(Sq(1.0)); }
  编译:失败
    error[E0038]: the trait `Shape` is not dyn compatible
     --> <实验目录>/src/u42d.rs:3:28
      |
    3 | fn main() { let v: Box<dyn Shape> = Box::new(Sq(1.0)); }
      |                            ^^^^^ `Shape` is not dyn compatible
      |
    note: for a trait to be dyn compatible it needs to allow building a vtable
          for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
     --> <实验目录>/src/u42d.rs:1:14
      |
    1 | trait Shape: Clone { fn area(&self) -> f64; }
      |       -----  ^^^^^ ...because it requires `Self: Sized`
      |       |
      |       this trait is not dyn compatible...
      = help: only type `Sq` implements `Shape`; consider using it directly instead.
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0038`.

where Self: Sized

实测 · 本机执行输出
  源码:
      1 | trait Shape {
      2 |     fn area(&self) -> f64;
      3 |     fn scaled(&self, k: f64) -> Self where Self: Sized;               // 只有具体类型才有,dyn 上没有
      4 |     fn describe<T: std::fmt::Display>(&self, tag: T) -> String where Self: Sized { format!("{tag}: {}", self.area()) }
      5 | }
      6 | struct Sq(f64);
      7 | impl Shape for Sq { fn area(&self) -> f64 { self.0 * self.0 } fn scaled(&self, k: f64) -> Sq { Sq(self.0 * k) } }
      8 | fn main() {
      9 |     let s = Sq(2.0);
     10 |     println!("具体类型可以用全部方法:{} {}", s.scaled(2.0).area(), s.describe("sq"));
     11 |     let d: Box<dyn Shape> = Box::new(Sq(3.0));
     12 |     println!("dyn 上只有对象安全的方法:{}", d.area());
     13 | }
  编译:成功
  运行(退出码 0):
    具体类型可以用全部方法:16 sq: 4
    dyn 上只有对象安全的方法:9
  编译:失败
    error: the `scaled` method cannot be invoked on a trait object
     --> <实验目录>/src/u42f.rs:4:58
      |
    1 | trait Shape { fn area(&self) -> f64; fn scaled(&self, k: f64) -> Self where Self: Sized; }
      |                                                                                   ----- this has a `Sized` requirement
    ...
    4 | fn main() { let d: Box<dyn Shape> = Box::new(Sq(1.0)); d.scaled(2.0); }
      |                                                          ^^^^^^
    
    error: aborting due to 1 previous error

vtable 的结构见Trait Object 与动态分发。

克隆 trait 对象

实测 · 本机执行输出
  源码:
      1 | trait Shape { fn area(&self) -> f64; fn box_clone(&self) -> Box<dyn Shape>; }
      2 | #[derive(Clone)] struct Sq(f64);
      3 | impl Shape for Sq { fn area(&self) -> f64 { self.0 * self.0 } fn box_clone(&self) -> Box<dyn Shape> { Box::new(self.clone()) } }
      4 | impl Clone for Box<dyn Shape> { fn clone(&self) -> Self { self.box_clone() } }
      5 | fn main() {
      6 |     let a: Box<dyn Shape> = Box::new(Sq(3.0));
      7 |     let b = a.clone();
      8 |     println!("克隆出的 trait 对象:{} {}", a.area(), b.area());
      9 | }
  编译:成功
  运行(退出码 0):
    克隆出的 trait 对象:9 9

分步演示

dyn 兼容
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。错误信息为原样的 rustc 1.98.1 输出(措辞「dyn compatible」是该版本的新叫法)
    • 没有验证:带关联类型/关联常量的 trait 的 dyn 兼容性(含 Iterator 的 dyn Iterator)、dyn Trait 的 upcasting、async fn in trait 是否 dyn 兼容
    京ICP备2026067502号-1京公网安备11010502063487号