Error handling with Result and Cargo
Returning errors instead of throwing them, the ? operator, and the Cargo workflow that builds, tests and ships a crate.
Result and the ? operator
use std::fs;
fn read_port(path: &str) -> Result<u16, String> {
let text = fs::read_to_string(path).map_err(|e| e.to_string())?;
let port = text.trim().parse::<u16>().map_err(|e| e.to_string())?;
Ok(port)
}
fn main() {
match read_port("config.txt") {
Ok(p) => println!("port {}", p),
Err(e) => eprintln!("failed: {}", e),
}
}Result<T, E>is returned, not thrown, so failure is visible in the signature and impossible to ignore accidentally.- The
?operator returns early onErrand converts the error through theFromtrait when the types differ. unwrap()andexpect()panic on failure. They are appropriate in tests and prototypes, not in code that serves requests.- Library crates should define a typed error enum, often with the
thiserrorcrate; binaries can useanyhow::Result<T>and add context with.context(...). - A panic unwinds or aborts the thread or process. Reserve it for bugs and broken invariants, not for expected conditions such as a missing file.
Do not convert every error into a string too early. Erasing the type discards the ability to match on the cause, so retry logic and HTTP status mapping become guesswork.
Cargo: layout, builds and dependencies
cargo new hello --bin # binary crate; --lib for a library
cd hello
cargo build # debug build into target/debug
cargo run # build and execute
cargo test # unit tests in #[cfg(test)] modules
cargo build --release # optimised, target/release
cargo add serde --features derive
cargo clippy # lint pass with many useful correctness lints
cargo fmt # apply rustfmt[package]
name = "hello"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
anyhow = "1"
[profile.release]
lto = true| Item | Purpose |
|---|---|
Cargo.toml | Manifest: package metadata, dependencies, profiles |
Cargo.lock | Exact resolved versions; commit it for binaries |
src/main.rs | Entry point of a binary crate, containing fn main |
src/lib.rs | Root of a library crate, where unit tests usually live |
tests/ | Integration tests that use the crate as an external dependency |
src/bin/ | Extra binaries in the same package, one file each |
⚠️
Integer overflow panics in a debug build but wraps in a release build, and debug assertions disappear. Run
cargo test --release before shipping so behaviour you validated in development is validated in the profile you deploy.FAQ
When is unwrap acceptable?
In tests, in examples, and after you have proven the value cannot be absent — for instance right after inserting a key into a map. Everywhere else prefer
?, unwrap_or_else or an explicit match.thiserror or anyhow?
thiserror for library crates that must expose typed, matchable errors. anyhow for applications where a readable chain of context is more useful than an exhaustive error enum.Related
Structs, enums and pattern matching Syntax and ownership
Last refreshed 2026-09-18.