Skip to content

打印

这一章介绍打印功能,因为后续所有章节都会用到它来观察变量的值和程序运行结果。掌握好这一章,可以让后续的学习和调试更加顺畅。

先看一个最简单的 Rust 程序:

rust
fn main() {
    // 打印 Hello, world! 到屏幕
    println!("Hello, world!");
}

println! 后面带 !,说明它是一个,不是普通函数。Rust 中的打印功能都是通过宏来实现的。

println! 打印宏

println! 是最常用的打印宏,使用 {} 作为占位符,将后面的参数填入对应位置:

rust
println!("Hello, {}!", "world");        // Hello, world!
println!("{} + {} = {}", 1, 2, 1 + 2);  // 1 + 2 = 3

基本用法

语法: println!("format string", arg1, arg2, ...)

rust
// 1. 按顺序填入
println!("{}, {}", "hello", "world");

// 2. 按位置填入
println!("{1}, {0}", "world", "hello");  // hello, world

// 3. 按名称填入
println!("{name} is {age}", name = "Alice", age = 30);

内联变量捕获

Rust 2021 版本起,可以直接在占位符中写变量名:

rust
let name = "Alice";
let age = 30;
println!("{name} is {age}");  // Alice is 30

注意: 内联语法只支持本地变量名,不支持表达式或字段访问:

rust
let x = Point { val: 42 };
println!("{x.val}");          // ❌ 编译错误
println!("{}", x.val);        // ✅ 正确写法
println!("{}", 1 + 2);        // ✅ 参数位置支持任意表达式

显示格式

使用 std::fmt 模块提供的格式化功能,可以控制输出的格式和样式。常用的占位符如下:

占位符对应 Trait说明
{}Display面向用户的输出,数字和字符串默认支持
{:?}Debug面向开发者的紧凑输出
{:#?}Debug美化输出,带缩进换行,适合查看结构体
{:p}Pointer显示内存地址(十六进制)
rust
let v = vec![1, 2, 3];
println!("{:?}", v);   // [1, 2, 3]
println!("{:#?}", v);  // 带缩进的多行格式
println!("{:p}", &v);  // 0x7ffeefbff5c0
  • 自定义结构体默认不支持 {}{:?}。添加 #[derive(Debug)] 注解后可使用 {:?}{:#?}。实现 Display trait 后才能使用 {}
  • println!format!write!panic! 等宏底层都使用同一个格式化引擎 std::fmt,格式语法完全通用。

数值进制

rust
let n = 255;
println!("{:b}", n);   // 11111111  (二进制)
println!("{:o}", n);   // 377       (八进制)
println!("{:x}", n);   // ff        (十六进制小写)
println!("{:X}", n);   // FF        (十六进制大写)
println!("{:#x}", n);  // 0xff      (带前缀)
println!("{:e}", n);   // 2.55e2    (科学计数法)

对齐与填充

rust
println!("|{:10}|", "left");    // |left      |  默认左对齐
println!("|{:<10}|", "left");   // |left      |  左对齐
println!("|{:>10}|", "right");  // |     right|  右对齐
println!("|{:^10}|", "center"); // |  center  |  居中

println!("{:0>5}", 42);         // 00042  (用 0 填充左侧)
println!("{:*^10}", "Hi");      // ****Hi****  (用 * 填充两侧)

浮点数精度

rust
println!("{:.2}", 3.14159);   // 3.14
println!("{:8.2}", 3.14159);  // "    3.14"  (宽度 8,保留 2 位小数)
println!("{:+}", 42);         // +42         (强制显示正负号)

dbg! 调试打印宏

dbg! 是专为调试设计的打印宏,它会自动打印文件名行号表达式的值,非常适合快速定位问题。

基本用法

语法: dbg!(表达式)

rust
let x = 42;
dbg!(x);      // [src/main.rs:2] x = 42
dbg!(x + 1);  // [src/main.rs:3] x + 1 = 43

dbg! 的特殊性

dbg! 最强大的特性是转移所有权并返回原值,这让它可以嵌入表达式中间使用:

rust
let a = 10;
let b = dbg!(a * 2) + 1;  // 打印 [src/main.rs:2] a * 2 = 20,同时 b = 21
println!("b = {b}");      // b = 21

也可以作为函数返回值的一部分:

rust
fn compute() -> i32 {
    let a = 5;
    dbg!(a * 2);  // 错误: 只打印 [src/main.rs:2] a * 2 = 10,不返回 10
    dbg!(a * 2)   // 打印 [src/main.rs:2] a * 2 = 10,同时返回 10
}

;结尾是语句,不返回值;没有;结尾是表达式,返回值为最后一个表达式的结果。

链式调用调试

dbg! 在调试链式调用时特别有用:

rust
let result = dbg!(vec![1, 2, 3])
    .iter()
    .map(|x| dbg!(x * 2))
    .collect::<Vec<_>>();
// 输出:
// [src/main.rs:2] vec![1, 2, 3] = [1, 2, 3]
// [src/main.rs:4] x * 2 = 2
// [src/main.rs:4] x * 2 = 4
// [src/main.rs:4] x * 2 = 6

空括号用法

dbg!() 不需要参数,直接打印当前代码位置:

rust
fn process() {
    dbg!();  // [src/main.rs:2]
    // 只打印位置信息,用于追踪代码执行流
}

println!dbg! 的区别

这是两种最常用的调试打印手段,它们的设计理念完全不同:

特性println!dbg!
第一个参数必须是字符串字面量(格式模板)可以是任意表达式,也可以留空
输出内容完全由你控制自动带上文件名、行号和变量名
所有权借用参数,不转移转移所有权,但返回值本身
输出流stdoutstderr
空括号println!() 仅打印换行dbg!() 打印当前代码位置

调试时推荐优先使用 dbg!,它自动带文件名和行号,信息量更大,用完也更容易清理。

打印宏进阶

Rust 提供了两组打印宏,分别输出到不同的流 stdoutstderr,适用于不同的场景。

stdout 和 stderr

stdout(标准输出流): 用于正常的程序输出:

换行说明
print!输出内容,不换行
println!输出内容并换行,最常用
dbg!打印文件名、行号和表达式的值,专用于调试

stderr(标准错误流): 用于错误信息和日志:

换行说明
eprint!输出到标准错误,不换行
eprintln!输出到标准错误并换行

为什么要分两个流?在终端中,用户经常会将程序输出重定向到文件(cargo run > output.txt)。如果只用 println!,错误信息也会进文件,屏幕上什么都看不到。使用 eprintln! 的错误信息不受重定向影响,始终显示在屏幕上。

stdout 和 stderr 的重定向

  • stdout(标准输出流) 默认指向屏幕,但可以通过 > 符号重定向到文件
  • stderr(标准错误流) 默认也指向屏幕,但可以通过 2> 符号重定向到文件
bash
cargo run > output.txt       # stdout 进文件,stderr 仍显示在屏幕
cargo run 2> error.txt       # stderr 进文件,stdout 仍显示在屏幕
cargo run &> all.txt         # stdout 和 stderr 都进文件
符号对应流典型用途
> / 1>标准输出 stdout存储程序的正常结果
2>标准错误 stderr存储报错和日志
&>全部完整记录所有输出

使用建议:

  • println!: 只打印用户请求的数据(搜索结果、计算结果等)
  • eprintln!: 打印"正在处理..."、"网络错误"、"缺少参数"等非业务信息

基于 MIT 协议发布