# 3.4 Print debugging: eprintln! and dbg!
> Debugging with output: stderr versus stdout, the dbg! macro's file-and-line reports, and a worked bug hunt.
Source: https://learnrust.net/chapter-3/print-debugging/
The oldest debugging technique is making the program narrate itself: print the values, print the progress, read the story. It's unglamorous, it works everywhere, and Rust ships a macro that does it unusually well. But first, a distinction that makes all debug output better.
## Two output streams
Your program actually has *two* output channels. **Standard output** (stdout) is where `println!` writes: the program's real product. **Standard error** (stderr) is a second stream for status and complaints, and `eprintln!` (note the `e`) writes there, with identical syntax.
On a plain terminal they look interleaved, so the difference seems cosmetic. It stops being cosmetic the moment output gets redirected. `cargo run > results.txt` sends *stdout* into the file; stderr still appears on screen. Debug messages on stdout would silently contaminate `results.txt`; debug messages on stderr stay visible and keep the product clean. (You've already seen the principle in action: panic reports and compiler messages go to stderr for exactly this reason.)
{% callout(kind="best", title="Best practice") %}
Program output goes to `println!`. Debugging chatter, warnings, and progress notes go to `eprintln!` (or `dbg!`, below). Adopt the split now, while it's free; untangling the two streams later, in a program whose output other programs consume, is a chore with your name on it.
{% end %}
## dbg!, the purpose-built one
Hand-rolled debug prints have a tedium tax: you type `eprintln!("x is {x}")`, then wonder *which* `x is 7` you're looking at when three of them print. The **dbg!** macro pays the tax for you. Give it an expression and it prints the file, the line, the expression's own source text, and its value, to stderr:
```rust
fn main() {
let price = 100;
let tax = price / 10;
dbg!(price + tax);
}
```
```
[src/main.rs:4:5] price + tax = 110
```
Location, code, value, no formatting strings, no labels to invent. And one more trick that makes it special: `dbg!` *returns the value it printed*, so it can wrap an expression in place without changing what the code computes:
```rust
fn main() {
let price = 100;
let total = dbg!(price / 10) + price;
println!("total: {total}");
}
```
```
[src/main.rs:3:17] price / 10 = 10
total: 110
```
The division still happened, `total` still got 110, and we X-rayed the intermediate value in passing. No restructuring, no temporary variables; wrap the suspicious subexpression, read the report, unwrap it when done. This wrap-in-place trick is what makes `dbg!` the halving strategy's perfect partner: any seam from lesson [3.3](@/chapter-3/a-strategy-for-debugging.md) can be observed by wrapping it.
## A worked hunt
The shipping bug. Expected total for a 100-coin order: 115 plus tax. The program says:
```rust
fn main() {
let price = 100;
let with_shipping = add_shipping(price);
let total = add_tax(price);
println!("total: {total}");
}
fn add_tax(amount: i32) -> i32 {
amount + amount / 10
}
fn add_shipping(amount: i32) -> i32 {
amount + 15
}
```
```
total: 110
```
110 is wrong (115 plus its tax should be 126), so: reproduce ✓, now locate. The pipeline is `add_shipping` then `add_tax`; check the seam by wrapping the value flowing *into* the tax step. Inside `add_tax`, wrap the parameter:
```rust
fn add_tax(amount: i32) -> i32 {
dbg!(amount);
amount + amount / 10
}
```
```
[src/main.rs:9:5] amount = 100
total: 110
```
There's the evidence: `add_tax` received **100**, the raw price, not the 115 that `add_shipping` produced. The bug isn't in either function's math; it's at the wiring in `main`, where sharp eyes will spot `add_tax(price)` getting the wrong variable. It should be `add_tax(with_shipping)`. One observation, root cause cornered; compare that to re-reading both functions' arithmetic five times (which is where staring would have taken you, since the arithmetic was never wrong).
The fix and retest: with `add_tax(with_shipping)`, the program prints `total: 126`, the `dbg!` line comes out, done. Worth noticing: the compiler had been hinting all along, with an unused-variable warning for `with_shipping`, computed and never read. Warnings are clues (lesson [0.11](@/chapter-0/warnings-lints-and-clippy.md) said they're symptoms); a bug hunt should always start by reading them.
{% callout(kind="warning", title="Warning") %}
`dbg!` is for *temporary* instrumentation, and its output format isn't something to build on (the docs explicitly decline to guarantee it). Before considering work finished, search the project for `dbg!` and clear the probes out; tidy code narrates only when asked. Your future teammates will judge a committed `dbg!` the way chefs judge a thumbprint in the plating. Fairly.
{% end %}
{% callout(kind="advanced", title="For advanced readers") %}
For programs that want *permanent*, switchable narration (servers logging requests, tools with a `--verbose` flag), the ecosystem has dedicated logging crates with levels, timestamps, and filtering. Appendix A.1 points at the standard choices. The dividing line: `dbg!` is scaffolding you remove; logging is plumbing you design.
{% end %}
## Quiz time
**Question #1**
Your program writes a report to stdout, and a teammate runs it as `cargo run > report.txt`. Which of your messages do they see on screen: the `println!` ones or the `eprintln!`/`dbg!` ones, and why does the difference matter here?
Show solution
Only the `eprintln!`/`dbg!` messages (stderr) appear on screen; all `println!` output went into `report.txt`. Which is exactly right: the file holds the clean product, the screen shows the chatter. If debug prints had used `println!`, they'd be *inside* the report.
**Question #2**
What does this print, in full?
```rust
fn main() {
let n = 6;
let doubled = dbg!(n * 2);
println!("{doubled}");
}
```
Show solution
```
[src/main.rs:3:19] n * 2 = 12
12
```
The `dbg!` reports location, expression text, and value (to stderr), then hands the 12 through to `doubled`, which prints normally (to stdout).
**Question #3**
In the worked example, why was wrapping `add_tax`'s *parameter* a smarter first probe than wrapping the arithmetic inside either function?
Show solution
It tested the seam between stages, per the halving strategy: one observation that decides whether the wrongness already existed *before* `add_tax`. It did (100 instead of 115), instantly clearing both functions' internals and pointing at the wiring. Probing the arithmetic first would have examined two innocent suspects in detail.
Prints make the program narrate. The next lesson gets you the same evidence without editing the program at all: pausing it live, mid-run, and looking around.