Skip to content

手动输出宏

调试最简单粗暴的方式就是往控制台打印信息, 我们需要在代码的各个关键位置插入打印宏, 观察程序的中间状态

作用
dbg打印表达式的同时打印源码文件/行号
println将内容输出到 stdout (换行)
eprintln将内容输出到 stderr (换行)

println! / eprintln! / print! / eprint! 以及格式化占位符 {} / {:?} 等详细内容, 已经在 1.常见宏.md 中介绍过, 这里不再赘述, 重点是 dbg!

dbg! 宏

dbg! 是一个专门用于调试的宏, 和 println! 相比有以下特点:

  • 自动带上源码文件路径行号, 定位更加方便
  • 输出到 stderr, 不会污染我们通过 stdout 输出的正常数据(例如管道传给其他程序的数据)
  • 会返回传入表达式的值, 因此可以就地嵌入到表达式中(甚至可以链式使用)
  • 会打印 文字名 = 值, 不需要手动补上变量名

提示

发布(Release) 版本中 dbg! 不会像 debug 断言那样被移除, 它会照常编译执行, 所以只把它用于开发期的临时调试, 不要留到正式代码里

方式示例
单独打印表达式dbg!(x + 1)
返回表达式值, 嵌入到赋值/表达式中let y = dbg!(x * 2);
链式调用fn_t().dbg() / dbg!(dbg!(x))
rust
fn main() {
    let x = 42;

    //=== 打印变量(自动带上 名字=值, 以及源码文件+行号) ===//
    dbg!(x);
    // [src/main.rs:4:5] x = 42

    // 打印表达式
    dbg!(x + 1);
    // [src/main.rs:7:5] x + 1 = 43

    //=== dbg! 会返回表达式的值, 可以就地嵌入 ===//
    let y = dbg!(x * 2);
    // [src/main.rs:11:13] x * 2 = 84
    println!("y = {y}"); // y = 84

    // 甚至可以链式使用
    dbg!(dbg!(x / 2) + 1);
    // [src/main.rs:16:7] x / 2 = 21
    // [src/main.rs:16:12] dbg!(x / 2) + 1 = 22

    // 打印数组等集合也很方便
    let nums = [1, 2, 3, 4];
    dbg!(nums);
    // [src/main.rs:22:5] nums = [
    //     1,
    //     2,
    //     3,
    //     4,
    // ]

    // 注意: 这里利用数组是 Copy 的, 打印后数组依旧可用
    // 如果是非 Copy 类型(比如 String), 直接 dbg!(s) 会「移动」(所有权转移)
    let s = String::from("hello");
    dbg!(&s); // 借用, 打印后还能继续使用
    // [src/main.rs:29:5] &s = "hello"
    println!("{s}"); // hello
}

注意点

dbg! 对于传入的表达式是按值(所有权)传递的。如果传入的是非 Copy 类型(如 String, Vec), 直接 dbg!(s) 会发生移动, 后续就无法再使用 s 了, 需要借用 dbg!(&s) 才能继续使用

调整编译器输出信息

打印 panic 的调用栈 RUST_BACKTRACE

当程序发生 panic! 时, 默认只打印错误信息, 并不会显示调用栈(调用堆栈)。 如果想定位 panic 到底是从哪一层被传上来的, 可以设置环境变量 RUST_BACKTRACE:

取值作用
1显示基本的调用栈
full显示完整的调用栈(包含所有细节)
未设置默认只显示 panic 位置, 不打印调用栈

该内容在 30-错误处理专题.md 中也有涉及

sh
# 运行时会先显示 panic 的位置, 再打印 stack backtrace
RUST_BACKTRACE=1 cargo run

# 输出 (节选):
# thread 'main' panicked at src/main.rs:4:5:
# 出错了
# stack backtrace:
#    0: rust_begin_unwind
#              at /rustc/.../library/std/src/panicking.rs:...
#    1: core::panicking::panic_fmt
#    2: main
#    3: std::sys_common::backtrace::__rust_begin_short_backtrace
#    ...

提示

RUST_BACKTRACE 设置的环境变量只在本次命令生效; 如果想默认一直打印调用栈, 也可以在源码中用 std::env::set_var("RUST_BACKTRACE", "1"); 在最开始设置, 不过更推荐在开发调试时通过命令行临时设置

其他环境变量 RUST_LOG

RUST_LOG 用于控制日志库(log + env_logger)的输出等级, 在下面的"日志"一节会用到

日志

log 与 env_logger

标准库没有提供内置的日志组件, 通常使用第三方 crate 组合:

  • log: 只提供日志门面(facade), 定义了日志的抽象
  • env_logger: 是 log 的一个具体实现, 负责真正把日志写出去

日志等级从高到低依次为(只会输出大于等于设定等级的日志):

等级宏说明
error!错误, 最严重
warn!警告
info!信息
debug!调试
trace!跟踪, 最详细(默认不输出)
toml
[dependencies]
log = "0.4"
env_logger = "0.11"
rust
use log::{debug, error, info, trace, warn};

fn main() {
    // 初始化日志器(这里用 env_logger 的默认配置)
    env_logger::init();

    trace!("some trace message");
    debug!("some debug message");
    info!("some info message");
    warn!("some warn message");
    error!("some error message");
}

默认 env_logger 的过滤等级是 error, 也就是只输出 error!。 想要看到更多等级的日志, 通过环境变量 RUST_LOG 指定:

sh
# 输出 error / warn / info / debug / trace 的全部日志
RUST_LOG=trace cargo run

# 只输出 error 和 warn
RUST_LOG=warn cargo run

更深入的日志配置(输出到文件、自定义格式、按日期分割等), 请参考 03.日志处理.md

tracing

tracing 是比 log 更进一步的应用级日志/追踪库, 专门为异步任务设计, 可以记录调用跨度(spans)和事件, 常用于实现对请求链路的追踪。

toml
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
rust
use tracing::{info, instrument, Level};

#[instrument]
async fn handle_request(path: &str) {
    info!(path, "处理请求");
    // ...
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().with_max_level(Level::TRACE).init();

    handle_request("/hello").await;
}

关于日志与追踪的详细介绍可以参考 03.日志处理.md

IDE 与 Debugger

在 IDE 中利用图形化的调试器, 可以给代码打断点、单步执行、实时查看变量值, 比单纯的打印调试高效很多

VSCode + rust-analyzer + CodeLLDB

组件作用
VSCode编辑器
rust-analyzerRust 语言服务, 提供补全、跳转、重构等智能提示
CodeLLDBLLDB 调试器扩展, 提供断点、单步、监视变量等图形化界面

使用步骤大致如下:

  1. 安装 VSCode, 并在扩展市场安装 rust-analyzerCodeLLDB
  2. 打开 Rust 项目, rust-analyzer 会自动加载并分析代码
  3. 在需要的地方点击行号左侧打上断点(红点)
  4. F5 选择 LLDB 调试配置, 启动调试
  5. 通过调试面板实现单步跳过(F10) / 步入(F11) / 步出(Shift+F11), 以及在左侧监听变量

提示

除了基于 LLDB 的调试器, Rust 社区也有使用 cargo-hackCriterion 等工具做性能测试/基准测试的实践, 但这些的重点侧重于"性能分析"而非"调试程序"

Released under the MIT License.