Part VIII — Professional Rust
This Part covers what happens once the code exists: finding bugs in running programs, structuring a growing codebase, putting quality gates in CI, and running Rust services in containers. It ends with the anti-patterns that code review catches most often.
65. Debugging: backtraces, lldb/gdb, tokio-console, Miri
65.1 Panics and backtraces
RUST_BACKTRACE=1 cargo run # backtrace on panic
RUST_BACKTRACE=full cargo run # with every frame, including std internals
RUST_LIB_BACKTRACE=1 # anyhow: capture a backtrace when an error is created
In production, install a panic hook that sends the panic to your logs, so it is not lost on a stderr nobody reads:
fn main() {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let location = info.location().map(|l| format!("{}:{}", l.file(), l.line())).unwrap_or_default();
let msg = info.payload().downcast_ref::<&str>().copied()
.or_else(|| info.payload().downcast_ref::<String>().map(String::as_str))
.unwrap_or("<non-string panic>");
// In a service: tracing::error!(%location, msg, "panic") so it reaches your log pipeline.
eprintln!(r#"{{"level":"error","event":"panic","location":"{location}","message":"{msg}"}}"#);
default_hook(info);
}));
let v: Vec<u8> = Vec::new();
let _ = std::panic::catch_unwind(|| v[3]);
}
65.2 A debugger session
cargo build # debug info is on by default in the dev profile
rust-lldb target/debug/kv # or rust-gdb on Linux; both add pretty-printers for Vec, String, Option...
(lldb) b kv.rs:120 # breakpoint at a line
(lldb) r serve
(lldb) frame variable args # prints Vec<Vec<u8>> readably thanks to the Rust formatters
(lldb) bt # backtrace
VS Code with rust-analyzer plus the CodeLLDB extension gives you the same
thing with a UI: click "Debug" above any fn main or #[test]. Delve in
the Go guide §58
and gdb in the C guide §14.4
are the equivalents.
65.3 Async debugging
A stuck async service usually has one of three causes: a task blocked in
synchronous code (§31.5), a lock held across .await, or a future that is
never woken. Two tools help:
tracingspans around each request and each important.await, so the logs show where a request stopped making progress.tokio-console, which works liketopfor tasks. It shows every task, how long it has been idle or busy, and its wake-up count. Enable it with theconsole-subscribercrate andRUSTFLAGS="--cfg tokio_unstable". A task with a large "busy" time and no.awaitis blocking the executor.
65.4 The rest of the toolbox
| Problem | Tool |
|---|---|
UB in unsafe code, data races in unsafe |
cargo +nightly miri test (§33.4) |
| Memory errors in C dependencies | RUSTFLAGS=-Zsanitizer=address cargo +nightly test |
| Concurrency orderings | loom (§61.1) |
What a macro or derive generated |
cargo expand |
| Why the binary is large | cargo bloat --release |
| Which line regressed performance | cargo flamegraph (§36.3), then git bisect run cargo bench |
| Compile time | cargo build --timings (an HTML report of what took how long) |