Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simpler alternative dbg!() macro #2361

Merged
merged 7 commits into from
Sep 17, 2018
Merged

Conversation

SimonSapin
Copy link
Contributor

@SimonSapin SimonSapin commented Mar 13, 2018

This is a simpler and more opinionated counter-proposal to RFC 2173.

Rendered
Tracking issue

This is a simpler and more opinionated counter-proposal to [RFC 2173](rust-lang#2173).
@Centril Centril added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Mar 13, 2018
@Centril
Copy link
Contributor

Centril commented Mar 13, 2018

Things I'd like to see included from RFC 2173:

  1. The reference and the guide level explanations should note that the output format is unspecified and should not be relied upon.
  2. An explanation that calling dbg!(val) moves val (unless typeof(val): Copy).
  3. Notes/plans for/on specialization. If we don't include this in the macro right now, I'd like to see the RFC propose that we include WrapDebug once stabilization land without going through another RFC process again.
  4. Some of the design rationale of the old RFC still applies to the new one, see: https://github.com/Centril/rfcs/blob/rfc/quick-debug-macro/text/0000-quick-debug-macro.md#formerly-unresolved-questions
    (I'll work up a PR against your branch for this soon-ish).

EDIT: Regarding point 3. I am now satisfied that we can backwards-compatibly add support for !Debug types after shipping the initial version of the macro, so this point is resolved in my eyes.

@ghost
Copy link

ghost commented Mar 13, 2018

An explanation that calling dbg!(val) moves val (unless typeof(val): Copy).

does it? how come? that seems like a bad idea™ , unless that's also true for (e)println!(), which seems to not be the case(due to magic) ?

@Centril
Copy link
Contributor

Centril commented Mar 13, 2018

does it? how come? that seems like a bad idea™ , unless that's also true for (e)println!(), which seems to not be the case(due to magic) ?

Yes, it does. It is not a bad idea, but instead good design. Not having move semantics would mean that you would have to evaluate the expression passed twice, and thus also any side effects. If you don't want to move p you can always use a reference as in: dbg!(&p) and then you move the reference instead, which you can do any number of times.

@ghost
Copy link

ghost commented Mar 13, 2018

Ah, my bad: I forgot dbg!(p) is returning p which is unlike (e)println!.
I stand corrected :)

(deleted)what I wanted to write before realizing that

Not having move semantics would mean that you would have to evaluate the expression passed twice, and thus also any side effects. If you don't want to move p you can always use a reference as in: dbg!(&p) and then you move the reference instead

I guess I don't see what is gained by moving it? And why doesn't it happen for println! too?
If you use &p instead of p doesn't the same evaluate the expression passed twice happen?

https://play.rust-lang.org/?gist=178efc0e3b3c9420dde9df730c15d3cf&version=stable

@repax
Copy link

repax commented Mar 13, 2018

I think this macro should be conditionally compiled, just like debug assertions are. It's rather important to distinguish debugging info from other kinds of logging. If dbg!() messages were to be always included then that would be very discouraging because you cannot always know, when writing some code, where and how that piece of code will be used.

@SimonSapin
Copy link
Contributor Author

@Centril I’ve added some wording for the exact formatting and for ownership. This RFC makes no plan for specialization. I don’t see the point of printing a line that says nothing about the value instead of failing to compile with !Debug types.

@repax I disagree, for reasons given in the Alternative section of the RFC.

@repax
Copy link

repax commented Mar 13, 2018

@SimonSapin, debug logging for bugs that only manifest during optimisation is a fringe case. Designing the dbg!() macro for this scenario seems ill-advised to me. Not only are these cases extremly rare, naming the macro dbg!() like this suggest that it serves as a debugging/development aid -- not being present in release builds.

Following the principle of least astonishment is not a bad decision. In this case, debug assertions and debug messages are both assumed to belong to debugging. If you write a routine that include dbg!() messages -- especially if the code base is large or is a multi-person project, or if it is part of a library -- you cannot know all the situations and context in which the debug macro is going to be used. It might be used in a loop indirectly though a third-party caller, or it might leak internal information that is harmless during development but potentially disastrous in a deployed release build.

We could not permit dbg!() for debugging purposes in our products knowing that it might unintentionally slip through to deployment/production, but would require more rules for review and deployment. In this case dbg!() is better suited in a separate library on crates.io. I think eprintln can do most of the things that dbg!() aims to do.

@SimonSapin
Copy link
Contributor Author

I’ve always found the phrase “principle of least astonishment” very unconvincing. It sounds fancy, but it does nothing to justify what is found to be “astonishing”, by whom, or why.

I my mind dbg!(x) is a shorter version of println!("{:?}", x). If you add it to your code while debugging and forget to remove it before pushing a git commit, you’re already doing something wrong IMO. The situation where the performance cost would matter should not occur in the first place.

@repax
Copy link

repax commented Mar 13, 2018

It's easy to make mistakes -- that's all. You can, and should, leave debug assertions when committing, but not debug messages? You still haven't motivated why the fringe case of optimisations-only bugs have precedence to regularity in the design.

One aim of good design should be to minimise the risk of errors, not just push the responsibility to someone else, or brush off good heuristics as fancy sounding. At least, that's something I believe.

Perhaps this macro is better suited in a library, or with a different name?

@SimonSapin
Copy link
Contributor Author

SimonSapin commented Mar 13, 2018

And you haven’t argued how disabling printing in release mode is more "regular" (or what that means).

As to having this macro in a library, that makes it effectively worthless in my opinion. As already discussed in #2173, the whole point is having something that easier to use than println!("{:?}", foo), and println!("{:?}", foo) is easier than adding a dependency.

@nielsle
Copy link

nielsle commented Mar 14, 2018

Related idea. How about changing the syntax for 'debug!', so that the following three statements have the same effect.

debug!("Values x = {:?} y = {:?}", x, y);
debug!("Values x = {:?}", x; y);
debug!("Values"; x, y);

Note arguments after the semicolon are formatted differently.

@Centril
Copy link
Contributor

Centril commented Mar 14, 2018

@nielsle

Related idea. How about changing the syntax for 'debug!', so that the following three statements have the same effect.

Entirely orthogonal if you are talking about the log crate as that requires doing extern crate log; and other setup as well. The dbg! macro is intended for quick and dirty debugging and optimized for just starting out & the playground.

@Centril
Copy link
Contributor

Centril commented Mar 14, 2018

@SimonSapin

I don’t see the point of printing a line that says nothing about the value instead of failing to compile with !Debug types.

The point is quite important and is discussed at:

The value of specialization and fallback here is that you can start debugging anywhere in generic code without having a Debug bound infecting everything upwards. This is especially beneficial if you are deep within a generic call stack.

@rpjohnst
Copy link

Bikeshed: name it trace! instead of dbg!? Or something else less abbreviated if that's too Haskell-y.

@Centril
Copy link
Contributor

Centril commented Mar 14, 2018

@rpjohnst The bikeshed so far: https://github.com/Centril/rfcs/blob/rfc/quick-debug-macro/text/0000-quick-debug-macro.md#bikeshed-the-name-of-the-macro

As a Haskeller I'm almost compelled to favor a Haskell based name, but.. I think this will cause users to think that the stack trace will somehow be printed out if you call trace!(..), so I think dbg!(..) more clearly associates this with debugging and the Debug trait while it doesn't associate with stack traces, which is semantically accurate.

@aturon
Copy link
Member

aturon commented Mar 16, 2018

I just want to register my support for this proposal; I think it strikes a good balance between packaging up the most impactful bits of the original RFC into a much more streamlined macro. This feels like a good foundation that we can gradually extend as time goes on.

@SimonSapin
Copy link
Contributor Author

Regarding specialization and !Debug types, I hadn’t considered the case of generic code. However I think that plans for this do not need to be in this RFC: it can be added compatibly as it would only make more programs compile. And keeping this RFC minimal might help its chances to be accepted.

@kornelski
Copy link
Contributor

I like this proposal.

I'd like to voice my support for automatically disabling these in release mode. It wouldn't be that helpful for investigation of heisenbugs that happen only in release mode, since mere presence of this statement changes generated code and timing (due to locks on stdout).

On many occasions I have had forgotten to remove my debug println!s, so I welcome a version that saves me this embarrassment.

@clarfonthey
Copy link
Contributor

Idea: assert! uses dbg! internally. This would probably result in a common dbg_plain! macro where dbg! includes the location in the code and dbg_plain! does not. dbg! then becomes { println!("[{}:{}] {}", file!(), line!(), dbg_plain!($val)); $val }

@Centril
Copy link
Contributor

Centril commented Apr 2, 2018

@clarcharr That definition of dbg! is not semantically equivalent; you are evaluating $val twice, and therefore repeating any side effects. I think preserving move semantics here is quite important for a language like Rust so I'm strongly in favor of not changing this.

@rust-lang/libs It seems the only outstanding issue remaining is whether to disable outputs on not(debug_assertions) or not. The original macro did this and I'm personally in favor of that as well for reasons mentioned by @repax. We will need to resolve this before adding this to the standard library cause macros don't respect #[unstable] (tho technically, the macro makes no promises as to the output format, which could imply that we don't promise to output anything at all). My summary of the pros and cons in this issue:

Pros of only keeping output when debug_assertions is enabled:

  • Hisenbugs are fringe cases.
  • The name dbg! somewhat implies a relation to debug_assertions.
  • Forgetting to remove dbg! in VCS could be embarrassing, so removing the output could help devs save face.
  • The debug! macro in the log crate does this.

Cons:

  • STDERR output is not very harmful even if it manifests in a release build.
  • Some heisenbugs could be caught if the output was kept.

@ssokolow
Copy link

ssokolow commented Apr 2, 2018

Intuition also suggests that dbg! should be conditional on debug_assertions if the user has experience with the debug_assert! macro.

Conversely, if the user encounters dbg! first and it's not conditional on debug_assertions, their first impression on seeing debug_assert! might be to wonder if assert! outputs to STDOUT while debug_assert! outputs to STDERR but both are unconditional. (Or whether they're controlled by some kind of built-in log-level-like filtering mechanism.)

@kornelski
Copy link
Contributor

kornelski commented Apr 2, 2018

dbg! is not an assertion. There could be a new option debug_print. What matters is that this option is disabled by cargo --release (unless there's [profile.release] debug_print = true)

@ssokolow
Copy link

ssokolow commented Apr 2, 2018

dbg! is not an assertion.

Good point.

There could be a new option debug_print.

Even better, as I see it. Being able to enable the assertions and prints separately could be useful.

What matters is that this option is disabled by cargo --release.

That was intended to be the thrust of my point but it didn't occur to me to be clear about that. It seems I'm still not fully recovered from that all-nighter I pulled a couple of days ago.

@Centril
Copy link
Contributor

Centril commented Apr 8, 2018

There could be a new option debug_print. What matters is that this option is disabled by cargo --release (unless there's [profile.release] debug_print = true)

I think that the RFC as written is forward compatible with that; for initial simplicity I'd like to not that go route for now; but we can extend the macro in the future if we so wish.

@eddyb
Copy link
Member

eddyb commented Apr 10, 2018

dbg! is not an assertion.

There is some precedent that the log crate uses cfg(debug_assertions) for its debug!.

@Centril Centril merged commit 153188b into rust-lang:master Sep 17, 2018
@Centril
Copy link
Contributor

Centril commented Sep 17, 2018

Huzzah! This RFC has been merged!

Tracking issue: rust-lang/rust#54306


Took a bit more time than I wanted to get to this; but I am working on an implementation right now.

@swfsql
Copy link

swfsql commented Sep 20, 2018

I use a small similar code I called "log_here". It has macros that functions as "newtypes" for format/error/warn/info/etc, that adds the [file:line] info as prefix as well.

So the usage is format_here/error_here/warn_here/and so on..
The benefit is that format_here may be used in various places, such as failure context addition (or in expects for results), etc.

kennytm added a commit to kennytm/rust that referenced this pull request Sep 21, 2018
…apin

Implement the dbg!(..) macro

Implements the `dbg!(..)` macro due to rust-lang#54306.
cc rust-lang/rfcs#2361

r? @alexcrichton
@gilescope
Copy link

Many a time I add println!("{:#?}", x) in the middle of the code, only to be told by the compiler that I need to borrow x as I am using it further down. I haven't read a good argument for why we move rather than borrow by default? I would have thought borrowing x would be more ergonomic (read less annoying)... (sorry - I'm coming to the party late here)

@eddyb
Copy link
Member

eddyb commented Sep 23, 2018

@gilescope println!("{:#?}", x) doesn't move x, it borrows x, so I'm not sure what you mean.

@gilescope
Copy link

Hmm, will need to check that, but the RFC seemed to be suggesting that it was going to move not borrow x for dbg!(x):

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

@Centril
Copy link
Contributor

Centril commented Sep 23, 2018

The macro takes ownership of the input passed because it then returns it, so you can do dbg!(a) + b which will have the same result as a + b but will also print a.

@mark-i-m
Copy link
Member

Hmm... come to think of it, that seems like it may have the unfortunate side effect of being used like this:

fn foo(arg: Bar) {
    dbg!(arg);
    baz(&arg); // error: `arg` was moved above!
}

Perhaps we need some sort of unused result lint?

@Centril
Copy link
Contributor

Centril commented Sep 23, 2018

@mark-i-m maybe we can use #[must_use] somehow? but it doesn't seem to work :(

(you can write baz(&dbg!(arg)) and it works...)

PS: let's move the discussion to the tracking issue :) rust-lang/rust#54306

@SimonSapin
Copy link
Contributor Author

I think this should not be #[must_use]. Printing a value (which might be Copy) in a statement by itself without using the return is an important use case IMO.

If moving / requiring a manual dbg!(&foo) borrow turns out annoying enough for !Copy types, I’d rather have the macro borrow implicitly and potentially give up on returning the value.

@Centril
Copy link
Contributor

Centril commented Sep 23, 2018

@SimonSapin

OK; I agree with you on #[must_use]...

I’d rather have the macro borrow implicitly and potentially give up on returning the value.

but I don't agree with this. This was an important design choice in allowing the user to write dbg!(a) + b and such things without having to create intermediate let bindings... Without it, the expressive power and usefulness diminishes a lot.

@mark-i-m
Copy link
Member

I completely agree with @Centril here. The ability to use this in the middle of an expression is the key feature IMHO.

@Centril
Copy link
Contributor

Centril commented Sep 23, 2018

I don't think it will be necessary anyways to do such a change because the macro already tells the user what the problem is clearly:

19 |     baz(&arg);
   |          ^^^ value used here after move

The main problem is that the error is "incorrectly spanned":

4  |             tmp => {
   |             --- value moved here

You would rather want to say:

L |    dbg!(arg);
  |         --- value moved here; try borrowing with `&arg` instead?

This seems like a general problem with macros which we should find a general solution to rather than institute an ergonomics regression...

bors added a commit to rust-lang/rust that referenced this pull request Sep 25, 2018
Implement the dbg!(..) macro

Implements the `dbg!(..)` macro due to #54306.
cc rust-lang/rfcs#2361

r? @alexcrichton
@eddyb
Copy link
Member

eddyb commented Sep 26, 2018

@gilescope Ah, my bad, I thought you meant println! moves, not just dbg!, in #2361 (comment).

@lachlansneff
Copy link

Doesn't this evaluate expr twice?

@mark-i-m
Copy link
Member

If you use it in an expression, I would imagine it just evaluates it once, right?

foo(dbg!(<fmt>, <expr>));

I imagine it would expand to something like

foo({
    let value = <expr>;
    println!(<fmt>, value);
    value
});

@Centril
Copy link
Contributor

Centril commented Sep 28, 2018

The macro is defined as:

macro_rules! dbg {
    ($val:expr) => {
        match $val {
            tmp => {
                eprintln!("[{}:{}] {} = {:#?}",
                    file!(), line!(), stringify!($val), &tmp);
                tmp
            }
        }
    }
}

which means that dbg!(expr) evaluates expr once.

@lachlansneff
Copy link

Whoops, my mistake. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-debugging Debugging related proposals & ideas A-macros-libstd Proposals that introduce new standard library macros disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet