bigywater.tech
Rust · 48

Blanket Implementation

blanket impl 是 Rust 里「为一类类型一次性实现 trait」的办法,标准库大量使用它:ToString、Into 都是。这篇验证它的工作方式、两个最典型的标准库例子、扩展 trait 模式,以及一个常被忽略的细节 —— 引用类型要不要单独实现。

to_string()
只实现 Display 就免费得到
Into
只实现 From 就免费得到;反过来没有(E0308)
&T
引用要单独写转发 impl 才满足 trait 约束

blanket impl

实测 · 本机执行输出
  源码:
      1 | use std::fmt::Display;
      2 | trait Loud { fn loud(&self) -> String; }
      3 | impl<T: Display> Loud for T { fn loud(&self) -> String { format!("{}!!!", self).to_uppercase() } }
      4 | struct Name(&'static str);
      5 | impl Display for Name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.0) } }
      6 | fn main() {
      7 |     println!("{} {} {} {}", "hello".loud(), 42.loud(), 3.5.loud(), Name("rust").loud());     // 任何实现了 Display 的类型都有 loud()
      8 | }
  编译:成功
  运行(退出码 0):
    HELLO!!! 42!!! 3.5!!! RUST!!!
  编译:失败
    error[E0599]: `NoDisplay` doesn't implement `std::fmt::Display`
        --> <实验目录>/src/u48b.rs:5:38
         |
       4 | struct NoDisplay;
         | ---------------- method `loud` not found for this struct because it doesn't satisfy `NoDisplay: Loud` or `NoDisplay: std::fmt::Display`
       5 | fn main() { println!("{}", NoDisplay.loud()); }
         |                                      ^^^^ method cannot be called on `NoDisplay` due to unsatisfied trait bounds
         |
    note: trait bound `NoDisplay: std::fmt::Display` was not satisfied
        --> <实验目录>/src/u48b.rs:3:9
         |
       3 | impl<T: Display> Loud for T { fn loud(&self) -> String { format!("{}!!!", self) } }
         |         ^^^^^^^  ----     -
         |         |
         |         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: items from traits can only be used if the trait is implemented and in scope
    note: `Loud` defines an item `loud`, perhaps you need to implement it
        --> <实验目录>/src/u48b.rs:2:1
         |
       2 | trait Loud { fn loud(&self) -> String; }
         | ^^^^^^^^^^
    …(共 30 行)

与孤儿规则的冲突限制见Trait 实现冲突与孤儿规则。

标准库里的例子

实测 · 本机执行输出
  源码:
      1 | use std::fmt;
      2 | struct Celsius(f64);
      3 | impl fmt::Display for Celsius { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:.1}°C", self.0) } }
      4 | struct Meters(f64);
      5 | struct Km(f64);
      6 | impl From<Km> for Meters { fn from(k: Km) -> Meters { Meters(k.0 * 1000.0) } }
      7 | fn main() {
      8 |     println!("只实现了 Display,免费得到 to_string():{}", Celsius(36.6).to_string());
      9 |     let m: Meters = Km(2.5).into();               // 只实现了 From,免费得到 Into
     10 |     println!("只实现了 From<Km> for Meters,免费得到 Into:{} 米", m.0);
     11 | }
  编译:成功
  运行(退出码 0):
    只实现了 Display,免费得到 to_string():36.6°C
    只实现了 From<Km> for Meters,免费得到 Into:2500 米
  源码:
      1 | struct Meters(f64); struct Km(f64);
      2 | impl Into<Meters> for Km { fn into(self) -> Meters { Meters(self.0 * 1000.0) } }       // 只实现 Into
      3 | fn main() { let _m: Meters = Meters::from(Km(1.0)); }
  编译:失败
    error[E0308]: mismatched types
       --> <实验目录>/src/u48d.rs:3:43
        |
      3 | fn main() { let _m: Meters = Meters::from(Km(1.0)); }
        |                              ------------ ^^^^^^^ expected `Meters`, found `Km`
        |                              |
        |                              arguments to this function are incorrect
        |
    note: associated function defined here
       --> <rust 标准库源码>/library/core/src/convert/mod.rs:594:8
        |
    594 |     fn from(value: T) -> Self;
        |        ^^^^
    help: call `Into::into` on this expression to convert `Km` into `Meters`
        |
      3 | fn main() { let _m: Meters = Meters::from(Km(1.0).into()); }
        |                                                  +++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0308`.

From / Into 的详细用法见From、Into 与类型转换。

扩展 trait

实测 · 本机执行输出
  源码:
      1 | trait StrExt { fn shout(&self) -> String; fn word_count(&self) -> usize; }
      2 | impl<T: AsRef<str> + ?Sized> StrExt for T {
      3 |     fn shout(&self) -> String { self.as_ref().to_uppercase() + "!" }
      4 |     fn word_count(&self) -> usize { self.as_ref().split_whitespace().count() }
      5 | }
      6 | fn main() {
      7 |     let s = String::from("hello rust world");
      8 |     let c: std::borrow::Cow<str> = std::borrow::Cow::Borrowed("cow str");
      9 |     println!("&str:{} {}", "a b".shout(), "a b".word_count());
     10 |     println!("String:{} {}", s.shout(), s.word_count());
     11 |     println!("Cow:{} {}", c.shout(), c.word_count());
     12 | }
  编译:成功
  运行(退出码 0):
    &str:A B! 2
    String:HELLO RUST WORLD! 3
    Cow:COW STR! 2

引用的转发实现

实测 · 本机执行输出
  编译:失败
    error[E0277]: the trait bound `&Dog: Speak` is not satisfied
     --> <实验目录>/src/u48f.rs:4:60
      |
    4 | fn main() { println!("{}", talk(Dog)); println!("{}", talk(&Dog)); }
      |                                                       ---- ^^^^ the trait `Speak` is not implemented for `&Dog`
      |                                                       |
      |                                                       required by a bound introduced by this call
      |
    note: required by a bound in `talk`
     --> <实验目录>/src/u48f.rs:3:12
      |
    3 | fn talk<S: Speak>(s: S) -> String { s.speak() }
      |            ^^^^^ required by this bound in `talk`
    help: consider removing the leading `&`-reference
      |
    4 - fn main() { println!("{}", talk(Dog)); println!("{}", talk(&Dog)); }
    4 + fn main() { println!("{}", talk(Dog)); println!("{}", talk(Dog)); }
      |
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.
  源码:
      1 | trait Speak { fn speak(&self) -> String; }
      2 | struct Dog; impl Speak for Dog { fn speak(&self) -> String { "汪".into() } }
      3 | impl<T: Speak + ?Sized> Speak for &T { fn speak(&self) -> String { (**self).speak() } }
      4 | impl<T: Speak + ?Sized> Speak for Box<T> { fn speak(&self) -> String { (**self).speak() } }
      5 | fn talk<S: Speak>(s: S) -> String { s.speak() }
      6 | fn main() { println!("{} {} {}", talk(Dog), talk(&Dog), talk(Box::new(Dog))); }
  编译:成功
  运行(退出码 0):
    汪 汪 汪

分步演示

blanket impl
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。错误信息为原样的 rustc 1.98.1 输出
    • 没有验证:blanket impl 对编译时间与一致性检查的影响、给已有 trait 新增 blanket impl 是否构成破坏性变更、具体 std 里哪些 trait 对 &T 做了转发(只验证了自定义 trait 的转发写法)
    京ICP备2026067502号-1京公网安备11010502063487号