My tiny but useful trace debugging code... Any suggestions from more experienced D users?
WraithGlade
wraithglade at protonmail.com
Thu Jun 26 22:51:06 UTC 2025
A while back (perhaps my first D forum post) I asked about
implementing an equivalent of other languages' very useful debug
printing macros such as Julia's `@show` or Nim's `dump` or Rust's
`dbg!`. Someone on the forum here very helpfully showed me how to
do it via D's `mixin` feature. I have since then refined my own
tiny module for doing so, with a few more features. Here is the
code for it, in my `trace.d` module:
```D
public import std.conv;
/// `trace_prefix` provides the common
`module_name.function_name-line_number:\t` prefix that is added
to each of the tracing functions in this module. The other
functions are likely to be more useful, unless a new function is
being created that reuses the same trace prefix format.
string trace_prefix(const bool should_mark = false) {
return `__FUNCTION__ ~ "-" ~ to!string(__LINE__) ~ ` ~
(should_mark ? `"*"` : `""`) ~ `~ ": \t"`;
}
/// `trace_message` outputs any arbitrary string you pass it, but
prefixed by an indicator of what module, function, and line
number the trace_message is located on, thus aiding print-based
debugging.
string trace_message(string message) {
return `writeln(` ~ trace_prefix() ~ ` ~ q"<` ~ message ~ `>");`;
}
/// `show` eases print debugging by printing both an expression
as a string and its corresponding evaluated value. It is intended
to be used via `mixin`, such as in `mixin(show("1 + 2")`, which
will print something like `module.function-line: 1 + 2 == 3`. The
`module.function-line:` prefix helps distinguish debug printing
from normal output and also helps to track where output is coming
from.
///
/// Printing both an expression's code and the expression's value
makes print debugging easier to keep track of accurately
(especially when multiple print statements are used, which would
otherwise be more easily confused with each other) and less
redundant (since `show` being a macro prevents the user from
needing to write the expression twice).
///
/// However, assertions (via `assert`) and unit tests (via
`unittest`) are often a better way to debug and maintain code
than any form of print debugging, because (1) assertions are
enforced automatically instead of requiring manual inspection and
(2) reading through print statements consumes a lot of time in
aggregate whereas assertions are instantaneous and hence much
more expedient when well applicable. Nonetheless, print debugging
is still very useful and intuitive, especially when you don't
know what a value is at all.
string show(string expr) {
return `writefln(` ~ trace_prefix() ~ `~ "%s == %s", q"<` ~
expr ~ `>", ` ~ expr ~ `);`;
}
/// `trace_scope` returns a mixin string that if mixed in at the
beginning of a scope inserts code that logs when the current
scope starts and ends, which may be useful for debugging
potentially, since it informs of when you are actually inside
that scope or not, which may or may not be when you think. Use it
by writing `mixin(trace_scope);`, placing it at the top of the
scope you intend to trace.
string trace_scope(string scope_name) {
return
`writeln(` ~ trace_prefix() ~ ` ~ q"<` ~ scope_name ~ `>" ~ "
entered");` ~
`scope(exit) writeln(` ~ trace_prefix(true) ~ ` ~ q"<` ~
scope_name ~ `>" ~ " exited");`;
}
```
`show` is for examining the values of things in a clean and
organized way (the most common use case).
`trace_message` is for any arbitrary string message tagged with
the module, function, and line.
`trace_scope` is for easier printing of when any specific scope
starts and ends.
All are useful.
You use them by writing `mixin(trace_function_name(...))` and
such, since they all return strings and you have to use `mixin`
to apply arbitrary macros in D. For example, try `mixin(show(1 +
2))` or `mixin(trace_scope(__FUNCTION__))` or similar.
(Unimportantly, you can see that I'm using snake_case, though
I've debated on and off using D's camelCase but I've always liked
snake_case and PascalCase and other conventions more than
camelCase in languages.)
Anyway, I really like how useful this is and basically this kind
of print debugging feature should be built in to *all modern
languages* in my opinion (and indeed is in all of the most
popular ones in some form and for good reason).
It works fine, but I was wondering what suggestions the D
community may have regarding both (1) whether or not better
pre-built functionality for this exists in the D standard library
and community libraries already and (2) how the above code could
be improved to be more readable. I tried to use string `format`
but it didn't even work right when I tried it.
How would *you* refactor it if you were trying to make it more
polished and readable?
D's string-based `mixin`s that support arbitrary string code are
useful, but as you can see keeping the quotations delimiters and
concatenation operations straight above is not very easy and it
took a long time to get these tracing functions working relative
to their conceptual simplicity because strings were so subtle and
easy to confuse oneself with the quote delimiters.
I also figured that other beginners could benefit from the above.
Such tracing functions provide a far better way of print
debugging than just raw print statements, although assertions and
unit tests are better in ideal cases because they don't require
manual examination, but print debugging is useful for when you
don't know what the state of something actually is at all of
course.
What are your thoughts, if any?
(PS: Thanks for reading.)
More information about the Digitalmars-d-learn
mailing list