Rust Best Practices and Common Mistakes
A closing checklist of habits that separate idiomatic, maintainable Rust from code that merely satisfies the compiler.
3 min read
Getting Rust to compile is already a higher bar than most languages ask for — but code that compiles isn't automatically code that's idiomatic, readable, or a good citizen of its own ecosystem. Here's a checklist worth returning to.
Let the tools catch what you'd otherwise miss
cargo clippy
cargo fmtclippy is a linter that catches non-idiomatic patterns the compiler itself allows — needless clones, verbose code with a simpler equivalent, common logic mistakes. fmt enforces a single consistent style automatically, so code review never has to spend time on formatting. Run both before every commit; most teams wire them into CI so a pull request can't merge without a clean clippy run.
Common mistakes to avoid
Reaching for .clone() to make the borrow checker happy.
// Avoid: cloning just to dodge a borrow error you don't understand yet
fn print_name(name: String) { println!("{name}"); }
let n = String::from("Ada");
print_name(n.clone());
print_name(n.clone());
// Prefer: borrow instead, no cloning needed
fn print_name(name: &str) { println!("{name}"); }
let n = String::from("Ada");
print_name(&n);
print_name(&n);A stray .clone() isn't wrong, exactly, but reaching for it reflexively usually means the underlying ownership design hasn't been thought through — and it does have a real runtime cost for heap-allocated types. Try borrowing first; clone deliberately, not defensively.
Overusing .unwrap() outside of prototypes and tests.
// Avoid, in real application code
let port: u16 = std::env::var("PORT").unwrap().parse().unwrap();
// Prefer: handle the failure explicitly
let port: u16 = std::env::var("PORT")
.unwrap_or_else(|_| String::from("8080"))
.parse()
.expect("PORT must be a valid number");Every .unwrap() is a potential panic waiting on user input, a missing file, or a flaky network call. Reserve it for cases truly guaranteed not to fail, and use .expect("message"), ?, or a full match everywhere else — panicking on bad input is rarely the experience you want to ship.
Stringly-typed state instead of an enum.
// Avoid
fn set_status(status: &str) { /* "pending", "active", "done", or a typo */ }
// Prefer
enum Status { Pending, Active, Done }
fn set_status(status: Status) { /* only valid states can exist */ }An enum makes invalid states unrepresentable, and a match on it won't compile if a case is missed — a &str gives you neither guarantee.
Ignoring Result instead of handling or propagating it.
// Avoid: a silently swallowed error
let _ = std::fs::write("out.txt", data);
// Prefer: at least log it, or propagate with ?
if let Err(e) = std::fs::write("out.txt", data) {
eprintln!("failed to write output: {e}");
}Rust forces you to acknowledge a Result, but let _ = technically satisfies that requirement while throwing the error away entirely — it compiles clean and fails silently in production.
A final checklist
- [ ]
cargo clippyandcargo fmtrun clean before every commit. - [ ] Function parameters borrow (
&str,&[T]) rather than take ownership, unless ownership is genuinely needed. - [ ]
.unwrap()/.expect()appear only where failure is truly impossible or truly fatal — not as a substitute for real error handling. - [ ] State that has a fixed set of possibilities is an
enum, not aStringor a set of booleans. - [ ] Every
Resultis handled, propagated with?, or explicitly and visibly ignored — never silently dropped. - [ ] Public items have doc comments (
///), checked bycargo doc --open. - [ ] Dependencies in
Cargo.tomlare ones you actually use — runcargo macheteor a similar tool periodically to find dead ones.
None of these are exotic techniques — they're the same ownership, enums, and Result covered throughout this course, just applied with a bit more discipline. That discipline is most of what separates Rust code that merely satisfies the borrow checker from Rust code a team can actually maintain.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.