Dbg!(): to optionally allow formatting

The dbg macro lacks a possibility to format the output. For instance, it is not possible to print an u64 as hexadecimal. Therefore, I was thinking about below macro that, when given more than one expression, does allow formatting:

#[macro_export]
macro_rules! dbg {
    ($expr:expr) => (dbg!("{:#?}", $expr));
    ($fmt:expr, $expr:expr$(, $opt:expr)*) => {
        match $expr {
            expr => {
                eprintln!(concat!("[{}:{}] {} = ", $fmt), file!(), line!(), stringify!($expr), &expr$(, $opt)*);
                expr
            }
        }
    }
}

With a single argument this behaves like current dbg, In case there are 2 or more, the first argument is a string literal format string, the second is the evaluated, optional remaining arguments can be provided as specified in the format string.

example:

let t = dbg!(2 + 1);

// instead of:
let _: u64 = dbg!((1 << 63) | (t << 8));

// this gives a nicer representation:
let _: u64 = dbg!("0x{:x}", (1 << 63) | (t << 8));
    
// also possible:
let _: u64 = dbg!("0x{:x}, with t={}", (1 << 63) | (t << 8), t);

Centril kindly disagreed, but suggested me to discuss this further here:

This is inconsistent with the originally-proposed mult-parameter formulation, where you could do let (x, y) = dbg!(i*n, j+1);

Note also that the first proposal for dbg (below) had far more options and configurability, but wasn’t accepted. It’s possible that for the same reasons, extensions to dbg also are likely not to be accepted.

Thank you scottmcm, I believe you meant this?, I agree Centrils original proposal is much more advanced than mine and enables almost everything I’d like.

However, unless I am mistaken, I believe this implementation does not allow either to print a value as hexadecimal? In my opinion the dbg!() macro’s role should primarily be to debug-annotate variables or code branches, and formatting helps the former in my opinion.

The current dbg!() implementation lacks formatting which makes it less convenient as debug output for bit masks, floating points and other structures. The original dbg implementation also missed formatting options, was not accepted, therefore it is not a valid argument per se to state that my implementation is not in compliance with it. There is room for improvement.

The multi-parameter in the original proposed dbg!() seems unnecessary to me because the evaluated argument can be passed as tuple.

let (i, j) = dbg!((3, 5));
let (k, l) = dbg!("{:?}", (3, 5));

// and if you really want to, you could still evaluate the 2nd argument of a tuple:

if dbg!("{:?}", (3, _j == 5)).1 {
    dbg!("J was 5");
}

I’ve made a few changes in my implementation (below) that allows using dbg!() without argument (just prints file:line) or with a string literal to print a notice, these use cases seem intuitive to me as well.

As mentioned dbg!() with a single expression behaves as current dbg!(). there are certain variables that are printed more nicely with formatting, this I try to show in my main() function.

dbg_macro_playground

#[macro_export]
macro_rules! dbg {
    () => (eprint!("[{}:{}]\n", file!(), line!()));
    ($l:literal) => (dbg!("{}", $l));
    ($expr:expr) => (dbg!(concat!(stringify!($expr), " = {:#?}"), $expr));
    ($fmt:expr, $expr:expr$(, $opt:expr)*) => {
    	match $expr {
            expr => {
                eprint!("[{}:{}] ", file!(), line!());
                eprint!(concat!($fmt, "\n"), &expr$(, $opt)*);
                expr
            }
        }
    }
}

So, I would like to see an optional format string to allow for things like hex (and note that you can write {:#x} rather than 0x{:x}). But I do not think we should accept additional arguments.

@josh Could you say more? Why don’t you think printing multiple arguments would be a good idea?

The arguments should match the return value.

They would do that:

let (a, b, c): (A, B, C);

dbg!(a): A
    (a): A

dbg!(a,): (A,)
    (a,): (A,)

dbg!(a, b): (A, B)
    (a, b): (A, B)

dbg!(a, b, c): (A, B, C)
    (a, b, c): (A, B, C)

There is perfect symmetry here between (a, b, c) and dbg!(a, b, c).

Moreover, it is useful, and expected.

It was discussed; [1], [2], [3], [4], [5].

I think multiple arguments are a more common use case than needing to specify formatting is. Also, from what I can tell, this would stabilize the format to a degree, which was explicitly a non-goal of dbg! (and specified in the documentation).

Since the debug output format is explicitly unstable and performance is not critical here (it’ll take longer as a human to look at the output), what about doing things like detecting large numbers of zero nibbles below the high bit and automatically displaying in hex (instead or additionally)?

Pending specifics of the algorithm (e.g. not making it trigger a change when expected and not otherwise..) and how this detection is injected at compile time, it sounds at least intriguing.

Maybe a sort of compromis is possible by concat!ng the first argument if it is a literal? I hope this could even be in compliance with Centril’s proposal as the dbg!() now does return both.

#[macro_export]
macro_rules! dbg {
    ($expr:expr) => {
        match $expr {
            expr => {
                eprintln!("[{}:{}] {} = {:#?}", file!(), line!(), stringify!($expr), &expr);
                expr
            }
        }
    };
    ($l:literal, $expr:expr) => {
        match $expr {
            expr => {
                eprintln!(concat!("[", file!(), ":", line!(), "] ",
                    stringify!($expr), " = ", $l), &expr);
                ($l, expr)
            }
        }
    }
}

then we could do

let (fmt, w) = dbg!("{}", 3);

// but this currently does not compile
let (a, b) = dbg!(1, 3);

The error in the 2nd is: formatting specifier missing. This may require evaluation of the literal: to see whether it can be concatenated, or not.

I have to ask: what’s the motivation for all of this extra complexity? And why can’t you create an ad hoc macro that adds hex or binary formatting? It could also just be in a crate on crates.io.

I hardly ever need to deal with a long string of bytes represented in hex or binary. In my experience such values are one of the following:

  • Really just a scalar, and decimal formatting is ok.
  • Actually a struct – which should derive Debug to expose its components.
  • An array of scalars.
  • Very unusual. Use custom formatting.

Not according to the original post in this thread, which seemed to ignore additional arguments and not return them.

While using it, I found the stable dbg did not suffice my purpose, I created an ad hoc macro, and thought it was still simple, and was wondering why dbg did not support a little more. With my last proposal there will be some complexity in the code, but I would hope the evaluation of a literal will be optimized away. Fine, if you all disagree, then I’ll just create my macro locally or create a crate for this.

I see; that would indeed be strange -- the original dbg! implementation supports multiple arguments and returns all of them as a tuple tho... :slight_smile:

So what I would do instead is to simply add another macro dbgf!(format, expr) for cases where the formatting is needed; it's not a whole lot of complexity imo and lets us have both several arguments in dbg! as well as custom formatting in dbgf! (if it turns out custom formatting is actually a common need, which I'm not sure about along the lines of @repax's argument).

So I think we tested this with crates.io: Rust Package Registry and it turned out that barely anyone used it for more than a year but once dbg! got added to std it was suddenly a big deal in the 1.32 release and talked a lot about in various places. (reddit, twitter, user forums, it was a big item in the blog post, and someone even made a python port of it... GitHub - tylerwince/pydbg: Python implementation of the Rust `dbg` macro). The key lesson is that "it could also just be a crate" doesn't work in this case because it's too much hassle to import when you need it, but you still need it.

I'd accept a PR to the dbg crate if you want to add a dbgf! macro :slight_smile:

1 Like

dbgf! sounds like a fine approach to me.

Wow, that's... nuanced, to say the least. A trailing comma in the macro invocation would change the resulting type? I find that really surprising given Rust's history of accepting trailing commas in parameter lists to no affect.

It might be surprising, but also most likely unimportant / irrelevant. For the distinction to matter, you must depend on the resulting value being used and not thrown away e.g. by writing let x = dbg!(y,); In that unlikely event, you will get a type error that makes it clear why this happened. What is more likely to happen is you write dbg!(y,); in which case it does not matter whether Y or (Y,) is the resulting type because it is thrown away.

But they do have an effect in parenthesis -- (0,) and (0) are not the same thing. So this is consistent with tuples, which makes sense IMO.

2 Likes