打印
这一章介绍打印功能,因为后续所有章节都会用到它来观察变量的值和程序运行结果。掌握好这一章,可以让后续的学习和调试更加顺畅。
先看一个最简单的 Rust 程序:
fn main() {
// 打印 Hello, world! 到屏幕
println!("Hello, world!");
}println! 后面带 !,说明它是一个宏,不是普通函数。Rust 中的打印功能都是通过宏来实现的。
println! 打印宏
println! 是最常用的打印宏,使用 {} 作为占位符,将后面的参数填入对应位置:
println!("Hello, {}!", "world"); // Hello, world!
println!("{} + {} = {}", 1, 2, 1 + 2); // 1 + 2 = 3基本用法
语法: println!("format string", arg1, arg2, ...)
// 1. 按顺序填入
println!("{}, {}", "hello", "world");
// 2. 按位置填入
println!("{1}, {0}", "world", "hello"); // hello, world
// 3. 按名称填入
println!("{name} is {age}", name = "Alice", age = 30);内联变量捕获
Rust 2021 版本起,可以直接在占位符中写变量名:
let name = "Alice";
let age = 30;
println!("{name} is {age}"); // Alice is 30注意: 内联语法只支持本地变量名,不支持表达式或字段访问:
let x = Point { val: 42 };
println!("{x.val}"); // ❌ 编译错误
println!("{}", x.val); // ✅ 正确写法
println!("{}", 1 + 2); // ✅ 参数位置支持任意表达式显示格式
使用 std::fmt 模块提供的格式化功能,可以控制输出的格式和样式。常用的占位符如下:
| 占位符 | 对应 Trait | 说明 |
|---|---|---|
{} | Display | 面向用户的输出,数字和字符串默认支持 |
{:?} | Debug | 面向开发者的紧凑输出 |
{:#?} | Debug | 美化输出,带缩进换行,适合查看结构体 |
{:p} | Pointer | 显示内存地址(十六进制) |
let v = vec![1, 2, 3];
println!("{:?}", v); // [1, 2, 3]
println!("{:#?}", v); // 带缩进的多行格式
println!("{:p}", &v); // 0x7ffeefbff5c0
- 自定义结构体默认不支持
{}和{:?}。添加#[derive(Debug)]注解后可使用{:?}和{:#?}。实现Displaytrait 后才能使用{}。println!、format!、write!、panic!等宏底层都使用同一个格式化引擎std::fmt,格式语法完全通用。
数值进制
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 (科学计数法)对齐与填充
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**** (用 * 填充两侧)浮点数精度
println!("{:.2}", 3.14159); // 3.14
println!("{:8.2}", 3.14159); // " 3.14" (宽度 8,保留 2 位小数)
println!("{:+}", 42); // +42 (强制显示正负号)dbg! 调试打印宏
dbg! 是专为调试设计的打印宏,它会自动打印文件名、行号和表达式的值,非常适合快速定位问题。
基本用法
语法: dbg!(表达式)
let x = 42;
dbg!(x); // [src/main.rs:2] x = 42
dbg!(x + 1); // [src/main.rs:3] x + 1 = 43dbg! 的特殊性
dbg! 最强大的特性是转移所有权并返回原值,这让它可以嵌入表达式中间使用:
let a = 10;
let b = dbg!(a * 2) + 1; // 打印 [src/main.rs:2] a * 2 = 20,同时 b = 21
println!("b = {b}"); // b = 21也可以作为函数返回值的一部分:
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! 在调试链式调用时特别有用:
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!() 不需要参数,直接打印当前代码位置:
fn process() {
dbg!(); // [src/main.rs:2]
// 只打印位置信息,用于追踪代码执行流
}println! 和 dbg! 的区别
这是两种最常用的调试打印手段,它们的设计理念完全不同:
| 特性 | println! | dbg! |
|---|---|---|
| 第一个参数 | 必须是字符串字面量(格式模板) | 可以是任意表达式,也可以留空 |
| 输出内容 | 完全由你控制 | 自动带上文件名、行号和变量名 |
| 所有权 | 借用参数,不转移 | 转移所有权,但返回值本身 |
| 输出流 | stdout | stderr |
| 空括号 | println!() 仅打印换行 | dbg!() 打印当前代码位置 |
调试时推荐优先使用
dbg!,它自动带文件名和行号,信息量更大,用完也更容易清理。
打印宏进阶
Rust 提供了两组打印宏,分别输出到不同的流 stdout 和 stderr,适用于不同的场景。
stdout 和 stderr
stdout(标准输出流): 用于正常的程序输出:
| 宏 | 换行 | 说明 |
|---|---|---|
print! | ❌ | 输出内容,不换行 |
println! | ✅ | 输出内容并换行,最常用 |
dbg! | ✅ | 打印文件名、行号和表达式的值,专用于调试 |
stderr(标准错误流): 用于错误信息和日志:
| 宏 | 换行 | 说明 |
|---|---|---|
eprint! | ❌ | 输出到标准错误,不换行 |
eprintln! | ✅ | 输出到标准错误并换行 |
为什么要分两个流?在终端中,用户经常会将程序输出重定向到文件(
cargo run > output.txt)。如果只用println!,错误信息也会进文件,屏幕上什么都看不到。使用eprintln!的错误信息不受重定向影响,始终显示在屏幕上。
stdout 和 stderr 的重定向
- stdout(标准输出流) 默认指向屏幕,但可以通过
>符号重定向到文件 - stderr(标准错误流) 默认也指向屏幕,但可以通过
2>符号重定向到文件
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!: 打印"正在处理..."、"网络错误"、"缺少参数"等非业务信息