Skip to Content
Course content

11: Comments and Documentation

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of time reviewing pull requests over the years, and there is one specific habit that always makes me sigh: the "obvious comment." You know the ones. A developer writes a line of code, and then writes a comment right above it that simply translates the code into English. It doesn't add value; it just adds noise.

The trap of redundant explanations

Imagine you're writing a utility to handle API retries. You might be tempted to write something like this:

// Calculate the delay using exponential backoff
let delay = 2u64.pow(attempt); 
// Multiply by the base multiplier
let final_delay = delay * base_ms;

If you're a Rust developer, you can already see exactly what's happening. Telling me that you're "calculating the delay" when the variable is named delay is a waste of your time and mine. When I see this in a codebase, it's a red flag that the author is using comments as a crutch because they aren't trusting their naming conventions. This is the naive way of documenting: treating comments as a narrative of the what.

Shifting to the "Why" and the "How to Use"

In Rust, we have a much better way to handle this. I want you to distinguish between // (regular comments) and /// (documentation comments). Regular comments are for the people maintaining the internals of the function—the "why did I use this weird workaround here?" notes. Documentation comments are for the people using your function.

Instead of describing the math inside the function, use a doc comment to describe the contract. Let's rewrite that backoff logic:

/// Calculates the wait time before a retry attempt.
///
/// The delay grows exponentially based on the attempt number to avoid 
/// overwhelming the server (thundering herd problem).
///
/// # Examples
///
/// ```
/// let delay = calculate_backoff(3, 100);
/// assert_eq!(delay, 800);
/// ```
pub fn calculate_backoff(attempt: u32, base_ms: u64) -> u64 {
    2u64.pow(attempt) * base_ms
}

See the difference? I'm not explaining the pow() call; I'm explaining the intent (preventing the thundering herd problem) and providing a concrete example of how it behaves. The best part is that those examples aren't just for show—they are "doc-tests." When you run cargo test, Rust actually compiles and runs the code inside those triple-slash comments to ensure your documentation never goes out of date. If you change the logic but forget to update the example, your build fails. That's a safety net you don't get in most other languages.

Context at the top of the file

Sometimes you need to describe the entire module rather than a single function. If you put /// at the top of a file, it attaches to the first item it finds. To document the module itself, you use //!. I like to think of the exclamation mark as saying, "This applies to the container I'm currently inside."

I usually use module-level docs to explain the architectural role of the file. For instance, if this backoff logic lived in a retry.rs file, I'd start the file with:

//! This module provides primitives for handling transient network failures.
//!
//! It includes strategies for exponential backoff and jitter to ensure
//! stable recovery during system outages.

By splitting your comments this way, you create a tiered information system. A user can run cargo doc --open and get a beautiful, searchable website that tells them exactly how to use your API without ever having to look at your implementation details. You're effectively separating the "User Manual" from the "Maintenance Log."




📋 Practical Task

Documenting a Thread-Safe Cache Manager

You have been handed a piece of legacy code for a simple CacheManager. The code works perfectly, but it's a "black box"—no one knows how to use it without reading every line of implementation. Your task is to transform this into a professional, documented library.

pub struct CacheManager {
    storage: std::collections::HashMap<String, String>,
}

impl CacheManager {
    pub fn new() -> Self {
        Self { storage: std::collections::HashMap::new() }
    }

    pub fn set(&mut self, key: String, value: String) {
        self.storage.insert(key, value);
    }

    pub fn get(&self, key: &str) -> Option<String> {
        self.storage.get(key).cloned()
    }
}

Requirements:

  • Add a module-level documentation block (//!) explaining that this manager provides a simple key-value store for string data.
  • Add documentation comments (///) to the set and get methods.
  • Include a # Examples section in the get method's documentation that demonstrates both a successful cache hit and a cache miss (using assert_eq!).
  • Ensure that the comments focus on the behavior (e.g., what happens when a key doesn't exist) rather than just repeating the function name.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.