Skip to content

Commit

Permalink
simpler dbg! - improve formatting & expand: GLE, Rationale, Prior art (
Browse files Browse the repository at this point in the history
  • Loading branch information
Centril authored and SimonSapin committed Sep 5, 2018
1 parent 06abf7c commit 00ab077
Showing 1 changed file with 188 additions and 29 deletions.
217 changes: 188 additions & 29 deletions text/0000-dbg-macro.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
- Feature Name: dbg_macro
- Feature Name: `dbg_macro`
- Start Date: 2018-03-13
- RFC PR:
- Rust Issue:
Expand Down Expand Up @@ -42,7 +42,7 @@ This RFC improves some aspects:
[guide-level-explanation]: #guide-level-explanation

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

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

foo(3)
```

This prints the following to `STDERR`:

```
[example.rs:2] n.checked_sub(4) = None
```

This requires the type of the expression to implement the `std::fmt::Debug` trait.
The macro moves the value (takes ownership of it, unless its type implements `Copy`)
and returns it unchanged.
Another example is `factorial` which we can debug like so:

```rust
fn factorial(n: u32) -> u32 {
if dbg!(n <= 1) {
dbg!(1)
} else {
dbg!(n * factorial(n - 1))
}
}

fn main() {
dbg!(factorial(4));
}
```

Running this program, in the playground, will print the following to `STDERR`:

```
[src/main.rs:1] n <= 1 = false
[src/main.rs:1] n <= 1 = false
[src/main.rs:1] n <= 1 = false
[src/main.rs:1] n <= 1 = true
[src/main.rs:2] 1 = 1
[src/main.rs:4] n * factorial(n - 1) = 2
[src/main.rs:4] n * factorial(n - 1) = 6
[src/main.rs:4] n * factorial(n - 1) = 24
[src/main.rs:9] factorial(4) = 24
```

Using `dbg!` requires type of the expression to implement the `std::fmt::Debug`
trait.

## Move semantics

The `dbg!(x)` macro moves the value `x` and takes ownership of it,
unless the type of `x` implements `Copy`, and returns `x` unchanged.
If you want to retain ownership of the value,
you can instead borrow `x` with `dbg!(&x)`.

## Unstable output format

The exact output printed by this macro should not be relied upon and is subject to future changes.


Expand All @@ -85,10 +127,9 @@ macro_rules! dbg {
}
}
}

```

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

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

# Alternatives
# Rationale and alternatives
[alternatives]: #alternatives

- See [RFC 2173](https://github.com/rust-lang/rfcs/pull/2173) and discussion there.
[RFC 2173] and provides an a more complex alternative that offers more control but is also more complex.
This RFC was designed with the goal of being a simpler and thus better fit for the standard library.

## Alternative: tweaking formatting

Any detail of the formatting can be tweaked. For example, `{:#?}` or `{:?}`?

## A simple macro without any control over output

This RFC does not offer users control over the exact output being printed.
This is because a use of this macro is intended to be run a small number of times before being removed.
If more control is desired, for example logging in an app shipped to end users,
other options such as `println!` or the `log` crate remain available.

## Accepting a single expression instead of many

If the macro accepts more than one expression (returning a tuple),
there is a question of what to do with a single expression.
Returning a one-value tuple `($expr,)` is probably unexpected,
but *not* doing so creates a discontinuty in the macro's behavior as things are added.
With only one expression accepted,
users can still pass a tuple expression or call the macro multiple times.

## Including `file!()` in the output

In a large project with multiple files,
it becomes quite difficult to tell what the origin of the output is.
Including `file!()` is therefore quite helpful in debugging.
However, it is not very useful on the [playground](https://play.rust-lang.org),
but that exception is acceptable.

## Including the line number

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

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

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

- Any detail of the formatting can be tweaked. For example, `{:#?}` or `{:?}`?
Another reason to exclude `column!()` is that we want to keep the macro simple, and thus,
we only want to keep the essential parts that help debugging most.

However, the `column!()` isn't very visually disturbing
since it uses horizontal screen real-estate but not vertical real-estate,
which may still be a good reason to keep it.
Nonetheless, this argument is not sufficient to keep `column!()`,
wherefore **this RFC will not include it**.

## Including `stringify!(expr)`

As discussed in the rationale regarding `column!()`,
`stringify!(expr)` improves the legibility of similar looking expressions.

Another major motivation is that with many outputs,
or without all of the source code in short term memory,
it can become hard to associate the printed output with the logic as you wrote it.
With `stringify!`, you can easily see how the left-hand side reduces to the right-hand side.
This makes it easier to reason about the trace of your program and why things happened as they did.
The ability to trace effectively can greatly improve the ability to debug with ease and speed.

## Returning the value that was given

One goal of the macro is to intrude and disturb as little as possible in the workflow of the user.
The macro should fit the user, not the other way around.
Returning the value that was given, i.e: that `dbg!(expr) == expr`
and `typeof(expr) == typeof(dbg!(expr))` allows just that.

To see how writing flow is preserved, consider starting off with:

```rust
let c = fun(a) + fun(b);
let y = self.first().second();
```

Now, you want to inspect what `fun(a)` and `fun(b)` evaluates to.
But you would like to avoid going through the hassle of:

1. saving `fun(a)` and `fun(b)` to a variable
2. printing out the variable
3. using it in the expression as `let c = fa + fb;`.

The same logic applies to inspecting the temporary state of `self.first()`.
Instead of the hassle, you can simply do:

```rust
let c = dbg!(fun(a)) + dbg!(fun(b));
let y = dbg!(self.first()).second();
```

This modification is considerably smaller and disturbs flow while debugging code to a lesser degree.

## Keeping output when `cfg!(debug_assertions)` is disabled

When `cfg!(debug_assertions)` is false,
printing could be disabled to reduce runtime cost in release builds.
However this cost is not relevant if uses of `dbg!` are removed before shipping to production,
where crates such as `log` may be better suited,
and deemed less important than the ability to easily investigate bugs that only occur with optimizations.
These kinds of bugs [do happen](https://github.com/servo/servo/issues/19519) and can be a pain to debug.

## `STDERR` should be used over `STDOUT` as the output stream

The messages printed using `dbg!` are not usually errors,
which is one reason to use `STDOUT` instead.
However, `STDERR` is often used as a second channel for extra messages.
This use of `STDERR` often occurs when `STDOUT` carries some data which you can't mix with random messages.

If we consider a program such as `ripgrep`,
where should hypothetical uses of `dbg!` print to in the case of `rg some_word < input_file > matching_lines`?
Should they end up on the terminal or in the file `matching_lines`?
Clearly the former is correct in this case.

One could say that this design is a lousy choice by the programmer
and that debug messages should be logged to a file,
but this macro must cater to "lousy" programmers who just want to debug quickly.

## Outputting `lit = lit` for `dbg!(lit);` instead of `lit`

The left hand side of the equality adds no new information wherefore it might be a redundant annoyance.
On the other hand, it may give a sense of symmetry with the non-literal forms such as `a = 42`.
Keeping `5 = 5` is also more consistent.
In either case, since the macro is intentionally simple,
there is little room for tweaks such as removing `lit = `.
For these reasons, and especially the last one, the output format `lit = lit` is used.

# Prior art
[prior-art]: #prior-art

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

Some examples are:
+ [Haskell](http://hackage.haskell.org/package/base-4.10.1.0/docs/Prelude.html#v:print)
+ [python](https://docs.python.org/2/library/pprint.html>)
+ [PHP](http://php.net/manual/en/function.print-r.php)

[`traceShowId`]: http://hackage.haskell.org/package/base-4.10.1.0/docs/Debug-Trace.html#v:traceShowId

The specific idea to return back the input `expr` in `dbg!(expr)` was inspired by [`traceShowId`] in Haskell.

# Unresolved questions
[unresolved]: #unresolved-questions

Expand Down

0 comments on commit 00ab077

Please sign in to comment.