bigywater.tech
Rust · 37

Trait Bound

trait bound 是泛型的「入场券」:告诉编译器 T 至少会什么。这篇用真实报错验证它的几个关键行为:什么时候必须写、不满足时报什么、多个约束与 where 的写法、按约束有条件地提供方法,以及它在机器码层面意味着什么。

E0369
泛型里用了没声明的操作
E0599
按约束提供的方法在不满足约束的类型上不存在
2 份
add 为 i32 与 f64 各生成一份机器码

为什么需要约束

实测 · 本机执行输出
  源码:
      1 | fn largest<T>(list: &[T]) -> &T {
      2 |     let mut m = &list[0];
      3 |     for x in list { if x > m { m = x; } }
      4 |     m
      5 | }
      6 | fn main() { println!("{}", largest(&[1, 5, 2])); }
  编译:失败
    error[E0369]: binary operation `>` cannot be applied to type `&T`
     --> <实验目录>/src/u37a.rs:3:26
      |
    3 |     for x in list { if x > m { m = x; } }
      |                        - ^ - &T
      |                        |
      |                        &T
      |
    help: consider restricting type parameter `T` with trait `PartialOrd`
      |
    1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
      |             ++++++++++++++++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0369`.

满足与不满足

实测 · 本机执行输出
  源码:
      1 | fn largest<T: PartialOrd>(list: &[T]) -> &T {
      2 |     let mut m = &list[0];
      3 |     for x in list { if x > m { m = x; } }
      4 |     m
      5 | }
      6 | fn main() {
      7 |     println!("i32:{}  f64:{}  char:{}  &str:{}", largest(&[1, 5, 2]), largest(&[1.5, 0.2]), largest(&['a', 'z', 'm']), largest(&["pear", "apple"]));
      8 |     println!("String:{}", largest(&[String::from("b"), String::from("a")]));
      9 | }
  编译:成功
  运行(退出码 0):
    i32:5  f64:1.5  char:z  &str:pear
    String:b
  源码:
      1 | fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut m = &list[0]; for x in list { if x > m { m = x; } } m }
      2 | struct P { x: i32 }
      3 | fn main() { let v = [P { x: 1 }, P { x: 2 }]; largest(&v); }
  编译:失败
    error[E0277]: can't compare `P` with `P`
     --> <实验目录>/src/u37c.rs:3:55
      |
    3 | fn main() { let v = [P { x: 1 }, P { x: 2 }]; largest(&v); }
      |                                               ------- ^^ no implementation for `P < P` and `P > P`
      |                                               |
      |                                               required by a bound introduced by this call
      |
      = help: the trait `PartialOrd` is not implemented for `P`
    note: required by a bound in `largest`
     --> <实验目录>/src/u37c.rs:1:15
      |
    1 | fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut m = &list[0]; for x in list { if x > m { m = x; } } m }
      |               ^^^^^^^^^^ required by this bound in `largest`
    help: consider annotating `P` with `#[derive(PartialOrd)]`
      |
    2 + #[derive(PartialOrd)]
    3 | struct P { x: i32 }
      |
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

多个约束与 where

实测 · 本机执行输出
  源码:
      1 | use std::fmt::{Debug, Display};
      2 | fn show_both<T: Display + Clone, U: Debug>(t: &T, u: &U) -> String { format!("{} / {:?} / 克隆的 {}", t, u, t.clone()) }
      3 | fn show_where<T, U>(t: &T, u: &U) -> String where T: Display + Clone, U: Debug { show_both(t, u) }
      4 | fn main() {
      5 |     println!("{}", show_both(&"hi", &vec![1, 2]));
      6 |     println!("{}", show_where(&3.5, &Some('x')));
      7 | }
  编译:成功
  运行(退出码 0):
    hi / [1, 2] / 克隆的 hi
    3.5 / Some('x') / 克隆的 3.5

有条件的方法

实测 · 本机执行输出
  源码:
      1 | use std::fmt::Display;
      2 | struct Pair<T> { a: T, b: T }
      3 | impl<T> Pair<T> { fn new(a: T, b: T) -> Self { Pair { a, b } } }                      // 对所有 T 都有
      4 | impl<T: Display + PartialOrd> Pair<T> {                                              // 只有 T 满足约束才有
      5 |     fn show_largest(&self) { if self.a >= self.b { println!("最大的是 a = {}", self.a) } else { println!("最大的是 b = {}", self.b) } }
      6 | }
      7 | struct NoDisplay;
      8 | fn main() {
      9 |     Pair::new(3, 9).show_largest();
     10 |     let p = Pair::new(NoDisplay, NoDisplay);
     11 |     p.show_largest();
     12 | }
  编译:失败
    error[E0599]: the method `show_largest` exists for struct `Pair<NoDisplay>`, but its trait bounds were not satisfied
        --> <实验目录>/src/u37e.rs:11:7
         |
       2 | struct Pair<T> { a: T, b: T }
         | -------------- method `show_largest` not found for this struct
    ...
       7 | struct NoDisplay;
         | ---------------- doesn't satisfy `NoDisplay: PartialOrd` or `NoDisplay: std::fmt::Display`
    ...
      11 |     p.show_largest();
         |       ^^^^^^^^^^^^ method cannot be called on `Pair<NoDisplay>` due to unsatisfied trait bounds
         |
    note: the following trait bounds were not satisfied:
          `NoDisplay: PartialOrd`
          `NoDisplay: std::fmt::Display`
        --> <实验目录>/src/u37e.rs:4:9
         |
       4 | impl<T: Display + PartialOrd> Pair<T> {                                              // 只有 T 满足约束才有
         |         ^^^^^^^   ^^^^^^^^^^  -------
         |         |         |
         |         |         unsatisfied trait bound introduced here
         |         unsatisfied trait bound introduced here
    note: the trait `std::fmt::Display` must be implemented
        --> <rust 标准库源码>/library/core/src/fmt/mod.rs:1187:1
         |
    1187 | pub trait Display: PointeeSized {
         | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    help: consider annotating `NoDisplay` with `#[derive(PartialEq, PartialOrd)]`
         |
       7 + #[derive(PartialEq, PartialOrd)]
    …(共 36 行)

写法 impl Trait 参数与约束是等价的,见impl Trait 参数与返回值。

单态化

实测 · 本机执行输出
  生成的 add 函数符号(每个具体类型一份): 2 个
  add::<f64>: fadd	d0, d0, d1 ; ret
  add::<i32>: add	w0, w1, w0 ; ret

单态化的详细代价见泛型单态化与静态分发与动态分发性能对比。

分步演示

trait bound
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。汇编为 --emit=asm -O 的 aarch64 输出
    • 没有验证:关联类型约束(Iterator)、高阶 trait bound(for<'a>)、约束的 ?Sized、泛型的代码体积膨胀的量化
    京ICP备2026067502号-1京公网安备11010502063487号