Skip to content

打印和格式化

官方教程到这里会给你讲一个"猜数字"的小游戏,虽然很有趣,但不建议初学者直接阅读。因为这个游戏涉及到很多概念(随机数、输入输出、循环、条件判断等),如果你还不熟悉 Rust 的基础语法,只会增加你的负担,让你更容易放弃。

本章提前介绍打印功能,是因为后续所有章节都会用到它来观察变量的值和程序运行结果。掌握好这一章,可以让后续的学习和调试更加顺畅。如果实在看不懂没关系,先有个印象.

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

rust
fn main() {
    println!("Hello, world!");
}

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

打印宏概览

Rust 提供了两组打印宏,分别输出到不同的流:

标准输出(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!:打印"正在处理..."、"网络错误"、"缺少参数"等非业务信息

占位符与格式化

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

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

三种参数写法:

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

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

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

显示格式(最常用)

占位符对应 Trait说明
{}Display面向用户的输出,数字和字符串默认支持
{:?}Debug面向开发者的紧凑输出
{:#?}Debug美化输出,带缩进换行,适合查看结构体
{:p}Pointer显示内存地址(十六进制)
rust
let v = vec![1, 2, 3];
println!("{:?}", v);   // [1, 2, 3]
println!("{:#?}", v);  // 带缩进的多行格式

自定义结构体默认不支持 {}{:?}。添加 #[derive(Debug)] 注解后可使用 {:?}{:#?}。实现 Display trait 后才能使用 {}

数值进制

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         (强制显示正负号)

println!dbg! 的区别

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

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

dbg! 的"转移所有权并返回"特性让它可以嵌入表达式中间使用,不打断代码逻辑:

rust
// 调试链式调用时非常有用
let result = dbg!(vec![1, 2, 3])
    .iter()
    .map(|x| dbg!(x * 2))
    .collect::<Vec<_>>();

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

format! 格式化到字符串

format! 使用和 println! 完全相同的格式语法,但不打印,而是返回一个 String,主要用于字符串的拼接:

rust
let s = format!("Hello, {}!", "world");  // 返回 String 类型 "Hello, world!"
let info = format!("{:.2}%", 99.9);      // 返回 String 类型 "99.90%"

输出流对比:

输出目标返回值
println!屏幕(stdout)
eprintln!屏幕(stderr)
format!字符串(内存)String
write!任意缓冲区Result

println!format!write!panic! 等宏底层都使用同一个格式化引擎 std::fmt,格式语法完全通用。

内联变量捕获

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);        // ✅ 参数位置支持任意表达式

基于 MIT 协议发布