Skip to content

Commit 00ab077

Browse files
CentrilSimonSapin
authored andcommitted
simpler dbg! - improve formatting & expand: GLE, Rationale, Prior art (#1)
1 parent 06abf7c commit 00ab077

1 file changed

Lines changed: 188 additions & 29 deletions

File tree

text/0000-dbg-macro.md

Lines changed: 188 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
- Feature Name: dbg_macro
1+
- Feature Name: `dbg_macro`
22
- Start Date: 2018-03-13
33
- RFC PR:
44
- Rust Issue:
@@ -42,7 +42,7 @@ This RFC improves some aspects:
4242
[guide-level-explanation]: #guide-level-explanation
4343

4444
To inspect the value of a given expression at run-time,
45-
it can be wrapped in the `dbg!` macro to print the value to `stderr`,
45+
it can be wrapped in the `dbg!` macro to print the value to `STDERR`,
4646
along with its source location and source code:
4747

4848
```rust
@@ -54,13 +54,55 @@ fn foo(n: usize) {
5454

5555
foo(3)
5656
```
57+
58+
This prints the following to `STDERR`:
59+
5760
```
5861
[example.rs:2] n.checked_sub(4) = None
5962
```
6063

61-
This requires the type of the expression to implement the `std::fmt::Debug` trait.
62-
The macro moves the value (takes ownership of it, unless its type implements `Copy`)
63-
and returns it unchanged.
64+
Another example is `factorial` which we can debug like so:
65+
66+
```rust
67+
fn factorial(n: u32) -> u32 {
68+
if dbg!(n <= 1) {
69+
dbg!(1)
70+
} else {
71+
dbg!(n * factorial(n - 1))
72+
}
73+
}
74+
75+
fn main() {
76+
dbg!(factorial(4));
77+
}
78+
```
79+
80+
Running this program, in the playground, will print the following to `STDERR`:
81+
82+
```
83+
[src/main.rs:1] n <= 1 = false
84+
[src/main.rs:1] n <= 1 = false
85+
[src/main.rs:1] n <= 1 = false
86+
[src/main.rs:1] n <= 1 = true
87+
[src/main.rs:2] 1 = 1
88+
[src/main.rs:4] n * factorial(n - 1) = 2
89+
[src/main.rs:4] n * factorial(n - 1) = 6
90+
[src/main.rs:4] n * factorial(n - 1) = 24
91+
[src/main.rs:9] factorial(4) = 24
92+
```
93+
94+
Using `dbg!` requires type of the expression to implement the `std::fmt::Debug`
95+
trait.
96+
97+
## Move semantics
98+
99+
The `dbg!(x)` macro moves the value `x` and takes ownership of it,
100+
unless the type of `x` implements `Copy`, and returns `x` unchanged.
101+
If you want to retain ownership of the value,
102+
you can instead borrow `x` with `dbg!(&x)`.
103+
104+
## Unstable output format
105+
64106
The exact output printed by this macro should not be relied upon and is subject to future changes.
65107

66108

@@ -85,10 +127,9 @@ macro_rules! dbg {
85127
}
86128
}
87129
}
88-
89130
```
90131

91-
The use of `match` over let is similar to the implementation of `assert_eq!`.
132+
The use of `match` over `let` is similar to the implementation of `assert_eq!`.
92133
It [affects the lifetimes of temporaries](
93134
https://stackoverflow.com/questions/48732263/why-is-rusts-assert-eq-implemented-using-a-match#comment84465322_48732525).
94135

@@ -98,40 +139,158 @@ https://stackoverflow.com/questions/48732263/why-is-rusts-assert-eq-implemented-
98139
Adding to the prelude should be done carefully.
99140
However a library can always define another macro with the same name and shadow this one.
100141

101-
# Alternatives
142+
# Rationale and alternatives
102143
[alternatives]: #alternatives
103144

104-
- See [RFC 2173](https://github.com/rust-lang/rfcs/pull/2173) and discussion there.
145+
[RFC 2173] and provides an a more complex alternative that offers more control but is also more complex.
146+
This RFC was designed with the goal of being a simpler and thus better fit for the standard library.
147+
148+
## Alternative: tweaking formatting
149+
150+
Any detail of the formatting can be tweaked. For example, `{:#?}` or `{:?}`?
151+
152+
## A simple macro without any control over output
153+
154+
This RFC does not offer users control over the exact output being printed.
155+
This is because a use of this macro is intended to be run a small number of times before being removed.
156+
If more control is desired, for example logging in an app shipped to end users,
157+
other options such as `println!` or the `log` crate remain available.
158+
159+
## Accepting a single expression instead of many
160+
161+
If the macro accepts more than one expression (returning a tuple),
162+
there is a question of what to do with a single expression.
163+
Returning a one-value tuple `($expr,)` is probably unexpected,
164+
but *not* doing so creates a discontinuty in the macro's behavior as things are added.
165+
With only one expression accepted,
166+
users can still pass a tuple expression or call the macro multiple times.
167+
168+
## Including `file!()` in the output
169+
170+
In a large project with multiple files,
171+
it becomes quite difficult to tell what the origin of the output is.
172+
Including `file!()` is therefore quite helpful in debugging.
173+
However, it is not very useful on the [playground](https://play.rust-lang.org),
174+
but that exception is acceptable.
175+
176+
## Including the line number
105177

106-
- This RFC does not offer users control over the exact output being printed.
107-
This is because a use of this macro is intended to be run a small number of times
108-
before being removed.
109-
If more control is desired (for example logging in an app shipped to end users),
110-
other options like `println!` or the `log` crate remain available.
178+
The argument is analogous to that for `file!()`. For a large file,
179+
it would also be difficult to locate the source of the output without `line!()`.
111180

112-
- If the macro accepts more than one expression (returning a tuple),
113-
there is a question of what to do with a single expression.
114-
Returning a one-value tuple `($expr,)` is probably unexpected,
115-
but *not* doing so creates a discontinuty in the macro’s behavior as things are added.
116-
With only one expression accepted, users can still pass a tuple expression
117-
or call the macro multiple times.
181+
## Excluding the column number
118182

119-
- Printing could be disabled when `cfg!(debug_assertions)` is false to reduce runtime cost
120-
in release build.
121-
However this cost is not relevant if uses of `dbg!` are removed before shipping
122-
to any form of production (where the `log` crate might be better suited)
123-
and deemed less important than the ability to easily investigate bugs
124-
that only occur with optimizations.
125-
(Which [do happen](https://github.com/servo/servo/issues/19519)
126-
and can be a pain to debug.)
183+
Most likely, only one `dbg!(expr)` call will occur per line.
184+
The remaining cases will likely occur when dealing with binary operators such as with:
185+
`dbg!(x) + dbg!(y) + dbg!(z)`, or with several arguments to a function / method call.
186+
However, since the macro prints out `stringify!(expr)`,
187+
the user can clearly see which expression on the line that generated the value.
188+
The only exception to this is if the same expression is used multiple times and
189+
crucically has side effects altering the value between calls.
190+
This scenario is probably uncommon.
191+
Furthermore, even in this case, one can visually distinguish between the calls
192+
since one is first and the second comes next.
127193

128-
- Any detail of the formatting can be tweaked. For example, `{:#?}` or `{:?}`?
194+
Another reason to exclude `column!()` is that we want to keep the macro simple, and thus,
195+
we only want to keep the essential parts that help debugging most.
196+
197+
However, the `column!()` isn't very visually disturbing
198+
since it uses horizontal screen real-estate but not vertical real-estate,
199+
which may still be a good reason to keep it.
200+
Nonetheless, this argument is not sufficient to keep `column!()`,
201+
wherefore **this RFC will not include it**.
202+
203+
## Including `stringify!(expr)`
204+
205+
As discussed in the rationale regarding `column!()`,
206+
`stringify!(expr)` improves the legibility of similar looking expressions.
207+
208+
Another major motivation is that with many outputs,
209+
or without all of the source code in short term memory,
210+
it can become hard to associate the printed output with the logic as you wrote it.
211+
With `stringify!`, you can easily see how the left-hand side reduces to the right-hand side.
212+
This makes it easier to reason about the trace of your program and why things happened as they did.
213+
The ability to trace effectively can greatly improve the ability to debug with ease and speed.
214+
215+
## Returning the value that was given
216+
217+
One goal of the macro is to intrude and disturb as little as possible in the workflow of the user.
218+
The macro should fit the user, not the other way around.
219+
Returning the value that was given, i.e: that `dbg!(expr) == expr`
220+
and `typeof(expr) == typeof(dbg!(expr))` allows just that.
221+
222+
To see how writing flow is preserved, consider starting off with:
223+
224+
```rust
225+
let c = fun(a) + fun(b);
226+
let y = self.first().second();
227+
```
228+
229+
Now, you want to inspect what `fun(a)` and `fun(b)` evaluates to.
230+
But you would like to avoid going through the hassle of:
231+
232+
1. saving `fun(a)` and `fun(b)` to a variable
233+
2. printing out the variable
234+
3. using it in the expression as `let c = fa + fb;`.
235+
236+
The same logic applies to inspecting the temporary state of `self.first()`.
237+
Instead of the hassle, you can simply do:
238+
239+
```rust
240+
let c = dbg!(fun(a)) + dbg!(fun(b));
241+
let y = dbg!(self.first()).second();
242+
```
243+
244+
This modification is considerably smaller and disturbs flow while debugging code to a lesser degree.
245+
246+
## Keeping output when `cfg!(debug_assertions)` is disabled
247+
248+
When `cfg!(debug_assertions)` is false,
249+
printing could be disabled to reduce runtime cost in release builds.
250+
However this cost is not relevant if uses of `dbg!` are removed before shipping to production,
251+
where crates such as `log` may be better suited,
252+
and deemed less important than the ability to easily investigate bugs that only occur with optimizations.
253+
These kinds of bugs [do happen](https://github.com/servo/servo/issues/19519) and can be a pain to debug.
254+
255+
## `STDERR` should be used over `STDOUT` as the output stream
256+
257+
The messages printed using `dbg!` are not usually errors,
258+
which is one reason to use `STDOUT` instead.
259+
However, `STDERR` is often used as a second channel for extra messages.
260+
This use of `STDERR` often occurs when `STDOUT` carries some data which you can't mix with random messages.
261+
262+
If we consider a program such as `ripgrep`,
263+
where should hypothetical uses of `dbg!` print to in the case of `rg some_word < input_file > matching_lines`?
264+
Should they end up on the terminal or in the file `matching_lines`?
265+
Clearly the former is correct in this case.
266+
267+
One could say that this design is a lousy choice by the programmer
268+
and that debug messages should be logged to a file,
269+
but this macro must cater to "lousy" programmers who just want to debug quickly.
270+
271+
## Outputting `lit = lit` for `dbg!(lit);` instead of `lit`
272+
273+
The left hand side of the equality adds no new information wherefore it might be a redundant annoyance.
274+
On the other hand, it may give a sense of symmetry with the non-literal forms such as `a = 42`.
275+
Keeping `5 = 5` is also more consistent.
276+
In either case, since the macro is intentionally simple,
277+
there is little room for tweaks such as removing `lit = `.
278+
For these reasons, and especially the last one, the output format `lit = lit` is used.
129279

130280
# Prior art
131281
[prior-art]: #prior-art
132282

133283
Many languages have a construct that can be as terse as `print foo`.
134284

285+
Some examples are:
286+
+ [Haskell](http://hackage.haskell.org/package/base-4.10.1.0/docs/Prelude.html#v:print)
287+
+ [python](https://docs.python.org/2/library/pprint.html>)
288+
+ [PHP](http://php.net/manual/en/function.print-r.php)
289+
290+
[`traceShowId`]: http://hackage.haskell.org/package/base-4.10.1.0/docs/Debug-Trace.html#v:traceShowId
291+
292+
The specific idea to return back the input `expr` in `dbg!(expr)` was inspired by [`traceShowId`] in Haskell.
293+
135294
# Unresolved questions
136295
[unresolved]: #unresolved-questions
137296

0 commit comments

Comments
 (0)