Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

注释和打印

在 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);
}

💡 核心知识点补充

  • **Debug vs Display**:几乎所有的 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)
占位符要求必须手动写 {}{:?}自动调用 {:?}
信息量仅打印你指定的内容自动包含文件名、行号、表达式
返回值返回单元类型 ()返回表达式的值(所有权转移)
典型用途最终程序输出给用户看开发过程中快速排查问题