前几天在群里看到有人讨论 dbg!宏已经在 Nightly 可以使用了,最近发布的 stable 版本 1.32.0 也可以使用了。翻看了一下并玩了玩,这个简单的宏确实是调试好帮手,特别是适合我这样的喜欢打印调试的开发者。这个提议从 2017 年 10 月开始,从 https://github.com/rust-lang/rfcs/pull/2173 可以看到,为了增加这个宏很多贡献者经过了无数次的讨论和回复。真是太佩服 Rust Team 的开发者,付出了这么多时间来增加这个看似很小又实用的功能。
使用
先看看这个调试宏是怎么使用的,目前使用这个宏需要切换到 Nightly 版本或者最新的稳定版,已经安装了 rustup 的话就很简单了:
rustup default nightly
rustup update
然后很简单就是把一个表达式当作参数传入:
fn factorial(n: u32) -> u32 {
if dbg!(n <= 1) {
dbg!(1)
} else {
dbg!(n * factorial(n - 1))
}
}
fn main() {
dbg!(factorial(5));
}
运行结果如下:
[src/main.rs:4] n <= 1 = false
[src/main.rs:4] n <= 1 = false
[src/main.rs:4] n <= 1 = false
[src/main.rs:4] n <= 1 = false
[src/main.rs:4] n <= 1 = true
[src/main.rs:5] 1 = 1
[src/main.rs:7] n * factorial(n - 1) = 2
[src/main.rs:7] n * factorial(n - 1) = 6
[src/main.rs:7] n * factorial(n - 1) = 24
[src/main.rs:7] n * factorial(n - 1) = 120
[src/main.rs:12] factorial(5) = 120
实现
原理当然也就是把表达式和位置打印出来,但是这里有个技巧,在宏里面使用 match,这是为了避免参数被调用多次,因为宏在编译之前会被展开。Rust 的宏比较复杂,也不可避免会有些 hacky,对于喜欢爱折腾的程序员还是有吸引力。再看看这个宏是怎么实现的,代码很少。:
macro_rules! dbg {
($val:expr) => {
match $val {
tmp => {
eprintln!("[{}:{}] {} = {:#?}",
file!(), line!(), stringify!($val), &tmp);
tmp
}
}
}
}
可以看到目前这个实现是只支持一个参数的,如果传入的参数类型没有实现 Copy Trait,可以传入引用。另外如果想同时打印多个参数,可以使用类似这样的做法:
dbg!((exp1, exp2))