Best Practices & Next Steps
Bring it all together with cargo fmt, clippy, testing with #[test], documentation, when (and when not) to reach for unsafe, and where to go after this course.
Introduction
You now know Rust's fundamentals: ownership and borrowing, structs and enums, control flow, functions, collections, and error handling. This final lesson rounds things out with the tooling and habits that separate code that merely compiles from genuinely idiomatic, production-quality Rust.
We'll also map out where to go from here, since fundamentals are just the foundation for the parts of Rust — traits, generics, async, and more — that make it such a capable language in practice.
- How cargo fmt and cargo clippy keep code consistent and idiomatic.
- How to write and run tests with #[test] and cargo test.
- How documentation comments (///) power cargo doc.
- When unsafe is (rarely) appropriate, and why it should stay isolated.
- Which ecosystem crates are worth knowing, and what to learn next.
cargo fmt and cargo clippy
`cargo fmt` automatically formats your code to the single, official Rust style — there's nothing to configure and nothing to debate. `cargo clippy` is a linter that goes well beyond formatting: it catches common mistakes, suggests more idiomatic patterns, and flags real correctness issues, not just style nits. Running both regularly (or wiring them into CI) is one of the highest-value habits you can build.
Writing Tests with #[test]
Rust has testing built in — no separate framework required. Any function annotated with `#[test]` becomes a test that `cargo test` will run automatically, typically grouped inside a `#[cfg(test)] mod tests { ... }` block so test code is excluded from your normal release build entirely. Assertions like `assert_eq!` and `assert!` check expected behavior and cause the test to fail with a clear message if it doesn't hold.
Documentation Comments
Comments starting with `///` (instead of `//`) are documentation comments, attached to the item directly below them. They support Markdown, can include runnable code examples, and are automatically compiled into browsable HTML documentation with `cargo doc --open`. Well-documented public APIs are a strong signal of a mature, trustworthy crate.
When (Not) to Use unsafe
The `unsafe` keyword unlocks a handful of operations the compiler can't fully verify — like dereferencing raw pointers or calling foreign (C) functions — that are needed for things like low-level systems code or FFI. For application-level code, you will almost never need it directly; the standard library and ecosystem crates already wrap the rare cases that require it behind safe APIs. When unsafe genuinely is necessary, keep it in small, clearly documented blocks with explicit invariants — it should never be used simply to "get past" a borrow-checker error.
Essential Ecosystem Crates
| Crate | What It's For |
|---|---|
| serde | The standard for serializing and deserializing data (JSON, YAML, and more). |
| tokio | The most widely used asynchronous runtime, powering async/await for network and I/O-heavy code. |
| clap | Declarative command-line argument parsing for building CLI tools. |
| anyhow / thiserror | Ergonomic error handling for applications (anyhow) and defining custom error types for libraries (thiserror). |
| reqwest | A high-level, ergonomic HTTP client built on top of tokio. |
Code Example
fn add(a: i32, b: i32) -> i32 { a + b}
#[cfg(test)]mod tests { use super::*;
#[test] fn it_adds_two_numbers() { assert_eq!(add(2, 3), 5); }}Run this with `cargo test`:
Click Run to see what this code prints.
Common Mistakes
- Skipping cargo clippy and missing idiomatic suggestions that catch real bugs, not just style nits.
- Writing unsafe blocks to "get past" a borrow-checker error instead of first trying to fix the underlying design — unsafe should be a deliberate, rare, well-documented choice.
- Publishing a crate without tests or documentation comments, making it hard for others (including future you) to trust or use it.
- Over-relying on .clone() everywhere as a substitute for actually understanding ownership, instead of designing your data flow around borrowing.
Best Practices
- Run cargo fmt and cargo clippy as part of your normal workflow, ideally enforced in CI.
- Write unit tests alongside your code with #[cfg(test)] modules, and integration tests in a top-level tests/ directory for larger projects.
- Document public APIs with /// doc comments — they show up automatically in cargo doc and make your code usable by others.
- Keep unsafe code, if you ever need it, isolated in small, well-commented modules with clearly stated invariants.
Frequently Asked Questions
The ownership model has a real learning curve, often called "fighting the borrow checker," but most developers report it clicking after a few weeks of regular practice, helped along by unusually clear compiler error messages.
serde (serialization), tokio (async runtime), clap (CLI argument parsing), anyhow and thiserror (ergonomic error handling), and reqwest (HTTP client).
Almost never for application code — the standard library and ecosystem crates handle the rare cases (FFI, low-level memory tricks) that need it, safely wrapped behind safe APIs.
Traits and generics next, then lifetimes in more depth, followed by either async/await with Tokio or a specific domain like web backends, CLI tools, embedded systems, or WebAssembly.
Key Takeaways
- cargo fmt and cargo clippy keep your code consistently styled and genuinely idiomatic — run both regularly.
- #[test] functions plus cargo test give you built-in testing with no separate framework needed.
- /// documentation comments power cargo doc and are a mark of a trustworthy, well-maintained crate.
- unsafe is rarely needed in application code, and should stay small, isolated, and well-documented when it is.
What to Learn Next
With ownership, structs, enums, control flow, functions, collections, and error handling under your belt, you have a genuinely solid Rust foundation. From here, the natural next topics are traits and generics (Rust's tools for shared behavior and code reuse), lifetimes in more depth, and then either async/await with a runtime like Tokio, or picking a domain — web backends, CLI tools, embedded systems, or WebAssembly — to specialize in.
Summary
Idiomatic formatting, built-in testing, first-class documentation tooling, and a deliberately rare, well-contained escape hatch in unsafe are a big part of why Rust code tends to stay reliable as projects grow. With this lesson, you have gone from Rust fundamentals all the way through ownership, data modeling, control flow, collections, and error handling — a genuinely solid foundation for writing real Rust software.