Skip to content

Commit 153188b

Browse files
authored
Merge pull request #2361 from SimonSapin/dbg-macro
Simpler alternative dbg!() macro
2 parents 1c51119 + 9a9488d commit 153188b

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed

text/2361-dbg-macro.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
- Feature Name: `dbg_macro`
2+
- Start Date: 2018-03-13
3+
- RFC PR: [rust-lang/rfcs#2361](https://github.com/rust-lang/rfcs/pull/2361)
4+
- Rust Issue: [rust-lang/rust#54306](https://github.com/rust-lang/rust/issues/54306)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Add a `dbg!($expr)` macro to the prelude (so that it doesn’t need to be imported)
10+
that prints its argument with some metadata (source code location and stringification)
11+
before returning it.
12+
13+
This is a simpler and more opinionated counter-proposal
14+
to [RFC 2173](https://github.com/rust-lang/rfcs/pull/2173).
15+
16+
17+
# Motivation
18+
[motivation]: #motivation
19+
20+
Sometimes a debugger may not have enough Rust-specific support to introspect some data
21+
(such as calling a Rust method), or it may not be convenient to use or available at all.
22+
`printf` debugging” is possible in today’s Rust with:
23+
24+
```rust
25+
println!("{:?}", expr);
26+
```
27+
28+
This RFC improves some aspects:
29+
30+
* The `"{:?}",` part of this line is boilerplate that’s not trivial to remember
31+
or even type correctly.
32+
* If the expression to be inspected is part of a larger expression,
33+
it either needs to be duplicated (which may add side-effects or computation cost)
34+
or pulled into a `let` binding which adds to the boilerplate.
35+
* When more than one expression is printed at different places of the same program,
36+
and the formatting itself (for example a plain integer)
37+
doesn’t indicate what value is being printed,
38+
some distinguishing information may need to be added.
39+
For example: `println!("foo = {:?}", x.foo());`
40+
41+
# Guide-level explanation
42+
[guide-level-explanation]: #guide-level-explanation
43+
44+
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`,
46+
along with its source location and source code:
47+
48+
```rust
49+
fn foo(n: usize) {
50+
if let Some(_) = dbg!(n.checked_sub(4)) {
51+
/*…*/
52+
}
53+
}
54+
55+
foo(3)
56+
```
57+
58+
This prints the following to `STDERR`:
59+
60+
```
61+
[example.rs:2] n.checked_sub(4) = None
62+
```
63+
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+
106+
The exact output printed by this macro should not be relied upon and is subject to future changes.
107+
108+
109+
# Reference-level explanation
110+
[reference-level-explanation]: #reference-level-explanation
111+
112+
The macro below is added to `src/libstd/macros.rs`,
113+
with a doc-comment based on the [Guide-level explanation][guide-level-explanation] of this RFC.
114+
115+
```rust
116+
#[macro_export]
117+
macro_rules! dbg {
118+
($expr:expr) => {
119+
match $expr {
120+
expr => {
121+
// The exact formatting here is not stable and may change in the future.
122+
eprintln!("[{}:{}] {} = {:#?}", file!(), line!(), stringify!($expr), &expr);
123+
expr
124+
}
125+
}
126+
}
127+
}
128+
```
129+
130+
The use of `match` over `let` is similar to the implementation of `assert_eq!`.
131+
It [affects the lifetimes of temporaries](
132+
https://stackoverflow.com/questions/48732263/why-is-rusts-assert-eq-implemented-using-a-match#comment84465322_48732525).
133+
134+
# Drawbacks
135+
[drawbacks]: #drawbacks
136+
137+
Adding to the prelude should be done carefully.
138+
However a library can always define another macro with the same name and shadow this one.
139+
140+
# Rationale and alternatives
141+
[alternatives]: #alternatives
142+
143+
[RFC 2173] and provides an a more complex alternative that offers more control but is also more complex.
144+
This RFC was designed with the goal of being a simpler and thus better fit for the standard library.
145+
146+
## Alternative: tweaking formatting
147+
148+
Any detail of the formatting can be tweaked. For example, `{:#?}` or `{:?}`?
149+
150+
## A simple macro without any control over output
151+
152+
This RFC does not offer users control over the exact output being printed.
153+
This is because a use of this macro is intended to be run a small number of times before being removed.
154+
If more control is desired, for example logging in an app shipped to end users,
155+
other options such as `println!` or the `log` crate remain available.
156+
157+
## Accepting a single expression instead of many
158+
159+
If the macro accepts more than one expression (returning a tuple),
160+
there is a question of what to do with a single expression.
161+
Returning a one-value tuple `($expr,)` is probably unexpected,
162+
but *not* doing so creates a discontinuty in the macro's behavior as things are added.
163+
With only one expression accepted,
164+
users can still pass a tuple expression or call the macro multiple times.
165+
166+
## Including `file!()` in the output
167+
168+
In a large project with multiple files,
169+
it becomes quite difficult to tell what the origin of the output is.
170+
Including `file!()` is therefore quite helpful in debugging.
171+
However, it is not very useful on the [playground](https://play.rust-lang.org),
172+
but that exception is acceptable.
173+
174+
## Including the line number
175+
176+
The argument is analogous to that for `file!()`. For a large file,
177+
it would also be difficult to locate the source of the output without `line!()`.
178+
179+
## Excluding the column number
180+
181+
Most likely, only one `dbg!(expr)` call will occur per line.
182+
The remaining cases will likely occur when dealing with binary operators such as with:
183+
`dbg!(x) + dbg!(y) + dbg!(z)`, or with several arguments to a function / method call.
184+
However, since the macro prints out `stringify!(expr)`,
185+
the user can clearly see which expression on the line that generated the value.
186+
The only exception to this is if the same expression is used multiple times and
187+
crucically has side effects altering the value between calls.
188+
This scenario is probably uncommon.
189+
Furthermore, even in this case, one can visually distinguish between the calls
190+
since one is first and the second comes next.
191+
192+
Another reason to exclude `column!()` is that we want to keep the macro simple, and thus,
193+
we only want to keep the essential parts that help debugging most.
194+
195+
However, the `column!()` isn't very visually disturbing
196+
since it uses horizontal screen real-estate but not vertical real-estate,
197+
which may still be a good reason to keep it.
198+
Nonetheless, this argument is not sufficient to keep `column!()`,
199+
wherefore **this RFC will not include it**.
200+
201+
## Including `stringify!(expr)`
202+
203+
As discussed in the rationale regarding `column!()`,
204+
`stringify!(expr)` improves the legibility of similar looking expressions.
205+
206+
Another major motivation is that with many outputs,
207+
or without all of the source code in short term memory,
208+
it can become hard to associate the printed output with the logic as you wrote it.
209+
With `stringify!`, you can easily see how the left-hand side reduces to the right-hand side.
210+
This makes it easier to reason about the trace of your program and why things happened as they did.
211+
The ability to trace effectively can greatly improve the ability to debug with ease and speed.
212+
213+
## Returning the value that was given
214+
215+
One goal of the macro is to intrude and disturb as little as possible in the workflow of the user.
216+
The macro should fit the user, not the other way around.
217+
Returning the value that was given, i.e: that `dbg!(expr) == expr`
218+
and `typeof(expr) == typeof(dbg!(expr))` allows just that.
219+
220+
To see how writing flow is preserved, consider starting off with:
221+
222+
```rust
223+
let c = fun(a) + fun(b);
224+
let y = self.first().second();
225+
```
226+
227+
Now, you want to inspect what `fun(a)` and `fun(b)` evaluates to.
228+
But you would like to avoid going through the hassle of:
229+
230+
1. saving `fun(a)` and `fun(b)` to a variable
231+
2. printing out the variable
232+
3. using it in the expression as `let c = fa + fb;`.
233+
234+
The same logic applies to inspecting the temporary state of `self.first()`.
235+
Instead of the hassle, you can simply do:
236+
237+
```rust
238+
let c = dbg!(fun(a)) + dbg!(fun(b));
239+
let y = dbg!(self.first()).second();
240+
```
241+
242+
This modification is considerably smaller and disturbs flow while debugging code to a lesser degree.
243+
244+
## Keeping output when `cfg!(debug_assertions)` is disabled
245+
246+
When `cfg!(debug_assertions)` is false,
247+
printing could be disabled to reduce runtime cost in release builds.
248+
However this cost is not relevant if uses of `dbg!` are removed before shipping to production,
249+
where crates such as `log` may be better suited,
250+
and deemed less important than the ability to easily investigate bugs that only occur with optimizations.
251+
These kinds of bugs [do happen](https://github.com/servo/servo/issues/19519) and can be a pain to debug.
252+
253+
## `STDERR` should be used over `STDOUT` as the output stream
254+
255+
The messages printed using `dbg!` are not usually errors,
256+
which is one reason to use `STDOUT` instead.
257+
However, `STDERR` is often used as a second channel for extra messages.
258+
This use of `STDERR` often occurs when `STDOUT` carries some data which you can't mix with random messages.
259+
260+
If we consider a program such as `ripgrep`,
261+
where should hypothetical uses of `dbg!` print to in the case of `rg some_word < input_file > matching_lines`?
262+
Should they end up on the terminal or in the file `matching_lines`?
263+
Clearly the former is correct in this case.
264+
265+
## Outputting `lit = lit` for `dbg!(lit);` instead of `lit`
266+
267+
The left hand side of the equality adds no new information wherefore it might be a redundant annoyance.
268+
On the other hand, it may give a sense of symmetry with the non-literal forms such as `a = 42`.
269+
Keeping `5 = 5` is also more consistent.
270+
In either case, since the macro is intentionally simple,
271+
there is little room for tweaks such as removing `lit = `.
272+
For these reasons, and especially the last one, the output format `lit = lit` is used.
273+
274+
# Prior art
275+
[prior-art]: #prior-art
276+
277+
Many languages have a construct that can be as terse as `print foo`.
278+
279+
Some examples are:
280+
+ [Haskell](http://hackage.haskell.org/package/base-4.10.1.0/docs/Prelude.html#v:print)
281+
+ [python](https://docs.python.org/2/library/pprint.html)
282+
+ [PHP](http://php.net/manual/en/function.print-r.php)
283+
284+
[`traceShowId`]: http://hackage.haskell.org/package/base-4.10.1.0/docs/Debug-Trace.html#v:traceShowId
285+
286+
The specific idea to return back the input `expr` in `dbg!(expr)` was inspired by [`traceShowId`] in Haskell.
287+
288+
# Unresolved questions
289+
[unresolved]: #unresolved-questions
290+
291+
Unbounded bikeshedding.

0 commit comments

Comments
 (0)