bigywater.tech
Rust · 51

Display 与 Debug

格式化是每天都在用的功能,但很多细节容易写错:Display 和 Debug 到底有什么不同、说明符的完整写法、为什么自己实现 Display 之后 {:>8} 不起作用。这篇逐项跑出真实输出。

E0277
{} 用在只有 Debug 的类型上
f.pad
自定义 Display 要遵守宽度与对齐必须用它,write! 会忽略
{:#010x}
0x000000ff:带前缀、补零、宽度 10 的十六进制

两个 trait

实测 · 本机执行输出
  源码:
      1 | #[derive(Debug)] struct P { x: i32, y: i32 }
      2 | fn main() {
      3 |     let p = P { x: 1, y: 2 };
      4 |     println!("{:?}", p);
      5 |     println!("{}", p);
      6 | }
  编译:失败
    error[E0277]: `P` doesn't implement `std::fmt::Display`
     --> <实验目录>/src/u51a.rs:5:20
      |
    5 |     println!("{}", p);
      |               --   ^ `P` cannot be formatted with the default formatter
      |               |
      |               required by this formatting parameter
      |
    help: the trait `std::fmt::Display` is not implemented for `P`
     --> <实验目录>/src/u51a.rs:1:18
      |
    1 | #[derive(Debug)] struct P { x: i32, y: i32 }
      |                  ^^^^^^^^
      = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

derive(Debug) 的展开见derive 自动生成 Trait 实现。

同一个值的两种输出

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     println!("字符串   Display: {}   Debug: {:?}", "a\"b\n", "a\"b\n");
      3 |     println!("字符     Display: {}   Debug: {:?}", '\'', '\'');
      4 |     println!("浮点     Display: {}   Debug: {:?}", 1.0f64, 1.0f64);
      5 |     println!("浮点     Display: {}   Debug: {:?}", 1e21f64, 1e21f64);
      6 |     println!("Option   Debug: {:?}", Some("x"));
      7 |     println!("Vec      Debug: {:?}", vec![Some(1), None]);
      8 |     println!("元组     Debug: {:?}", (1, "a", 2.5));
      9 |     println!("{{:#?}} 分行缩进:\n{:#?}", (1, vec![Some("a")]));
     10 | }
  编译:成功
  运行(退出码 0):
    字符串   Display: a"b
       Debug: "a\"b\n"
    字符     Display: '   Debug: '\''
    浮点     Display: 1   Debug: 1.0
    浮点     Display: 1000000000000000000000   Debug: 1e21
    Option   Debug: Some("x")
    Vec      Debug: [Some(1), None]
    元组     Debug: (1, "a", 2.5)
    {:#?} 分行缩进:
    (
        1,
        [
            Some(
                "a",
            ),
        ],
    )

格式说明符

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     println!("[{:8}] [{:<8}] [{:>8}] [{:^8}] [{:*^8}] [{:->8}]", "ab", "ab", "ab", "ab", "ab", "ab");
      3 |     println!("[{:8}] [{:<8}] [{:08}] [{:+}] [{:+08.2}]", 42, 42, 42, 42, 3.14159);
      4 |     println!("[{:.2}] [{:8.3}] [{:<8.1}] [{:e}] [{:.1e}]", 3.14159, 3.14159, 3.14159, 1234.5, 1234.5);
      5 |     println!("[{:x}] [{:X}] [{:#x}] [{:#010x}] [{:b}] [{:#b}] [{:o}]", 255, 255, 255, 255, 5, 5, 8);
      6 |     let w = 6; let p = 2;
      7 |     println!("[{:w$.p$}] [{:>1$}] [{:.*}]", 2.71828, 7, 3, 1.23456);
      8 |     println!("位置参数 {0} {1} {0},命名参数 {name}", "a", "b", name = "n");
      9 | }
  编译:成功
  运行(退出码 0):
    [ab      ] [ab      ] [      ab] [   ab   ] [***ab***] [------ab]
    [      42] [42      ] [00000042] [+42] [+0003.14]
    [3.14] [   3.142] [3.1     ] [1.2345e3] [1.2e3]
    [ff] [FF] [0xff] [0x000000ff] [101] [0b101] [10]
    [  2.72] [      7] [1.235]
    位置参数 a b a,命名参数 n

自定义 Display 与 Debug

实测 · 本机执行输出
  源码:
      1 | use std::fmt;
      2 | struct A(&'static str); struct B(&'static str);
      3 | impl fmt::Display for A { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } }          // write!:宽度被忽略
      4 | impl fmt::Display for B { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.pad(self.0) } }                      // f.pad:遵守宽度与对齐
      5 | struct Secret { user: &'static str, pass: &'static str }
      6 | impl fmt::Debug for Secret { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Secret").field("user", &self.user).field("pass", &"***").finish() } }
      7 | fn main() {
      8 |     println!("write!  实现:[{:>6}] [{:<6}]", A("ab"), A("ab"));
      9 |     println!("f.pad   实现:[{:>6}] [{:<6}]", B("ab"), B("ab"));
     10 |     println!("{:?}", Secret { user: "ann", pass: "hunter2" });
     11 |     println!("{:#?}", Secret { user: "ann", pass: "hunter2" });
     12 | }
  编译:成功
  运行(退出码 0):
    write!  实现:[ab] [ab]
    f.pad   实现:[    ab] [ab    ]
    Secret { user: "ann", pass: "***" }
    Secret {
        user: "ann",
        pass: "***",
    }

分步演示

Display 与 Debug
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:{:p} 指针格式、{:?} 在 HashMap 上的顺序(不稳定)、i18n、fmt::Write 与 io::Write 的区别、format_args! 的零分配
    京ICP备2026067502号-1京公网安备11010502063487号