调试模式 Debug
编译 debug 模式
可以用 cargo 生成:
cargo build --debug foo_bar
也可以手动调用 rustc
, 生成 debug 模块的目标:
rustc -g foo.rs
如果感觉性能不好的话, 可以加上 O2
选项:
rustc -g -O foo.rs
debug_assert!()
宏
debug_assert!()
宏类似于 C 语言中的 assert()
宏, 只在 debug
模式下有效.
在 release
模式下, 被展开为空白代码.
debug_assertions 属性
#[cfg(debug_assertions)]
用于判断是否处于 debug 编译模式, 通常在调试模式下, 会打印出更多的调试代码.
看下面的例子:
#[allow(unused_variables)] fn main() { let keys = [1, 1, 2, 3, 5, 8, 13, 21]; #[cfg(debug_assertions)] for i in 1..keys.len() { assert!(keys[i - 1] <= keys[i]); } }
dbg 宏
这个宏用于打印表达式本身的内容, 代码位置以及其返回值到错误输出 (stderr), 比 eprintln!()
等要更为快捷,
而且它并不会影响表达式本身的执行 (但是有副作用). 先看几个例子:
fn fibonacci(n: i32) -> i32 { if dbg!(n == 1 || n == 2) { return 1; } dbg!(fibonacci(n - 1) + fibonacci(n - 2)) } fn main() { let fib = fibonacci(5); assert_eq!(fib, 5); }
上面的代码, 输出如下调试信息:
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = false
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = false
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = false
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = true
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = true
[code/perf/src/bin/dbg-macro.rs:9:5] fibonacci(n - 1) + fibonacci(n - 2) = 2
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = true
[code/perf/src/bin/dbg-macro.rs:9:5] fibonacci(n - 1) + fibonacci(n - 2) = 3
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = false
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = true
[code/perf/src/bin/dbg-macro.rs:6:8] n == 1 || n == 2 = true
[code/perf/src/bin/dbg-macro.rs:9:5] fibonacci(n - 1) + fibonacci(n - 2) = 2
[code/perf/src/bin/dbg-macro.rs:9:5] fibonacci(n - 1) + fibonacci(n - 2) = 5
可以发现, dbg!
宏对于跟踪递归代码非常有效, 它能打印出类似函数调用栈的信息.
获取当前代码的位置信息
标准库中带了好几个宏, 用于返回当前源代码的位置信息:
file!()
, 当前源代码文件的文件名line!()
, 当前代码所在的行号, 从 1 开始计数column!()
, 当前代码所在的列号, 从 1 开始计数
看一个代码示例:
// Copyright (c) 2024 Xu Shaohua <shaohua@biofan.org>. All rights reserved. // Use of this source is governed by GNU General Public License // that can be found in the LICENSE file. fn main() { let filename = file!(); let line_num = line!(); let column_num = column!(); println!("filename: {filename}, line num: {line_num}, column num: {column_num}"); }
filename: code/perf/src/bin/file-macro.rs, line num: 7, column num: 22
但是, 如何获取函数名呢? 目前标准库还不支持, 但我们可以使用第三方的库:
获取函数调用者的信息
除了上面介绍的获取当前代码信息信息之外, 在函数体内部, 也可以跟踪哪个地方在调用它, 这对于追踪较复杂的 函数调用关系链比较有效.
use std::panic::Location; fn noop() { let caller = Location::caller(); println!("caller is: {caller:?}"); dbg!(caller); } fn main() { let _x = 42; noop(); }
上述代码, 会输出 noop()
函数的调用者的信息:
caller is: Location { file: "code/perf/src/bin/get-caller-location.rs", line: 8, col: 18 }
[code/perf/src/bin/get-caller-location.rs:10:5] caller = Location {
file: "code/perf/src/bin/get-caller-location.rs",
line: 8,
col: 18,
}
在标准库的引用计数类型的指针中, 经常用它来追踪函数的调用者信息. 比如 RefCell<T>
:
#![allow(unused)] fn main() { //! From `core/src/cell.rs` impl RefCell { #[inline] #[cfg_attr(feature = "debug_refcell", track_caller)] pub fn try_borrow(&self) -> Result<Ref<'_, T>, BorrowError> { match BorrowRef::new(&self.borrow) { Some(b) => { #[cfg(feature = "debug_refcell")] { // `borrowed_at` is always the *first* active borrow if b.borrow.get() == 1 { self.borrowed_at.set(Some(crate::panic::Location::caller())); } } // SAFETY: `BorrowRef` ensures that there is only immutable access // to the value while borrowed. let value = unsafe { NonNull::new_unchecked(self.value.get()) }; Ok(Ref { value, borrow: b }) } None => Err(BorrowError { // If a borrow occurred, then we must already have an outstanding borrow, // so `borrowed_at` will be `Some` #[cfg(feature = "debug_refcell")] location: self.borrowed_at.get().unwrap(), }), } } #[stable(feature = "try_borrow", since = "1.13.0")] #[inline] #[cfg_attr(feature = "debug_refcell", track_caller)] pub fn try_borrow_mut(&self) -> Result<RefMut<'_, T>, BorrowMutError> { match BorrowRefMut::new(&self.borrow) { Some(b) => { #[cfg(feature = "debug_refcell")] { self.borrowed_at.set(Some(crate::panic::Location::caller())); } // SAFETY: `BorrowRefMut` guarantees unique access. let value = unsafe { NonNull::new_unchecked(self.value.get()) }; Ok(RefMut { value, borrow: b, marker: PhantomData }) } None => Err(BorrowMutError { // If a borrow occurred, then we must already have an outstanding borrow, // so `borrowed_at` will be `Some` #[cfg(feature = "debug_refcell")] location: self.borrowed_at.get().unwrap(), }), } } } }