- Feature Name: quick_debug_macro
- Start Date: 2017-10-13
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Adds a macro dbg!([expr1 , expr2, .., exprN])
for quick and dirty Debug
ing
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.
The motivation to add this new dbg!
macro is two-fold.
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.
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.
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.
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.
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.
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);
.
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 }
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.
The same examples above will print nothing to STDERR
and will instead simply
evaluate the 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]
=> () = ()
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
.
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
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.
"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 ofexpr
and 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.
-
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
p
holds.
In both 3. and 4. if RHS should panic, then LHS = shall at least be printed.
The dbg!
macro is semantically (with the notable detail that the helper macros
and any non-pub
fn
s 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)*)
}};
}
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),* ) }};
}
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
.
It could be considered bloat, and println!("{:#?}", expr)
might be
sufficiently ergonomic for both experienced rustaceans and newcomers.
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.
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 thelog
crate.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.show
was 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.
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.
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.
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.
Yes, for a large file, it would also be difficult to locate the source of the output otherwise.
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.
Yes, it helps the user see the source of the value printed.
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.
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.
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: Debug
in 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.
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
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.
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.
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.
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 = 2
and 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.
The format used by the macro should be resolved prior to merging. There are currently no unresolved questions.