Step 2 of 7
Reading panics and backtraces
When a Rust program hits a bug it can't recover from, it panics: it stops, prints a message to stderr, and exits with code 101. The message is precise, so read it slowly.
fn main() {
let lines = ["12", "7", "x3"];
let mut total = 0;
for line in lines {
let n: i32 = line.parse().expect("each line should be a whole number");
total += n;
println!("running total: {total}");
}
}
running total: 12
running total: 19
Then, on stderr:
thread 'main' (31337) panicked at src/main.rs:5:35:
each line should be a whole number: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main': which thread panicked (in tests, it's the test's name). The number in parentheses is the thread's id and changes on every run; older Rust versions don't print it.src/main.rs:5:35: file, line and column. Line 5, column 35 is the.expect(...)call.- The message: your
expecttext, then the error'sDebugform. A goodexpectmessage says what you expected to be true, which is why it reads well here. - The lines printed before the panic tell you how far the program got: it failed on the third line,
"x3".
The fix is to handle bad lines instead of crashing:
fn main() {
let lines = ["12", "7", "x3"];
let mut total = 0;
for line in lines {
match line.parse::<i32>() {
Ok(n) => total += n,
Err(e) => eprintln!("skipping {line:?}: {e}"),
}
}
println!("total: {total}");
}
total: 19
Messages you'll meet
| Message | Usual cause |
|---|---|
index out of bounds: the len is 3 but the index is 3 |
a loop or index one past the end |
called `Option::unwrap()` on a `None` value |
a lookup or search found nothing |
called `Result::unwrap()` on an `Err` value: ... |
parsing or I/O failed; the error follows |
attempt to subtract with overflow |
an unsigned number would go below 0 (debug builds check this) |
attempt to divide by zero |
dividing by an empty collection's length, often |
RefCell already borrowed |
a RefCell borrowed mutably while another borrow was alive |
not yet implemented |
a todo!() was reached |
Backtraces
The panic location says where it broke, but often the real mistake is in the caller. Set the RUST_BACKTRACE environment variable to see the whole chain of calls, the backtrace:
fn average(scores: &[u32]) -> u32 {
let total: u32 = scores.iter().sum();
total / scores.len() as u32
}
fn main() {
let rounds: Vec<Vec<u32>> = vec![vec![70, 80], vec![]];
for r in &rounds {
println!("{}", average(r));
}
}
$ RUST_BACKTRACE=1 cargo run
75
thread 'main' (8226) panicked at src/main.rs:3:5:
attempt to divide by zero
stack backtrace:
0: __rustc::rust_begin_unwind
at /rustc/2d8144b7.../library/std/src/panicking.rs:689:5
1: core::panicking::panic_fmt
at /rustc/2d8144b7.../library/core/src/panicking.rs:80:14
2: core::panicking::panic_const::panic_const_div_by_zero
at /rustc/2d8144b7.../library/core/src/panicking.rs:175:17
3: scores::average
at ./src/main.rs:3:5
4: scores::main
at ./src/main.rs:9:24
5: <fn() as core::ops::function::FnOnce<()>>::call_once
at /rustc/2d8144b7.../library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Read it from the top and skip the frames from std and core (the panic machinery). Frame 3 is the first one in your crate: scores::average at line 3. Frame 4 is who called it: main, line 9. So main passed an empty round, and average didn't expect one. On Windows PowerShell, set the variable with $env:RUST_BACKTRACE=1 first. RUST_BACKTRACE=full shows every frame.
Common mistakes
- Reading only the last line. The first line (location) and the message carry the information; the
note:line is just a tip. - Fixing the line that panicked instead of the cause. If
averagereceives an empty slice, decide whetheraverageshould handle it (returnOption) or the caller should never pass one. .unwrap()everywhere. When something truly can't fail,.expect("why it can't")makes the panic message explain itself.
Your task
This program panics before finishing. Run it, read the panic message, and fix the loop so it prints the change between each pair of neighboring readings:
18 -> 21: +3
21 -> 19: -2
19 -> 25: +6
Challenge 2
This order summary panics with called `Option::unwrap()` on a `None` value. Find out which item causes it, and handle the missing price instead of panicking: print kiwi: unknown item and leave it out of the total. Expected output:
apple: 120
kiwi: unknown item
pear: 95
total: 215
Challenge 3
The warehouse program panics with attempt to subtract with overflow. Stock is a u32, which can't go below 0. Use checked_sub (it returns None instead of overflowing) so an order larger than the stock is refused and the program keeps going:
shipped 2, 3 left
cannot ship 4, only 3 left
shipped 1, 2 left
Previous: dbg! and eprintln!: print debugging done right Next: Debuggers and logging