bigywater.tech
Rust · 46

默认 Trait 方法

默认方法让 trait 的「必须实现」降到最小,把其余功能免费送给实现者。这篇验证它的几个行为:最小实现换来多少免费能力、覆盖默认实现能省多少工作、一个编译器查不出来的陷阱(互相依赖的默认方法),以及它在 trait 对象里怎么分发。

1001 → 0
覆盖 count_all 后 next_value 的调用次数
-6
互相依赖的默认方法都没覆盖时,程序因栈溢出终止的退出码
1 个
Iterator 的必须实现方法(next),其余都是默认方法

最小实现

实测 · 本机执行输出
  源码:
      1 | struct Countdown(u32);
      2 | impl Iterator for Countdown {
      3 |     type Item = u32;
      4 |     fn next(&mut self) -> Option<u32> { if self.0 == 0 { None } else { self.0 -= 1; Some(self.0 + 1) } }
      5 | }
      6 | fn main() {
      7 |     println!("collect:{:?}", Countdown(5).collect::<Vec<_>>());
      8 |     println!("map + filter + sum:{}", Countdown(10).filter(|n| n % 2 == 0).map(|n| n * n).sum::<u32>());
      9 |     println!("zip:{:?}", Countdown(3).zip(Countdown(3).map(|n| n * 10)).collect::<Vec<_>>());
     10 |     println!("max:{:?},min:{:?},count:{},last:{:?}", Countdown(7).max(), Countdown(7).min(), Countdown(7).count(), Countdown(7).last());
     11 |     println!("fold:{},any:{},all:{},position:{:?}", Countdown(4).fold(0, |a, b| a + b), Countdown(4).any(|x| x == 3), Countdown(4).all(|x| x > 0), Countdown(9).position(|x| x == 5));
     12 |     println!("take_while + skip + step_by:{:?}", Countdown(20).skip(2).step_by(4).take_while(|&x| x > 3).collect::<Vec<_>>());
     13 | }
  编译:成功
  运行(退出码 0):
    collect:[5, 4, 3, 2, 1]
    map + filter + sum:220
    zip:[(3, 30), (2, 20), (1, 10)]
    max:Some(7),min:Some(1),count:7,last:Some(1)
    fold:10,any:true,all:true,position:Some(4)
    take_while + skip + step_by:[18, 14, 10, 6]

Iterator 的更多内容见Iterator Trait与自定义 Iterator。

覆盖默认实现

实测 · 本机执行输出
  源码:
      1 | struct Slow { left: u32, calls: u32 }
      2 | struct Fast { left: u32, calls: u32 }
      3 | trait Count { fn next_value(&mut self) -> Option<u32>; fn count_all(&mut self) -> u32 { let mut n = 0; while self.next_value().is_some() { n += 1; } n } }
      4 | impl Count for Slow { fn next_value(&mut self) -> Option<u32> { self.calls += 1; if self.left == 0 { None } else { self.left -= 1; Some(self.left) } } }     // 用默认的 count_all:逐个数
      5 | impl Count for Fast { fn next_value(&mut self) -> Option<u32> { self.calls += 1; if self.left == 0 { None } else { self.left -= 1; Some(self.left) } } fn count_all(&mut self) -> u32 { let n = self.left; self.left = 0; n } }   // 覆盖:直接返回
      6 | struct Q(i32);
      7 | impl PartialEq for Q { fn eq(&self, o: &Q) -> bool { self.0 == o.0 } }          // 只实现 eq
      8 | fn main() {
      9 |     let mut s = Slow { left: 1000, calls: 0 }; let mut f = Fast { left: 1000, calls: 0 };
     10 |     let (a, b) = (s.count_all(), f.count_all());
     11 |     println!("默认实现:count_all = {},调用了 next_value {} 次", a, s.calls);
     12 |     println!("覆盖实现:count_all = {},调用了 next_value {} 次", b, f.calls);
     13 |     println!("Q(1) != Q(2) 来自 PartialEq 的默认方法 ne:{}", Q(1) != Q(2));
     14 | }
  编译:成功
  运行(退出码 0):
    默认实现:count_all = 1000,调用了 next_value 1001 次
    覆盖实现:count_all = 1000,调用了 next_value 0 次
    Q(1) != Q(2) 来自 PartialEq 的默认方法 ne:true

互相依赖的默认方法

实测 · 本机执行输出
  编译: 成功
  警告/错误: (无)
  运行:退出码 -6(负数表示被信号终止:6);stderr 第一行:thread 'main' (7034937) has overflowed its stack

trait 对象里的默认方法

实测 · 本机执行输出
  源码:
      1 | trait Greeter {
      2 |     fn name(&self) -> String;
      3 |     fn greet(&self) -> String { format!("你好,{}!", self.name()) }
      4 | }
      5 | struct Plain; struct Formal;
      6 | impl Greeter for Plain { fn name(&self) -> String { "小明".into() } }
      7 | impl Greeter for Formal { fn name(&self) -> String { "王先生".into() } fn greet(&self) -> String { format!("{} 您好,欢迎光临。", self.name()) } }
      8 | fn main() {
      9 |     let list: Vec<Box<dyn Greeter>> = vec![Box::new(Plain), Box::new(Formal)];
     10 |     for g in &list { println!("{}", g.greet()); }
     11 | }
  编译:成功
  运行(退出码 0):
    你好,小明!
    王先生 您好,欢迎光临。

trait 定义的基础见Trait 定义与实现。

分步演示

默认方法
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。曾尝试用耗时比较默认与覆盖实现,结果被优化器折叠成常数、不可信,所以改用调用次数
    • 没有验证:默认方法的单态化与内联、在覆盖实现里调用「被覆盖的默认实现」的写法(Rust 没有 super 调用)、默认方法与 specialization
    京ICP备2026067502号-1京公网安备11010502063487号