bigywater.tech
Rust · 45

Supertrait

supertrait 是「trait 的继承」:一个 trait 要求实现者先实现另一个。这篇用真实编译逐条验证它的规则:要求怎么体现、泛型约束里的传递、trait 对象能不能向上转型(这个能力在近几个版本才稳定),以及同名方法的歧义。

E0277
没有实现 supertrait 就实现子 trait 的错误
upcasting
&dyn Person → &dyn Named 在 rustc 1.98 可用
E0034
两个 supertrait 同名方法时的歧义

supertrait 的要求

实测 · 本机执行输出
  源码:
      1 | use std::fmt::Display;
      2 | trait Pretty: Display {
      3 |     fn pretty(&self) -> String { format!("★ {} ★", self) }          // 能用 Display,因为它是 supertrait
      4 | }
      5 | struct Name(&'static str);
      6 | impl Display for Name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.0) } }
      7 | impl Pretty for Name {}
      8 | fn main() { println!("{}", Name("rust").pretty()); }
  编译:成功
  运行(退出码 0):
    ★ rust ★
  源码:
      1 | use std::fmt::Display;
      2 | trait Pretty: Display { fn pretty(&self) -> String { format!("★ {} ★", self) } }
      3 | struct Plain;
      4 | impl Pretty for Plain {}
      5 | fn main() {}
  编译:失败
    error[E0277]: `Plain` doesn't implement `std::fmt::Display`
     --> <实验目录>/src/u45b.rs:4:17
      |
    4 | impl Pretty for Plain {}
      |                 ^^^^^ unsatisfied trait bound
      |
    help: the trait `std::fmt::Display` is not implemented for `Plain`
     --> <实验目录>/src/u45b.rs:3:1
      |
    3 | struct Plain;
      | ^^^^^^^^^^^^
    note: required by a bound in `Pretty`
     --> <实验目录>/src/u45b.rs:2:15
      |
    2 | trait Pretty: Display { fn pretty(&self) -> String { format!("★ {} ★", self) } }
      |               ^^^^^^^ required by this bound in `Pretty`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

约束里的传递

实测 · 本机执行输出
  源码:
      1 | trait Named { fn name(&self) -> String; }
      2 | trait Aged { fn age(&self) -> u32; }
      3 | trait Person: Named + Aged { fn intro(&self) -> String { format!("{},{} 岁", self.name(), self.age()) } }
      4 | struct Ann; impl Named for Ann { fn name(&self) -> String { "Ann".into() } } impl Aged for Ann { fn age(&self) -> u32 { 30 } } impl Person for Ann {}
      5 | fn greet<T: Person>(p: &T) -> String { format!("你好 {}(也能直接用 name():{})", p.intro(), p.name()) }   // T: Person 就够了,不需要再写 Named + Aged
      6 | fn main() { println!("{}", greet(&Ann)); }
  编译:成功
  运行(退出码 0):
    你好 Ann,30 岁(也能直接用 name():Ann)

trait 对象向上转型

实测 · 本机执行输出
  源码:
      1 | trait Named { fn name(&self) -> String; }
      2 | trait Person: Named { fn id(&self) -> u32; }
      3 | struct Ann; impl Named for Ann { fn name(&self) -> String { "Ann".into() } } impl Person for Ann { fn id(&self) -> u32 { 7 } }
      4 | fn only_named(n: &dyn Named) -> String { n.name() }
      5 | fn main() {
      6 |     let p: &dyn Person = &Ann;
      7 |     println!("通过 &dyn Person 调用 id() = {},name() = {}", p.id(), p.name());
      8 |     println!("向上转型成 &dyn Named:{}", only_named(p));
      9 |     let b: Box<dyn Person> = Box::new(Ann); let n: Box<dyn Named> = b;
     10 |     println!("Box<dyn Person> 转成 Box<dyn Named>:{}", n.name());
     11 | }
  编译:成功
  运行(退出码 0):
    通过 &dyn Person 调用 id() = 7,name() = Ann
    向上转型成 &dyn Named:Ann
    Box<dyn Person> 转成 Box<dyn Named>:Ann

trait 对象的基础见Trait Object 与动态分发。

同名方法

实测 · 本机执行输出
  源码:
      1 | trait A { fn hello(&self) -> &'static str { "A" } }
      2 | trait B { fn hello(&self) -> &'static str { "B" } }
      3 | trait C: A + B { fn call(&self) -> &'static str { self.hello() } }
      4 | fn main() {}
  编译:失败
    error[E0034]: multiple applicable items in scope
     --> <实验目录>/src/u45e.rs:3:56
      |
    3 | trait C: A + B { fn call(&self) -> &'static str { self.hello() } }
      |                                                        ^^^^^ multiple `hello` found
      |
    note: candidate #1 is defined in the trait `A`
     --> <实验目录>/src/u45e.rs:1:11
      |
    1 | trait A { fn hello(&self) -> &'static str { "A" } }
      |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    note: candidate #2 is defined in the trait `B`
     --> <实验目录>/src/u45e.rs:2:11
      |
    2 | trait B { fn hello(&self) -> &'static str { "B" } }
      |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    help: disambiguate the method for candidate #1
      |
    3 - trait C: A + B { fn call(&self) -> &'static str { self.hello() } }
    3 + trait C: A + B { fn call(&self) -> &'static str { A::hello(&self) } }
      |
    help: disambiguate the method for candidate #2
      |
    3 - trait C: A + B { fn call(&self) -> &'static str { self.hello() } }
    3 + trait C: A + B { fn call(&self) -> &'static str { B::hello(&self) } }
      |
    …(共 30 行)
  源码:
      1 | trait A { fn hello(&self) -> &'static str { "A" } }
      2 | trait B { fn hello(&self) -> &'static str { "B" } }
      3 | trait C: A + B { fn call(&self) -> String { format!("{} {}", A::hello(self), B::hello(self)) } }
      4 | struct S; impl A for S {} impl B for S {} impl C for S {}
      5 | fn main() { println!("{}", S.call()); }
  编译:成功
  运行(退出码 0):
    A B

类似的歧义在Trait 定义与实现里也出现过。

分步演示

supertrait
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。upcasting 的可用性只验证了本机的 rustc 1.98.1
    • 没有验证:supertrait 带泛型参数、supertrait 与关联类型的交互、dyn A + B 的多 trait 对象(只允许一个非 auto trait)、upcasting 在 Rc/Arc 上的行为
    京ICP备2026067502号-1京公网安备11010502063487号