注释和打印
在 Rust 中,注释是写给程序员看的“备忘录”,而打印则是程序与外界沟通的最基本方式。理解这两者能极大地提升开发和调试效率。
一、 注释 (Comments)
Rust 支持多种注释风格,除了代码解释外,Rust 的注释还深度集成了文档生成工具 cargo doc。
1. 常规注释
- 单行注释 :使用
//,这是最常用的注释方式。 - 块注释 (多行) :使用
/* ... */。虽然 Rust 支持,但社区更倾向于在多行也使用单行注释。
fn main() {
// 这是一个单行注释
let x = 5; // 也可以在代码行末尾
/* 这是一个块注释
它可以跨越多行 */
let y = 10;
}
2. 文档注释 (Doc Comments)
这是 Rust 的特色,用于生成 HTML 格式的 API 文档。
- 三斜杠
///:为紧随其后的 项目 (如函数、结构体)生成文档。支持 Markdown 语法。 - 双斜杠感叹号
//!:为包含该注释的 条目 (如整个 crate 或模块)生成文档。
//! # 核心逻辑模块
//! 这个模块包含了一些数学运算函数。
/// 将两个数字相加。
///
/// # Examples
/// ```
/// let res = add(1, 2);
/// ```
fn add(a: i32, b: i32) -> i32 {
a + b
}
在文档章节会详细介绍文档注释的使用方法和注意事项。
二、 打印 (Printing)
Rust 的打印是通过一组宏 (Macros) 来实现的。宏的显著特征是名称末尾带有感叹号 !。
1. 核心宏
| 宏名称 | 功能描述 |
|---|---|
print! | 打印到标准输出,不换行。 |
println! | 打印到标准输出,自动换行 。 |
format! | 不打印,而是返回一个格式化后的 String。 |
eprintln! | 打印到标准错误输出 (stderr) ,常用于打印错误信息。 |
2. 占位符
参数位置与命名
除了按顺序匹配,你还可以通过索引或名称来复用变量。
fn main() {
let name = "Alice";
let age = 30;
// 按顺序匹配
println!("{} is {} years old.", name, age); // Alice is 30 years old.
// 索引匹配
println!("{0} is {1} years old.", name, age); // Alice is 30 years old.
// 具名匹配
println!("{name} is {age} years old."); // Alice is 30 years old.
}
格式化占位符核心语法
下表总结了占位符 {} 内部可以使用的所有核心语法:
| 类别 | 语法示例 | 效果描述 | 42或“Hi“为例 |
|---|---|---|---|
| 基础展示 | {} | 调用 Display 特征,普通人类可读输出。 | 42 |
{:?} | 调用 Debug 特征,程序员调试用输出。 | 42 | |
{:#?} | 漂亮打印 (Pretty Print),多行缩进展示复杂结构。 | (分行显示的结构) | |
| 参数索引 | {0} | 使用第 1 个位置参数(索引从 0 开始)。 | 42 |
{name} | 使用具名参数。 | Hi | |
| 对齐与填充 | {:10} | 设置宽度为 10,默认左对齐(字符串)或右对齐(数字)。 | "Hi " |
{:<10} | 强制左对齐。 | "Hi " | |
{:>10} | 强制右对齐。 | " Hi" | |
{:^10} | 强制居中对齐。 | " Hi " | |
{:*^10} | 使用 * 进行填充(填充字符必须在对齐符号前)。 | "****Hi****" | |
| 数字进制 | {:b} | 转换为二进制 (Binary)。 | 101010 |
{:o} | 转换为八进制 (Octal)。 | 52 | |
{:x} / {:X} | 转换为十六进制 (Hex),大小写决定字母大小写。 | 2a / 2A | |
{:#x} | 带有进制前缀的十六进制。 | 0x2a | |
| 精度与正负 | {:.2} | 浮点数保留 2 位小数。 | 3.14 |
{:+.2} | 强制显示正负号。 | +42.00 | |
{:05} | 宽度为 5,不足部分用 0 填充。 | 00042 | |
| 特殊指针 | {:p} | 打印内存地址(适用于引用或原生指针)。 | 0x7ffee1234567 |
| 转义 | {{ / }} | 在格式化字符串中显示原始的大括号。 | { / } |
为了更直观地理解如何组合这些选项,请看下面的综合实例:
use std::mem::size_of;
#[derive(Debug)]
struct Point { x: i32, y: i32 }
fn main() {
let p = Point { x: 10, y: 20 };
let pi = 3.14159;
// 1. 组合:宽度、对齐、填充、精度
// 效果:居中对齐,宽度10,用'-'填充,保留2位小数
println!("数值展示: {:*^10.2}", pi);
// 2. 指针地址展示
// 使用 :p 查看变量在栈上的地址
let r = &p;
println!("结构体 p 的地址: {:p}", r);
// 3. 进制与前缀
let val = 255;
println!("十六进制: {:#X}, 二进制: {:b}", val, val);
// 4. 调试复杂结构
// 使用 {:#?} 实现易读的缩进输出
println!("漂亮打印结构体: {:#?}", p);
}
💡 核心知识点补充
- **
DebugvsDisplay**:几乎所有的 Rust 标准库类型都实现了Debug(用于调试),但并非都实现了Display(因为某些类型没有唯一的人类可读展示方式)。 - 指针长度:在 64 位系统上,使用
{:p}打印出的地址通常对应一个 8 字节(单字长)的内存位置。 - 内存效率:所有的
print!系列宏在编译时都会被检查。如果占位符数量与参数不匹配,编译器会直接报错,这保证了运行时的类型安全。
结构化调试打印
- 派生 Debug: 使用
#[derive(Debug)]注解结构体或枚举,自动生成 Debug 实现。{:?}:以调试模式打印(需要类型实现std::fmt::Debug)。{:#?}: 美化打印 ,会自动分行并添加缩进,适合查看大型结构体。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("rect 是 {:?}", rect); // 输出: rect 是 Rectangle { width: 30, height: 50 }
println!("rect 是 {:#?}", rect); // 美化输出,多行缩进
}
- 手动实现 Debug: 如果需要自定义格式,实现 std::fmt::Debug trait
use std::fmt;
struct Point {
x: i32,
y: i32,
}
impl fmt::Debug for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Point")
.field("x", &self.x)
.field("y", &self.y)
.finish()
}
}
fn main() {
let p = Point { x: 1, y: 2 };
println!("{:?}", p); // 输出: Point { x: 1, y: 2 }
}
- Debug vs Display:
- Debug:用于开发者,格式如 { x: 1, y: 2 },通过 {:?}。
- Display:用于用户友好输出,通过 {}。需手动实现 std::fmt::Display。
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
println!("{}", p); // 输出: (1, 2)
3. dbg宏
dbg! 宏用于调试,它打印表达式的值和源代码位置,然后返回该值。适合插入代码中快速检查,而不中断流程。
- 语法:
- dbg!(表达式);:打印表达式的文件名、行号、列号和值,返回表达式本身。
- 支持借用(&),避免所有权转移。
#[derive(Debug)] // 使用 dbg! 要求类型必须实现 Debug 特征
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
// 1. println! 的方式:必须单独写一行,且需要手动写描述
println!("矩形的数据是: {:?}", rect);
// 2. dbg! 的方式:直接包裹表达式
// 它会打印:[src\main.rs:12:5] rect = Rectangle { width: 30, height: 50 }
dbg!(&rect);
// 3. 嵌套使用(逻辑不中断)
let area = dbg!(rect.width * rect.height); // 打印计算过程并把结果赋给 area
println!("面积是: {}", area);
}
-
与结构体结合: dbg! 使用 Debug trait,如果结构体未实现 Debug,会编译错误。
-
注意:dbg! 只在调试构建中有效,在发布模式下可能被优化掉。输出到 stderr,便于区分正常输出。
dbg! 与 println! 的详细对比
| 特性 | println! | dbg! |
|---|---|---|
| 输出目标 | 标准输出 (stdout) | 标准错误 (stderr) |
| 占位符要求 | 必须手动写 {} 或 {:?} | 自动调用 {:?} |
| 信息量 | 仅打印你指定的内容 | 自动包含文件名、行号、表达式 |
| 返回值 | 返回单元类型 () | 返回表达式的值(所有权转移) |
| 典型用途 | 最终程序输出给用户看 | 开发过程中快速排查问题 |