|
| 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