bigywater.tech
Rust · 12

Newtype 模式

Newtype 是 Rust 里最轻的抽象:一个只有一个字段的元组结构体。它能让「米」和「秒」不再互相混淆,能让你为别人的类型实现别人的 trait。这篇用真实的编译结果逐条验证它的好处、零成本,以及它带来的小麻烦(失去内部类型的方法)和解法(Deref)。

E0308
newtype 把 speed(秒, 米) 的错误调用拦在编译期(type 别名拦不住)
8 = 8
f64 与 struct Meters(f64) 的字节数,机器码相同
E0117
直接为 Vec 实现 Display 违反孤儿规则

别名与 newtype

实测 · 本机执行输出
  源码:
      1 | type Meters = f64;
      2 | type Seconds = f64;
      3 | fn speed(d: Meters, t: Seconds) -> f64 { d / t }
      4 | fn main() {
      5 |     let m: Meters = 100.0; let s: Seconds = 9.58;
      6 |     println!("正确的顺序  speed(m, s) = {:.2}", speed(m, s));
      7 |     println!("参数写反了 speed(s, m) = {:.4}(编译器没有报任何错误)", speed(s, m));
      8 | }
  编译:成功
  运行(退出码 0):
    正确的顺序  speed(m, s) = 10.44
    参数写反了 speed(s, m) = 0.0958(编译器没有报任何错误)
  源码:
      1 | struct Meters(f64);
      2 | struct Seconds(f64);
      3 | fn speed(d: Meters, t: Seconds) -> f64 { d.0 / t.0 }
      4 | fn main() {
      5 |     let m = Meters(100.0); let s = Seconds(9.58);
      6 |     println!("{:.2}", speed(s, m));
      7 | }
  编译:失败
    error[E0308]: arguments to this function are incorrect
     --> <实验目录>/src/u12b.rs:6:23
      |
    6 |     println!("{:.2}", speed(s, m));
      |                       ^^^^^ -  - expected `Seconds`, found `Meters`
      |                             |
      |                             expected `Meters`, found `Seconds`
      |
    note: function defined here
     --> <实验目录>/src/u12b.rs:3:4
      |
    3 | fn speed(d: Meters, t: Seconds) -> f64 { d.0 / t.0 }
      |    ^^^^^
    help: swap these arguments
      |
    6 -     println!("{:.2}", speed(s, m));
    6 +     println!("{:.2}", speed(m, s));
      |
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0308`.

零成本

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    size_of::<f64>() = 8,size_of::<Meters>() = 8
  两个函数共同的机器码: fadd	d0, d0, d1 ; ret
  汇编里的别名行:`_add_raw = _add_new`(LLVM 发现两个函数完全相同,把其中一个合并成另一个的别名)

「零成本抽象」的更多例子见Zero-Cost Abstraction。

绕过孤儿规则

实测 · 本机执行输出
  源码:
      1 | use std::fmt;
      2 | impl fmt::Display for Vec<String> {
      3 |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.join(", ")) }
      4 | }
      5 | fn main() {}
  编译:失败
    error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
     --> <实验目录>/src/u12e.rs:2:1
      |
    2 | impl fmt::Display for Vec<String> {
      | ^^^^^^^^^^^^^^^^^^^^^^-----------
      |                       |
      |                       `Vec` is not defined in the current crate
      |
      = note: impl doesn't have any local type before any uncovered type parameters
      = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
      = note: define and implement a trait or new type instead
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0117`.
  源码:
      1 | use std::fmt;
      2 | struct Wrapper(Vec<String>);
      3 | impl fmt::Display for Wrapper {
      4 |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) }
      5 | }
      6 | fn main() {
      7 |     let w = Wrapper(vec!["hello".to_string(), "world".to_string()]);
      8 |     println!("w = {w}");
      9 | }
  编译:成功
  运行(退出码 0):
    w = [hello, world]

孤儿规则本身见Trait 实现冲突与孤儿规则。

方法转发与 Deref

实测 · 本机执行输出
  源码:
      1 | struct Names(Vec<String>);
      2 | fn main() {
      3 |     let n = Names(vec!["a".into()]);
      4 |     println!("{}", n.len());
      5 | }
  编译:失败
    error[E0599]: no method named `len` found for struct `Names` in the current scope
     --> <实验目录>/src/u12g.rs:4:22
      |
    1 | struct Names(Vec<String>);
      | ------------ method `len` not found for this struct
    ...
    4 |     println!("{}", n.len());
      |                      ^^^ method not found in `Names`
      |
      = help: items from traits can only be used if the trait is implemented and in scope
      = note: the following trait defines an item `len`, perhaps you need to implement it:
              candidate #1: `ExactSizeIterator`
    help: one of the expressions' fields has a method of the same name
      |
    4 |     println!("{}", n.0.len());
      |                      ++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0599`.
  源码:
      1 | use std::ops::Deref;
      2 | struct Names(Vec<String>);
      3 | impl Deref for Names { type Target = Vec<String>; fn deref(&self) -> &Vec<String> { &self.0 } }
      4 | fn main() {
      5 |     let n = Names(vec!["a".into(), "b".into()]);
      6 |     println!("len = {},first = {:?},iter 拼接 = {}", n.len(), n.first(), n.iter().cloned().collect::<Vec<_>>().join("+"));
      7 | }
  编译:成功
  运行(退出码 0):
    len = 2,first = Some("a"),iter 拼接 = a+b

为所有 newtype 都实现 Deref 并不总是好主意(它让内部类型的方法全部暴露出来,削弱了封装);这里只验证了它能转发,没有讨论取舍。

分步演示

newtype
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。汇编用 --emit=asm -O 生成,目标 aarch64-apple-darwin
    • 没有验证:derive_more 等 crate 简化 newtype 的样板、Deref 对自动解引用与方法解析顺序的影响、#[repr(transparent)] 与 FFI
    京ICP备2026067502号-1京公网安备11010502063487号