- Feature Name: quick_debug_macro
- Start Date: 2017-10-13
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Summary
Adds a macro dbg!([expr1 , expr2, .., exprN]) for quick and dirty Debuging
of expressions to the terminal. The macro evaluates expressions, prints it to
STDERR, and finally yields a flat tuple of ([expr1, expr2, .. exprN]).
On release builds, the macro is the identity function and has no side effects.
The macro is added to the prelude of the standard library.
Motivation
The motivation to add this new dbg! macro is two-fold.
For aspiring rustaceans
One of the often asked questions is how to print out variables to the terminal.
Delaying the need to explain formatting arguments in the statement
println!("{:?}", expr); can help aspiring rustaceans to quickly learn the
language. With dbg!(expr); there is no longer such need, which can be delayed
until the developer actually cares about the format of the output and not just
what value the expression evaluates to.
For experienced developers
By using dbg!(expr);, the burden of a common papercut: writing
println!("{:?}", expr); every time you want to see the evaluted-to value of an
expression, can be significantly reduced. The developer no longer has to remember
the formatting args and has to type significantly less (12 characters to be exact).
To increase the utility of the macro, it acts as a pass-through function on the expression by simply printing it and then yielding it. On release builds, the macro is the identity function - thus, the macro can be used in release builds without hurting performance while allowing the debugging of the program in debug builds. To see why this is useful, consider starting off with:
let c = fun(a) + fun(b);
let y = self.first().second();Now, you want to inspect what fun(a) and fun(b) is, but not go through the
hassle of 1) saving fun(a) and fun(b) to a variable, 2) printing out the
variable, 3) then finally use it in the expression as let c = fa + fb;.
The same applies to inspecting the temporary state of self.first().
Instead of this hassle, you can simply do:
let c = dbg!(fun(a)) + dbg!(fun(b));
let y = dbg!(self.first()).second();This modification is considerably smaller and disturbs flow while developing code to a lesser degree.
Additionally, by allowing the user to pass in multiple expressions and label them, the utility is further augmented.
Why not use the log crate?
While the log crate offers a lot of utility, it first has to be used with
extern crate log;. A logger then has to be set up before expressions can be
logged. It is therefore not suitable for introducing newcommers to the language.
Not useful as a crate
This RFC is for quick and dirty debugging and has to be in the standard library
to be useful. Why? Because while the utility provided by the dbg! macro is
necessary, it is unlikely that a developer, and that includes the author of
the RFC, would take a dependency on a crate that just provides the macro. The
hassle of constantly re-adding such a crate temporarily would also be greater
than just using println!("{:#?}", expr); in a majority of cases. But using
println! is still a paper cut. Furthermore, dbg! is unlikely to ever be a
crate that you can extern crate for in the Rust playground, so the macro
can't be used to quickly help users on #rust and other venues, which is one
of the goals of the macro.
Guide-level explanation
On debug builds
First, some preliminaries:
#[derive(Debug)] // The type of expr in dbg!(expr) must be Debug.
struct Point {
x: usize,
y: usize,
}With the following example, which most newcomers will benefit from:
fn main() {
dbg!(Point { x: 1, y: 2 });
let p = Point { x: 4, y: 5 };
dbg!(p);
}The program will print the points to STDERR as:
[DEBUGGING, src/main.rs:1]
=> Point{x: 1, y: 2,} = Point {
x: 1,
y: 2
}
[DEBUGGING, src/main.rs:4]
=> p = Point {
x: 4,
y: 5
}
You may also save the debugged value to a variable or use it in an expression since the debugging is pass-through. This is seen in the following example:
fn main() {
let x = dbg!(1 + 2);
let y = dbg!(x + 1) + dbg!(3);
dbg!(y);
}This prints the following to STDERR:
[DEBUGGING, src/main.rs:1]
=> 1 + 2 = 3
[DEBUGGING, src/main.rs:2]
=> x + 1 = 4
[DEBUGGING, src/main.rs:2]
=> 3 = 3
[DEBUGGING, src/main.rs:3]
=> y = 7
More expressions may be debugged in one invocation of the macro, as seen in the following example:
fn main() {
let a = 1;
let b = 2;
let _ : u32 = dbg!(a);
let _ : (u32, u32) = dbg!(a, b);
let _ : (u32, u32, u32) = dbg!(a, b, a + b);
let p = Point { x: 4, y: 5 };
let q = Point { x: 2, y: 1 };
let qp : (&Point, &Point) = dbg!(&p, &q);
}As seen in the example, the type of the expression dbg!(expr) is the type of
expr. For dbg!(expr1, expr2 [, .., exprN]) the type is that of the tuple
(expr1, expr2 [, .., exprN]).
The example above prints the following to STDERR:
[DEBUGGING, src/main.rs:3]
=> a = 1
[DEBUGGING, src/main.rs:4]
=> a = 1, b = 2
[DEBUGGING, src/main.rs:5]
=> a = 1, b = 2, a + b = 3
[DEBUGGING, src/main.rs:9]
=> &p = Point {
x: 4,
y: 5
}, &q = Point {
x: 2,
y: 1
}
Furthermore, instead of using stringify! on the expressions, which is done by
default, the user may provide labels, as done in:
fn main() {
let w = 1;
let h = 2;
dbg!("width" => w, "height" => h, "area" => w * h);
let p = Point { x: 4, y: 5 };
dbg!("first point" => &p, "same point" => &p);
}This allows the user to provide more descriptive names if necessary. With this
example, the following is printed to STDERR:
[DEBUGGING, src/main.rs:3]
=> "width" = 1, "height" = 2, "area" = 2
[DEBUGGING, src/main.rs:6]:
=> "first point" = Point {
x: 4,
y: 5
}, "same point" = Point {
x: 4,
y: 5
}
The ways of using the macro as illustrated in later (not the first) examples will mostly benefit existing Rust programmers.
Move semantics
It is important to note here that since the type Point is not Copy, it has
move semantics. Since dbg!(p) would involve moving p, using dbg!(p, p);
would involve moving the value twice, which Rust will not allow. Therefore,
a borrow to p is used in dbg!("first point" => &p, "second point" => &p);.
Compact mode:
Those developers who feel the source location header is overly verbose may
choose to opt-out by setting the environment variable RUST_DBG_COMPACT to
"1". This is a one-time setup cost the developer has to make for all current
and future Rust projects.
The effect of flipping this switch on is to print out the following instead for the two last examples in on-debug-builds:
[src/main.rs:3] a = 1
[src/main.rs:4] a = 1, b = 2
[src/main.rs:5] a = 1, b = 2, a + b = 3
[src/main.rs:9] &p = Point { x: 4, y: 5}, &q = Point { x: 2, y: 1 }
and:
[src/main.rs:3] "width" = 1, "height" = 2, "area" = 2
[src/main.rs:6] "first point" = Point { x: 4, y: 5 }, "same point" = Point { x: 4, y: 5 }
Dealing with panics
In the following example we have a panic in the second argument:
fn main() {
let (a, b) = (1, 2);
dbg!(a, panic!(), b);
}running this program will print the following to STDERR:
[DEBUGGING, src/main.rs:2]
=> a = 1, panic!() =
and to STDOUT:
thread 'main' panicked at 'explicit panic', src/main.rs:2:12
As can be seen from output, nothing is printed on RHS of panic!(). Why?
Because there's no value to present on RHS. Since a panic may cause necessary
side effects for subsequent arguments in dbg!(..) to not happen, the macro is
fail-fast on any panic in order to avoid cascading panics and unsafety.
On release builds
The same examples above will print nothing to STDERR and will instead simply
evaluate the expressions.
Calling dbg!() without any expressions
If you invoke the macro without providing any expressions as arguments, the
macro will treat this as if you passed the unit value () from which it
follow that the type will be the unit type.
Doing this can be useful if you want to ensure that a path is taken in some conditional flow. An example:
fn main() {
// assume we have: `some_important_conditional: bool` defined elsewhere.
if some_important_conditional {
dbg!();
}
}which may produce the following if some_important_conditional holds:
[DEBUGGING, src\lib.rs:4]
=> () = ()
Types which are not Debug
This feature will be available once specialization has been stabilized
and not before.
If you are writing generic code and want to debug the value of some expression
expr: T where T: Debug might hold, but you don't want to add this to the
bound, you may simply use dbg!(expr). This may be useful when you are deep
within some generic algorithm and the hassle of moving up the call stack is
in the way of productivity. Instead, if T: Debug holds implicitly, then the
debug impl will be used, otherwise we try to give as much helpful information
as we can.
This is solved via specialization. The expression is first wrapped in a
struct which has a Debug impl for all types which gives information about the
type of the expression. For types which are Debug, the Debug impl of the
struct is specialized to use the Debug implementation of the wrapped type.
With the following example:
fn main() {
struct X(usize);
let a = X(1);
dbg!(&a);
}the following is printed to STDERR:
[DEBUGGING, src/main.rs:3]
=> &a = [<unknown> of type &main::X is !Debug]
This tells you the type of &a, and that it is not Debug.
An example from the real world
You have been given a task to implement n!, the factorial function - a common
task for those learning programming, which you have decided to implement using a
simple recursive solution looking like this:
fn factorial(n: u32) -> u32 {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
fn main() {
factorial(4);
}Now, you, as a learner, want to see how the recursion expands, and use the
dbg! macro to your aid:
fn factorial(n: u32) -> u32 {
if dbg!(n <= 1) {
dbg!(1)
} else {
dbg!(n * factorial(n - 1))
}
}
fn main() {
dbg!(factorial(4));
}You run the program, and get back a print out which clearly shows the function recursively descending into a stack, before ascending and building the final value, and then it shows the final answer again.
[DEBUGGING, src/main.rs:1]
=> n <= 1 = false
[DEBUGGING, src/main.rs:1]
=> n <= 1 = false
[DEBUGGING, src/main.rs:1]
=> n <= 1 = false
[DEBUGGING, src/main.rs:1]
=> n <= 1 = true
[DEBUGGING, src/main.rs:2]
=> 1 = 1
[DEBUGGING, src/main.rs:4]
=> n * factorial(n - 1) = 2
[DEBUGGING, src/main.rs:4]
=> n * factorial(n - 1) = 6
[DEBUGGING, src/main.rs:4]
=> n * factorial(n - 1) = 24
[DEBUGGING, src/main.rs:9]
=> factorial(4) = 24
or prints, with RUST_DBG_COMPACT = 1:
[src/main.rs:1] n <= 1 = false
[src/main.rs:1] n <= 1 = false
[src/main.rs:1] n <= 1 = false
[src/main.rs:1] n <= 1 = true
[src/main.rs:2] 1 = 1
[src/main.rs:4] n * factorial(n - 1) = 2
[src/main.rs:4] n * factorial(n - 1) = 6
[src/main.rs:4] n * factorial(n - 1) = 24
[src/main.rs:9] factorial(4) = 24
But you prefer labels, since you think they are more informative, and use them instead:
fn factorial(n: u32) -> u32 {
if dbg!("are we at the base case?" => n <= 1) {
dbg!("base value" => 1)
} else {
dbg!("ascending with n * factorial(n - 1)" => n * factorial(n - 1))
}
}which prints:
[DEBUGGING, src/main.rs:1]
=> "are we at the base case?" = false
[DEBUGGING, src/main.rs:1]
=> "are we at the base case?" = false
[DEBUGGING, src/main.rs:1]
=> "are we at the base case?" = false
[DEBUGGING, src/main.rs:1]
=> "are we at the base case?" = true
[DEBUGGING, src/main.rs:2]
=> "base value" = 1
[DEBUGGING, src/main.rs:4]
=> "ascending with n * factorial(n - 1)" = 2
[DEBUGGING, src/main.rs:4]
=> "ascending with n * factorial(n - 1)" = 6
[DEBUGGING, src/main.rs:4]
=> "ascending with n * factorial(n - 1)" = 24
[DEBUGGING, src/main.rs:9]
=> factorial(4) = 24
or prints, with RUST_DBG_COMPACT = 1:
[src/main.rs:1] "are we at the base case?" = false
[src/main.rs:1] "are we at the base case?" = false
[src/main.rs:1] "are we at the base case?" = false
[src/main.rs:1] "are we at the base case?" = true
[src/main.rs:2] "base value" = 1
[src/main.rs:4] "ascending with n * factorial(n - 1)" = 2
[src/main.rs:4] "ascending with n * factorial(n - 1)" = 6
[src/main.rs:4] "ascending with n * factorial(n - 1)" = 24
[src/main.rs:9] factorial(4) = 24
Finally, you'd also like to see the value of n at each recursion step. Using
the multiple-arguments feature, you write, with very little effort, and run:
fn factorial(n: u32) -> u32 {
if dbg!(n, (n <= 1)).1 {
dbg!(n, 1).1
} else {
dbg!(n, n * factorial(n - 1)).1
}
}which outputs:
[DEBUGGING, src/main.rs:1]
=> n = 4, (n <= 1) = false
[DEBUGGING, src/main.rs:1]
=> n = 3, (n <= 1) = false
[DEBUGGING, src/main.rs:1]
=> n = 2, (n <= 1) = false
[DEBUGGING, src/main.rs:1]
=> n = 1, (n <= 1) = true
[DEBUGGING, src/main.rs:2]
=> n = 1, 1 = 1
[DEBUGGING, src/main.rs:4]
=> n = 2, n * factorial(n - 1) = 2
[DEBUGGING, src/main.rs:4]
=> n = 3, n * factorial(n - 1) = 6
[DEBUGGING, src/main.rs:4]
=> n = 4, n * factorial(n - 1) = 24
[DEBUGGING, src/main.rs:9]
=> factorial(4) = 24
or prints, with RUST_DBG_COMPACT = 1:
[src/main.rs:1] n = 4, (n <= 1) = false
[src/main.rs:1] n = 3, (n <= 1) = false
[src/main.rs:1] n = 2, (n <= 1) = false
[src/main.rs:1] n = 1, (n <= 1) = true
[src/main.rs:2] n = 1, 1 = 1
[src/main.rs:4] n = 2, n * factorial(n - 1) = 2
[src/main.rs:4] n = 3, n * factorial(n - 1) = 6
[src/main.rs:4] n = 4, n * factorial(n - 1) = 24
[src/main.rs:9] factorial(4) = 24
Reference-level explanation
NOTE: The exact output format is not meant to be stabilized even when/if the macro is stabilized.
The macro is called dbg and accepts either a comma-separated or
comma-terminated list of expr, or a list of label => expr which
is also separated or terminated with commas.
The terminated versions are defined as:
($($val: expr),+,) => { dbg!( $($val),+ ) };($($lab: expr => $val: expr),+,) => { dbg!( $($lab => $val),+ ) };
The separated versions accept the following:
($($val: expr),+)($($lab: expr => $val: expr),+)
Finally, the macro can be called as dbg!().
The macro only prints something if cfg!(debug_assertions) holds, meaning that
if the program is built as a release build, nothing will be printed, and the
result of using the macro on an expressions or expressions is simply the
expression itself or a flat tuple of the expressions themselves. In effect the
result is applying the identity function on the expression(s), but the call will
be inlined away such that the overhead is zero.
The type of dbg!(expressions)
"Applying" dbg on a list of expressions
[expr1, expr2 [, .., exprN] gives back an expression of the following type
and value:
-
List of size 0,
dbg!(): The type is the unit type(). -
List of size 1,
dbg!(expr): The type is the type ofexprand the value is the value ofexpr. -
Otherwise,
dbg!(expr1, expr2 [, expr3, .., exprN]): The type is the type of the tuple(expr1, expr2 [, expr3, .., exprN])which is the value.
Schematic/step-wise explanation (for debug builds)
-
The standard error is locked and wrapped in a buffered writer called
err. -
Assume
let p = option_env!("RUST_DBG_COMPACT").map_or(true, |s| s == "0");.
If p holds, the file name (given by file!()) and line number (line!()) is
included in the print out for increased utility when the macro is used in
non-trivial code. This is wrapped by [DEBUGGING, <location>]\n=> as in:
write!(&mut err, "[DEBUGGING, {}:{}]\n=> ", file!(), line!())If p does not hold, this instead prints:
write!(&mut err, "[{}:{}] ", file!(), line!())-
- For
()
- For
Defined as dbg!(()).
-
- For
($($val: expr),+)
- For
For each $val (the expression), the following is printed, comma separated:
The value of the expression is presented on the right hand side (RHS) of an
equality sign = while the result of stringify!(expr) is presented on the
left hand side (LHS). This is done so that the developer easily can see the
syntactic structure of the expression that evaluted to RHS.
In other words, the following:
write!(&mut err, "{} = {:#?}", stringify!($lab), tmp); is done.
If p holds, {:?} is used as the format instead of {:#?}.
-
- For
($($lab: expr => $val: expr),+):
- For
For each $lab => $val (the label and expression), the following is printed,
comma separated: The value of the expression is presented on RHS of an equality
sign = while the label is presented on LHS.
In other words, the following:
write!(&mut err, "{} = {:#?}", stringify!($lab), tmp); is done.
If p holds, {:?} is used as the format instead of {:#?}.
The label is also verified to be a string slice literal.
- Finally, a newline is printed, or two newlines in the case
pholds.
In both 3. and 4. if RHS should panic, then LHS = shall at least be printed.
Example implementation
The dbg! macro is semantically (with the notable detail that the helper macros
and any non-pub fns must be inlined in the actual implementation):
pub fn in_detailed_mode() -> bool {
option_env!("RUST_DBG_COMPACT").map_or(false, |s| s == "0")
}
macro_rules! verify_str_lit {
($($expr: expr),*) => {
$({
let _ = concat!($expr, "");
let _ : &'static str = $expr;
})*
};
}
macro_rules! w {
($err: ident, $($t: tt)+) => { write!(&mut $err, $($t)+).unwrap(); }
}
macro_rules! dbg_header {
($err: ident) => {
if in_detailed_mode() {
w!($err, "[DEBUGGING, {}:{}]\n=> ", file!(), line!())
} else {
w!($err, "[{}:{}] ", file!(), line!())
}
};
}
macro_rules! dbg_footer {
($err: ident) => {
if in_detailed_mode() { w!($err, "\n\n") } else { w!($err, "\n") }
};
}
macro_rules! dbg_expr {
($err: ident, $lab: expr => $val: expr) => {{
w!($err, "{} = ", $lab);
let _tmp = $val;
if in_detailed_mode() { w!($err, "{:#?}", _tmp) }
else { w!($err, "{:?}" , _tmp) }
_tmp
}}
}
macro_rules! dbg_core {
($labf: expr => $valf: expr $(, $lab: expr => $val: expr)*) => {{
#[allow(unreachable_code, unused_must_use, unused_parens)]
let _r = {
#[cfg(not(debug_assertions))] { ($valf $(, $val)*) }
#[cfg(debug_assertions)] {
use ::std::io::Write;
let stderr = ::std::io::stderr();
let mut err = ::std::io::BufWriter::new(stderr.lock());
dbg_header!(err);
let ret = (
dbg_expr!(err, $labf => $valf)
$(, {
w!(err, ", ");
dbg_expr!(err, $lab => $val)
} )*
);
dbg_footer!(err);
ret
}
};
_r
}};
}
#[macro_export]
macro_rules! dbg {
// Handle `dbg!()` <-- literal
() => {
dbg!( () );
};
// Handle trailing comma:
($($val: expr),+,) => {
dbg!( $($val),+ )
};
($($lab: expr => $val: expr),+,) => {
dbg!( $($lab => $val),+ )
};
// Without label, use source of $val:
($valf: expr $(, $val: expr)*) => {
dbg_core!($valf => $valf $(, $val => $val)*)
};
// With label:
($labf: expr => $valf: expr $(, $lab: expr => $val: expr)*) => {{
verify_str_lit!($labf $(, $lab)*);
dbg_core!($labf => $valf $(, $lab => $val)*)
}};
}Exact implementation
The exact implementation, which is authoritative on the semantics of this RFC, is given by:
#[macro_export]
macro_rules! dbg {
// Handle `dbg!()` <-- literal
() => {
dbg!( () );
};
// Handle trailing comma:
($($val: expr),+,) => {
dbg!( $($val),+ )
};
($($lab: expr => $val: expr),+,) => {
dbg!( $($lab => $val),+ )
};
// Without label, use source of $val:
($valf: expr $(, $val: expr)*) => {{
// in order: for panics, clarification on: dbg!(expr);, dbg!(expr)
#[allow(unreachable_code, unused_must_use, unused_parens)]
let _r = {
#[cfg(not(debug_assertions))] { ($valf $(, $val)*) }
#[cfg(debug_assertions)] {
// DEBUG: Lock STDERR in a buffered writer.
// Motivation:
// 1. to avoid needless re-locking of STDERR at every write(ln)!.
// 2. to ensure that the printed message is not interleaved, which
// would disturb the readability of the output, by other messages to
// STDERR.
use ::std::io::Write;
let stderr = ::std::io::stderr();
let mut err = ::std::io::BufWriter::new(stderr.lock());
// Are we in not in detailed mode (compact)?
// If so:
// + {:?} is used instead of {:#?},
// + Header is: [<location>]
let detailed = option_env!("RUST_DBG_COMPACT")
.map_or(true, |s| s == "0");
(if detailed {
write!(&mut err, "[DEBUGGING, {}:{}]\n=> ", file!(), line!())
} else {
write!(&mut err, "[{}:{}] ", file!(), line!())
}).unwrap();
// Foreach label and expression:
// 1. Evaluate each expression,
// 2. Print out $lab = value of expression
let _ret = (
{
// Print out $lab = :
write!(&mut err, "{} = ", stringify!($valf)).unwrap();
// Evaluate, tmp is value:
let _tmp = $valf;
// Won't get further if $val panics.
// Print out tmp:
(if detailed { write!(&mut err, "{:#?}", _tmp) }
else { write!(&mut err, "{:?}" , _tmp) }).unwrap();
// Yield tmp:
_tmp
}
$(, {
// Comma separator:
write!(&mut err, ", ").unwrap();
// Print out $lab = :
write!(&mut err, "{} = ", stringify!($val)).unwrap();
// Evaluate, tmp is value:
let _tmp = $val;
// Won't get further if $val panics.
// Print out tmp:
(if detailed { write!(&mut err, "{:#?}", _tmp) }
else { write!(&mut err, "{:?}" , _tmp) }).unwrap();
// Yield tmp:
_tmp
} )*
);
// Newline:
(if detailed { writeln!(&mut err, "\n") }
else { writeln!(&mut err, "") }).unwrap();
// Return the expression:
_ret
}
};
_r
}};
// With label:
($labf: expr => $valf: expr $(, $lab: expr => $val: expr)*) => {{
// in order: for panics, clarification on: dbg!(expr);, dbg!(expr)
#[allow(unreachable_code, unused_must_use, unused_parens)]
let _r = {
#[cfg(not(debug_assertions))] { ($valf $(, $val)*) }
#[cfg(debug_assertions)] {
// DEBUG: Lock STDERR in a buffered writer.
// Motivation:
// 1. to avoid needless re-locking of STDERR at every write(ln)!.
// 2. to ensure that the printed message is not interleaved, which
// would disturb the readability of the output, by other messages to
// STDERR.
use ::std::io::Write;
let stderr = ::std::io::stderr();
let mut err = ::std::io::BufWriter::new(stderr.lock());
// Are we in not in detailed mode (compact)?
// If so:
// + {:?} is used instead of {:#?},
// + Header is: [<location>]
let detailed = option_env!("RUST_DBG_COMPACT")
.map_or(true, |s| s == "0");
(if detailed {
write!(&mut err, "[DEBUGGING, {}:{}]\n=> ", file!(), line!())
} else {
write!(&mut err, "[{}:{}] ", file!(), line!())
}).unwrap();
// Foreach label and expression:
// 1. Evaluate each expression,
// 2. Print out $lab = value of expression
let _ret = (
{
// Enforce is_literal_string($lab):
let _ = concat!($labf, "");
let _ : &'static str = $labf;
// Print out $lab = :
write!(&mut err, "{} = ", stringify!($labf)).unwrap();
// Evaluate, tmp is value:
let _tmp = $valf;
// Won't get further if $val panics.
// Print out tmp:
(if detailed { write!(&mut err, "{:#?}", _tmp) }
else { write!(&mut err, "{:?}" , _tmp) }).unwrap();
// Yield tmp:
_tmp
}
$(, {
// Comma separator:
write!(&mut err, ", ").unwrap();
// Enforce is_literal_string($lab):
let _ = concat!($lab, "");
let _ : &'static str = $lab;
// Print out $lab = :
write!(&mut err, "{} = ", stringify!($lab)).unwrap();
// Evaluate, tmp is value:
let _tmp = $val;
// Won't get further if $val panics.
// Print out tmp:
(if detailed { write!(&mut err, "{:#?}", _tmp) }
else { write!(&mut err, "{:?}" , _tmp) }).unwrap();
// Yield tmp:
_tmp
} )*
);
// Newline:
(if detailed { writeln!(&mut err, "\n") }
else { writeln!(&mut err, "") }).unwrap();
// Return the expression:
_ret
}
};
_r
}};
}On release builds, this macro reduces to:
#[macro_export]
macro_rules! dbg {
// Handle `dbg!()` <-- literal
() => {
dbg!( () );
};
// Handle trailing comma:
($($val: expr),+,) => {
dbg!( $($val),+ )
};
($($lab: expr => $val: expr),+,) => {
dbg!( $($lab => $val),+ )
};
// Without label, use source of $val:
($valf: expr $(, $val: expr)*) => {{
// in order: for panics, clarification on: dbg!(expr);, dbg!(expr)
#[allow(unreachable_code, unused_must_use, unused_parens)]
let _r = {{ ($valf $(, $val)*) }};
_r
}};
// With label:
($labf: expr => $valf: expr $(, $lab: expr => $val: expr)*) => {{
// in order: for panics, clarification on: dbg!(expr);, dbg!(expr)
#[allow(unreachable_code, unused_must_use, unused_parens)]
let _r = {{ ($valf $(, $val)*) }};
_r
}};
}which further reduces to the following, which clearly shows that the invocation is nothing more than the identity on the tuple passed:
#[macro_export]
macro_rules! dbg {
() => { dbg!( () ); };
($($val: expr),+,) => { dbg!( $($val),+ ) };
($($lab: expr => $val: expr),+,) => { dbg!( $($lab => $val),+ ) };
($( $val: expr),+) => {{ ( $($val),* ) }};
($($lab: expr => $val: expr),+) => {{ ( $($val),* ) }};
}Specialization and non-Debug types.
This feature will be available once specialization has been stabilized
and not before. Once that happens, the feature will simply be added to the
macro without going through another RFC process.
The following is added inside the macro:
// All of this is internal to the macro and not exported:
struct WrapDebug<T>(T);
use std::fmt::{Debug, Formatter, Result};
impl<T> Debug for WrapDebug<T> {
default fn fmt(&self, f: &mut Formatter) -> Result {
use std::intrinsics::type_name;
write!(f, "[<unknown> of type {} is !Debug]",
unsafe { type_name::<T>() })
}
}
impl<T: Debug> Debug for WrapDebug<T> {
fn fmt(&self, f: &mut Formatter) -> Result { self.0.fmt(f) }
}This mechanism is inspired by version 0.1.2 of debugit.
Changes in the exact implementation:
The lines with let _tmp = $valf; and let _tmp = $val; are replaced with
let _tmp = WrapDebug($valf); and let _tmp = WrapDebug($val);. The lines
with _tmp are replaced with _tmp.0.
Drawbacks
It could be considered bloat, and println!("{:#?}", expr) might be
sufficiently ergonomic for both experienced rustaceans and newcomers.
Rationale and alternatives
The formatting is informative, but could be formatted in other ways depending
on what is valued. A more terse format could be used if stringify! or
file!(), line and column numbers is not deemed beneficial, which this RFC
argues it should. The RFC argues that the possibility of opting out to this
header via an env var strikes a good balance.
For more alternatives, questions and how they were resolved, see formerly unresolved for a more detailed Q & A.
The impact of not merging the RFC is that the papercut, if considered as such, remains.
Bikeshed: The name of the macro
Several names has been proposed for the macro. Some of the candidates were:
debug!, which was the original name. This was however already used by thelogcrate.d!, which was deemded to be too short to be informative and convey intent.dump!, which was confused with stack traces.show!, inspired by Haskell.showwas deemed less obvious thandbg!.peek!, which was also deemed less obvious.DEBUG!, which was deemed too screamy.qdbg!, which was deemed to hurt searchability and learnability since it isn't prefixed withd(ebug).
While it is unfortunate that debug! was unavailable, dbg! was deemed the
next best thing, which is why it was picked as the name of the macro.
How do we teach this?
Part of the motivation for this macro was to delay
the point at which aspiring rustaceans have to learn how formatting arguments
work in the language. For this to be effective, the macro should be taught prior
to teaching formatting arguments, but after teaching the user to write their
first "hello world" and other uses of println!("<string literal>") which does
not involve formatting arguments, which should first be taught when formatting
is actually interesting, and not as a part of printing out the value of an
expression.
Formerly unresolved questions
The following section gives a overview of certain design decisions taken during the RFC process and a detailed reasoning behind the decisions. These questions are ordered by when they were introduced.
1. Should the file!() be included?
Yes, since it would be otherwise difficult to tell where the output is coming from in a larger project with multiple files. It is not very useful on the playground, but that exception is acceptable.
2. Should the line number be included?
Yes, for a large file, it would also be difficult to locate the source of the output otherwise.
3. Should the column number be included?
No. It is more likely than not that no more than one dbg!(...) will occur.
If it does, it will most likely be when dealing with binary operators such as
with: dbg!(x) + dbg!(y) + dbg!(z), or with several arguments to a
function / method call. However, since the macro prints out stringify!(expr),
which in the case of the additions would result in:
x = <val>, y = <val>, z = <val>, the user can clearly see which expression on
the line that generated the value. The only exception to this is if the same
expression is used multiple times and crucically has side effects altering the
value between calls. This scenario is probably very uncommon. Furthermore, even
in this case, one can distinguish between the calls since one is first and the
second comes next, visually.
However, the column!() isn't very visually disturbing since it uses horizontal
screen real-estate but not vertical real-estate, which may still be a good reason
to keep it. Nonetheless, this argument is not sufficient to keep column!(),
wherefore this RFC will not include it.
4. Should the stringify!($val) be included?
Yes, it helps the user see the source of the value printed.
5. Should the macro be pass-through with respect to the expression?
In other words: should the value of applying the macro to the expression be the value of the expression?
Yes, the pass-through mechanism allows the macro to be less intrusive as discussed in the motivation.
6. Should the macro act as the identity function on release modes?
If the answer to this is yes, 5. must also be yes, i.e: 6. => 5.
Yes, since some users who develop programs, and not libraries, can leave
such dbg!(..) invocations in and push it to source control since it won't
affect debug builds of the program.
7. Should the macro accept expressions where: ![typeof(expr) : Debug]?
In other words, should expressions and values of non-Debug types be accepted
by the macro via specialization for Debug types?
Yes, and for two reasons:
- Avoiding the bound
T: Debugin generic code.
To see why, let's consider answering this question with a no. Imagine having some generic algorithm in your code:
fn process_items<I>(iter: I) where I: Iterator {
for elem in iter { /* .. */ }
}Now you want inspect the value of elem is, so you use dbg!(elem);:
fn process_items<I>(iter: I) where I: Iterator {
for elem in iter {
dbg!(elt);
// ..
}
}However, since doing dbg!(elem) requires that I::Item : Debug, you can't.
If you add the Debug bound, you'll also need to add it to any function, where
Item is held generic, which calls process_items, and transitively, you may
need add the bound to several function calls up the stack. Doing such a change
is not ergonomic as it may require you to even jump through different files.
With specialization, you can instead use the Debug trait implicitly.
- Some information is better than none.
Even if the type of expr does not satisfy the Debug bound, valuable
information can be displayed to the user. By using std::intrinsics::type_name
for non-Debug types, the user can at least know what the type of the
expression is, which is not nothing.
8. Should a trailing newline be added after each dbg!(exprs...)?
Yes. The result of answer in the negative would use the following format:
[DEBUGGING, src/main.rs:85]
=> a = 1
[DEBUGGING, src/main.rs:86]
=> a = 1, b = 2
[DEBUGGING, src/main.rs:87]
=> a = 1, b = 2, a + b = 3
instead of:
[DEBUGGING, src/main.rs:85]
=> a = 1
[DEBUGGING, src/main.rs:86]
=> a = 1, b = 2
[DEBUGGING, src/main.rs:87]
=> a = 1, b = 2, a + b = 3
The latter format, to many readers, look considerably more readable thanks to
visual association of a particular set of values with the DEBUGGING header and
make the users own println!(..) and eprintln!(..) calls stand out more due
to the absence of the header.
A counter argument to this is that users with IDEs or vertically short terminals
may have as little as 25% of vertical screen space allocated for the program's
output with the rest belonging to the actual code editor. To these users, lines
are too precious to waste in this manner since scrolling may require the use of
the mouse or switching of keyboard input focus.
However, it is more unlikely that a user will see the information they are looking for in a small window without scrolling. Here, searchability is aided by grouping which is visually pleasing to process.
This was resolved by having the env var RUST_DBG_COMPACT = 1 format the above
example as:
[src/main.rs:85] a = 1
[src/main.rs:86] a = 1, b = 2
[src/main.rs:87] a = 1, b = 2, a + b = 3
9. Should literals used in dbg!(lit); print out lit instead of lit = lit?
No. The left hand side of the equality adds no new information wherefore it
might be a redundant annoyance. On the other hand, it may give a sense of
symmetry with the non-literal forms such as a = 42. Keeping 5 = 5 is also
more consistent, wherefore that format will be used.
10. Should dbg!(expr); generate an "unused" warning?
No. In the case of:
fn main() {
let a = 42;
dbg!(a);
}the macro is used in "print" mode instead of "passhrough inspector" mode. Both are expected and supported ways of using this macro wherefore no warning should be raised.
11. Should STDOUT be used over STDERR as the output stream?
No. The messages printed using dbg!(..) are not usually errors, which is
one reason to use STDOUT instead. However, STDERR is often used as a second
channel for extra messages. This use of STDERR often occurs when STDOUT
carries some data which you can't mix with random messages.
If we consider a program such as ripgrep, where should hypothetical uses of
dbg!(..) print to in the case of rg some_word < input_file > matching_lines?
Should they end up on the terminal or in the file matching_lines? Clearly
the former is correct in this case.
One could say that this design is a lousy choice by the programmer and that debug messages should be logged to a file, but this macro must cater to "lousy" programmers who just want to debug quickly.
For these reasons, STDERR should be used.
12. Should std::panic::catch_unwind be used to handle panics?
No. If expr in dbg!("label" => expr) panics, should something be printed
on the RHS of "label" => as in: "label" => <panic> ? If so, should all
panics be caught such that:
fn main() {
let (a, b) = (1, 2);
dbg!(a, panic!(), b);
}prints (using RUST_DBG_COMPACT = 1) to STDERR:
[src/main.rs:2] a = 1, panic!() = <panic>, b = 2and to STDOUT:
thread 'main' panicked at 'explicit panic', src/main.rs:2:12
and a single panic "re-thrown" after everything has been printed?
This is a bad idea for two reasons:
-
If
foo()panics in(foo(), bar()), thenbar()is not evaluated. The user should be able to expect similar semantics fromdbg!(foo(), bar())to(foo(), bar()). -
Given
(foo(), bar()), a panic infoo()entails that the postconditions offoo()aren't guaranteed. Ifbar()relies on these postconditions offoo()in its preconditions, then since the postconditions do not always hold,bar()must not be evaluated.
Now that the second question has been resolved in the negative, we can resolve
the first one. Since "label" => combined with a message in STDOUT as seen
in dealing with panics is sufficiently clear, the overhead of catch_unwind
is for very little gain, wherefore this question is too answered in the negative.
Unresolved questions
The format used by the macro should be resolved prior to merging. There are currently no unresolved questions.