一、什么是格式化输出?

Rust 的格式化输出功能强大且灵活,通过println!print!format! 等宏实现。

支持多种占位符(如 {} 、 {:?} 、 {:#?} )和格式化选项(如宽度、精度、对齐方式),能够满足从简单文本输出到复杂数据结构调试的需求。

Rust 还允许通过实现 DisplayDebug 特征来自定义类型格式化行为,同时支持动态精度、宽度控制和元组结构体等高级用法。

二、基础格式化

1. println! 宏基础

src/main.rs 中添加:

rust 复制代码
// 基础字符串输出
println!("Hello, World!");

// 变量插入
let name = "Alice";
println!("Name: {}", name);

// 多变量插入
let age = 30;
println!("{} is {} years old", name, age);

关键特性

  • {} 是占位符,按顺序匹配参数
  • 自动类型推导,无需指定类型
  • 支持任意实现了 Display trait 的类型

2. 基础类型格式化

rust 复制代码
// 数字格式化
let num = 42;
println!("Decimal: {}", num);
println!("Hex: {:x}", num);      // 小写十六进制
println!("Binary: {:b}", num);   // 二进制
println!("Debug: {:?}", num);    // 调试格式

// 浮点数控制
let pi = 3.1415926;
println!("2 decimals: {:.2}", pi);
println!("Scientific: {:e}", pi);

常用格式说明符

  • x/X:十六进制(小写/大写)
  • b:二进制
  • o:八进制
  • e:科学计数法
  • .N:保留 N 位小数

三、高级格式化技巧

1. 对齐与填充

rust 复制代码
// 右对齐填充
println!("{:>10}", "test");  // 输出 "     test"

// 左对齐填充
println!("{:<10}", "test");  // 输出 "test     "

// 零填充
println!("{:0>5}", 42);      // 输出 "00042"

// 符号填充
println!("{:+>6}", 10);      // 输出 "   +10"
println!("{:-<6}", 10);      // 输出 "10----"

2. 宽度与精度控制

rust 复制代码
// 固定宽度
println!("{:5}", 42);        // 输出 "   42"

// 浮点数精度组合
println!("{:10.3}", pi);     // 输出 "     3.142"

// 截断字符串
println!("{:.3}", "Hello");  // 输出 "Hel"

3. 类型强制转换

rust 复制代码
// 显式指定类型
println!("0x{:x}", 255);     // 输出 "0xff"
println!("0o{:o}", 255);     // 输出 "0o377"

// 调试输出
println!("{:?}", (1, "a"));  // 输出 "(1, \"a\")"

四、自定义格式化

1. 实现 Display trait

rust 复制代码
use std::fmt;

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    let p = Point { x: 10, y: 20 };
    println!("Point: {}", p);  // 输出 "Point: (10, 20)"
}

2. 调试输出 (Debug trait)

rust 复制代码
#[derive(Debug)]
struct Person {
    name: String,
    age: u8,
}

fn main() {
    let person = Person {
        name: "Bob".to_string(),
        age: 45,
    };
    println!("{:?}", person);  // 输出 "Person { name: "Bob", age: 45 }"
}

3. 自定义格式参数

rust 复制代码
struct Temperature(f32);

impl fmt::Display for Temperature {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let precision = f.precision().unwrap_or(2);
        write!(f, "{:.*}°C", precision, self.0)
    }
}

fn main() {
    let temp = Temperature(25.678);
    println!("{}", temp);          // 输出 "25.68°C"
    println!("{:.1}", temp);       // 输出 "25.7°C"
}

五、错误处理与调试

1. 编译时类型检查

rust 复制代码
// 类型不匹配会触发编译错误
// println!("{} {}", 42, "text");  // 错误:类型不匹配

// 显式指定类型
println!("{} {}", 42 as i32, "text");  // 正确

2. 格式化错误处理

rust 复制代码
use std::fmt;

struct MyError;

impl fmt::Debug for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Custom error message")
    }
}

fn main() {
    let err = MyError;
    eprintln!("Error: {:?}", err);  // 输出 "Error: Custom error message"
}

六、性能优化技巧

1. 避免重复格式化

rust 复制代码
// 低效方式
for i in 0..1000 {
    println!("Processing item {}", i);
}

// 高效方式(预格式化)
let format_str = "Processing item {}";
for i in 0..1000 {
    println!(format_str, i);
}

2. 使用 write!

rust 复制代码
use std::io::Write;

fn main() {
    let mut buffer = String::new();
    write!(&mut buffer, "Number: {}, {}", 10, 20).unwrap();
    println!("{}", buffer);  // 输出 "Number: 10, 20"
}

七、实战案例:表格输出

rust 复制代码
use prettytable::{Table, Row, Cell};

fn main() {
    let mut table = Table::new();
    table.add_row(Row::new(["Name", "Age"]));
    table.add_row(Row::new(["Alice", Cell::new("30").align(prettytable::Alignment::RIGHT)]));
    table.add_row(Row::new(["Bob", "25"]));
    table.printstd();
}

Cargo.toml 中添加依赖:

toml 复制代码
[dependencies]
prettytable = "0.8"

八、{}、{:?} 和 {:#?} 的区别

在 Rust 中,{}、{:?} 和 {:#?} 是三种常见的格式化占位符,它们分别对应不同的 trait 实现,用于控制输出的格式。以下是它们的区别和使用场景:

在 Rust 中,{}{:?}{:#?} 是三种常见的格式化占位符,它们分别对应不同的 trait 实现,用于控制输出的格式。以下是它们的区别和使用场景:


1. {}:用户友好格式(Display trait)

  • 作用:调用类型的 Display trait 实现,输出用户友好的格式。
  • 特点
    • 用于面向最终用户的输出(如日志、用户界面)。
    • 如果类型没有手动实现 Display,直接使用 {} 会编译报错。
    • 默认实现由开发者定义(需要显式实现)。
  • 示例
rust 复制代码
struct Circle {
    radius: f64,
}

impl std::fmt::Display for Circle {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "Circle with radius {:.2}", self.radius)
    }
}

fn main() {
    let circle = Circle { radius: 5.0 };
    println!("{}", circle); // 输出: Circle with radius 5.00
}

2. {:?}:调试格式(Debug trait)

  • 作用:调用类型的 Debug trait 实现,输出调试信息。
  • 特点
    • 用于开发阶段的调试输出。
    • 如果类型派生了 #[derive(Debug)],可以直接使用 {:?}
    • 输出格式为紧凑的一行,适合快速查看数据结构。
  • 示例
rust 复制代码
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Point { x: 10, y: 20 };
    println!("{:?}", point); // 输出: Point { x: 10, y: 20 }
}

3. {:#?}:美化调试格式(Debug trait 的扩展)

  • 作用:同样调用 Debug trait,但输出格式更美观。
  • 特点
    • 输出为多行格式,字段对齐,适合复杂结构体或嵌套数据。
    • 需要类型实现了 Debug(通过 #[derive(Debug)] 或手动实现)。
  • 示例
rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle { width: 10, height: 5 };
    println!("{:#?}", rect);
    // 输出:
    // Rectangle {
    //     width: 10,
    //     height: 5,
    // }
}

对比总结

占位符 对应 trait 输出格式 用途 是否需要手动实现
{} Display 用户友好格式 面向用户或日志输出 ✅ 需要手动实现
{:?} Debug 紧凑的一行调试信息 开发阶段调试 ❌ 可自动派生
{:#?} Debug 多行美化格式 调试复杂数据结构 ❌ 可自动派生

关键区别

  1. Display vs Debug
  • Display 是用户友好的输出(例如 5.00),而 Debug 是开发者调试的输出(例如 5.0)。
  • Display 需要手动实现,Debug 可以自动派生。
  1. {:?} vs {:#?}
  • {:?} 输出紧凑(适合快速查看),而 {:#?} 输出多行美化(适合复杂结构)。

注意事项

  • 未实现 Display 的类型不能使用 {}
rust 复制代码
#[derive(Debug)]
struct MyStruct;
println!("{}", MyStruct); // ❌ 编译错误:MyStruct 没有实现 Display
  • 手动实现 Debug

如果需要自定义 Debug 格式,可以手动实现 Debug trait:

rust 复制代码
use std::fmt;

struct User {
    username: String,
    password: String,
}

impl fmt::Debug for User {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 隐藏敏感信息
        f.debug_struct("User")
         .field("username", &self.username)
         .field("password", &"[REDACTED]")
         .finish()
    }
}

fn main() {
    let user = User {
        username: "alice".to_string(),
        password: "secret".to_string(),
    };
    println!("{:?}", user); // 输出: User { username: "alice", password: "[REDACTED]" }
}

应用场景

  • Display:用户界面、日志(如 println!("Value: {}", value))。
  • Debug:调试信息(如 println!("{:?}", value))。
  • {:#?}:调试复杂数据结构(如嵌套结构体或数组)。

通过合理选择这些占位符,可以更高效地进行开发和调试!