Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
211: Doc Tests as Living Documentation
Imagine you've just bought a complex piece of furniture from IKEA. You open the manual, and it tells you: "Insert Screw A into Slot B." You try it, but Slot B doesn't exist anymore because the manufacturer updated the design six months ago, but forgot to update the printed manual. You're left staring at a pile of wood and a useless piece of paper. That's exactly what happens in software when our documentation "rots"—the code evolves, but the examples in the README or the API docs stay frozen in time.
Rust solves this with doc tests. Think of doc tests as a little robot that lives inside your manual. Every time you run your tests, this robot reads your documentation, finds the code examples, and actually tries to build the furniture. If the robot fails to put Screw A into Slot B, the test fails. You're forced to either fix the code or update the manual. The documentation is no longer a static promise; it's a living part of your test suite.
Stop Lying to Your Users
I can't tell you how many times I've pulled my hair out because a library's documentation showed a function call that wouldn't even compile. In Rust, we use the /// (three slashes) syntax for documentation comments. Anything inside a markdown code block within those comments is treated as a test case by cargo test.
Let's look at a real example. Suppose we're building a simple DiscountCalculator. Instead of just telling the user it works, we show them—and we make the compiler prove it.
/// Calculates the final price after applying a percentage discount.
///
/// # Examples
///
/// ```
/// use my_store::DiscountCalculator;
///
/// let final_price = DiscountCalculator::apply(100.0, 20.0);
/// assert_eq!(final_price, 80.0);
/// ```
///
/// This function assumes the discount is a percentage (0-100).
pub struct DiscountCalculator;
impl DiscountCalculator {
pub fn apply(price: f64, discount: f64) -> f64 {
price * (1.0 - (discount / 100.0))
}
}
When you run cargo test, Rust extracts that code block, wraps it in a hidden main function, and executes it. If you later change apply to take a decimal (0.20) instead of a percentage (20.0), the doc test will crash, alerting you that your documentation is now lying to your users.
Dealing with the "Happy Path" and Beyond
Most people only write doc tests for the "happy path," but I want you to push further. What happens when the input is garbage? You can document your error handling and test it simultaneously. If your function panics under certain conditions, you can tell the doc test to expect that using a special attribute.
Check this out:
/// Divides the total cost across a number of people.
///
/// # Panics
///
/// Panics if the number of people is zero.
///
/// ```
/// use my_store::BillSplitter;
///
/// // This should work fine
/// assert_eq!(BillSplitter::split(100.0, 4), 25.0);
///
/// // This will panic, and we tell Rust to expect it
/// #[should_panic]
/// BillSplitter::split(100.0, 0);
/// ```
pub struct BillSplitter;
impl BillSplitter {
pub fn split(amount: f64, people: u32) -> f64 {
if people == 0 {
panic!("Cannot split a bill among zero people!");
}
amount / people as f64
}
}
By adding #[should_panic] inside the doc test, you're essentially saying: "I'm documenting that this specific edge case causes a crash, and I want to make sure it continues to crash this way." It's a powerful way to ensure your safety guarantees don't accidentally disappear during a refactor.
Hidden Machinery and Customizing the Test
Sometimes, your doc test needs a bit of setup that you don't actually want the user to see in the final documentation—maybe some tedious imports or a helper function. You can hide these lines by starting them with a hash #. Rust will execute them, but rustdoc will hide them from the rendered HTML.
I use this constantly to keep examples clean. If I have a 10-line setup just to get a single variable ready, I'll hide the boilerplate so the reader can focus on the actual API call. It keeps the "signal-to-noise" ratio high while keeping the test fully reproducible.
📋 Practical Task
Implementing a Documented Password Validator
Your task is to create a PasswordValidator struct that ensures a password is at least 8 characters long and contains at least one digit. Instead of writing a separate test module, you will implement the requirements using Doc Tests.
- Create a struct
PasswordValidatorwith a methodis_valid(password: &str) -> bool. - Write a documentation comment (
///) foris_valid. - Inside that comment, include a
# Examplessection with at least three code blocks:- A valid password that returns
true. - A password that is too short and returns
false. - A password with no digits that returns
false.
- A valid password that returns
- Run
cargo testto ensure your documentation is actually correct and that the "living documentation" passes.
There are no comments for now.