一、什么是格式化输出?
Rust 的格式化输出功能强大且灵活,通过println!
、print!
、 format!
等宏实现。
支持多种占位符(如 {} 、 {:?} 、 {:#?}
)和格式化选项(如宽度、精度、对齐方式),能够满足从简单文本输出到复杂数据结构调试的需求。
Rust 还允许通过实现 Display
和 Debug
特征来自定义类型格式化行为,同时支持动态精度、宽度控制和元组结构体等高级用法。
二、基础格式化
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 |
多行美化格式 | 调试复杂数据结构 | ❌ 可自动派生 |
关键区别
Display
vsDebug
:
Display
是用户友好的输出(例如5.00
),而Debug
是开发者调试的输出(例如5.0
)。Display
需要手动实现,Debug
可以自动派生。
{:?}
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)
)。{:#?}
:调试复杂数据结构(如嵌套结构体或数组)。
通过合理选择这些占位符,可以更高效地进行开发和调试!