How to Use cargo doc to Generate Documentation

Run cargo doc to generate HTML documentation from your Rust code comments and open it in your browser.

When comments aren't enough

You spent the weekend building a parser for a new config format. The logic is solid. The tests pass. You zip the folder and email it to a teammate with the subject line "Check this out." Two hours later, they reply: "Cool, but what does parse_config actually take? And why is there a Result everywhere?" You stare at the screen. The comments are buried in the source files. Your teammate doesn't want to hunt through lib.rs to understand your API. They want a clean reference. That's where cargo doc comes in. It turns your doc comments into a navigable website, so your users never have to read raw source code to figure out how to use your crate.

Documentation as a build artifact

cargo doc is the command that reads your documentation comments and builds a static HTML site. It doesn't just copy text. It links types together, generates a search index, and even runs tests hidden inside your comments. Think of it as a compiler for documentation. You write the content in your code, and cargo doc assembles the presentation layer. The output lives in target/doc, and the command can open it for you instantly.

Rust treats documentation as part of the build. If your docs are broken, the build can fail. This keeps documentation in sync with the code. You don't maintain a separate wiki or markdown file. The docs live where the code lives. When the code changes, the docs change.

/// Parses a configuration string into a `Config` struct.
///
/// Returns an `Error` if the format is invalid.
pub fn parse(s: &str) -> Result<Config, Error> {
    // Implementation details...
    todo!()
}

Run cargo doc --open in your project root. Cargo compiles your crate, extracts the doc comments, generates the HTML, and opens the main page in your browser. You see a clean interface with your function, its signature, and the description you wrote. The Config and Error types are linked to their own pages. Clicking them jumps to the definition. This cross-referencing is automatic.

Run cargo doc --open. Your documentation is now a website, not a text file.

How the engine works

When you run cargo doc, Cargo treats documentation as a build artifact. It compiles your crate and its dependencies to extract type information. The compiler scans for lines starting with /// or //!. These are doc comments. The compiler attaches them to the items they precede.

The documentation engine then generates HTML files. It creates a search index so users can find types by name. It links every type reference to its definition. If you mention Vec in a comment, the generated page links to the standard library's Vec docs. This happens without you writing URLs. You write type names, and the tool handles the rest.

The output structure is predictable. target/doc/crate_name/index.html is the entry point. Each module and item gets its own HTML file. The search bar at the top indexes everything. You can browse by module, by type, or by function. The layout is consistent across all Rust crates, so users already know how to navigate your docs.

Convention aside: never commit target/doc to version control. The documentation is reproducible from source. Committing it bloats the repository and causes merge conflicts. Add target/ to your .gitignore.

Intra-doc links and the magic of brackets

Rust doc comments support special syntax for linking. This is where cargo doc shines. You can link to types, functions, and modules using bracket notation.

/// A container that holds a value.
///
/// Use `[Option]` when the value might be missing.
/// Use `[Result]` when an operation can fail.
pub struct Container<T> {
    value: T,
}

In the generated HTML, [Option] becomes a clickable link to std::option::Option. [Result] links to std::result::Result. The brackets tell rustdoc to resolve the name to a type. If the name isn't in scope, rustdoc warns you.

Backticks work differently. `Vec` renders as code. It does not create a link. Use backticks for code fragments, variable names, or when you want to mention a type without linking it. Use brackets when you want the user to jump to the definition.

/// Returns the length of the `[Vec]`.
///
/// # Panics
///
/// Panics if the index is out of bounds.
pub fn get_length(v: &Vec<i32>) -> usize {
    v.len()
}

Treat broken links as errors. If rustdoc warns about an unresolved link, fix it. Users clicking a dead link lose trust in the documentation.

Doc tests: examples that verify themselves

Rust doc comments can contain code blocks. These aren't just for display. They are executable tests. When you run cargo test, Rust compiles and runs every code block in your doc comments. This keeps examples accurate. If the code changes and the example breaks, the build fails.

/// A simple counter that tracks increments.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// use my_crate::Counter;
///
/// let mut counter = Counter::new();
/// counter.increment();
/// assert_eq!(counter.get(), 1);
/// ```
///
/// You can also reset the counter:
///
/// ```
/// use my_crate::Counter;
///
/// let mut counter = Counter::new();
/// counter.increment();
/// counter.reset();
/// assert_eq!(counter.get(), 0);
/// ```
pub struct Counter {
    value: u64,
}

impl Counter {
    /// Creates a new counter starting at zero.
    pub fn new() -> Self {
        Counter { value: 0 } // Initialize with zero state
    }

    /// Increments the counter by one.
    pub fn increment(&mut self) {
        self.value += 1; // Update internal state
    }

    /// Resets the counter to zero.
    pub fn reset(&mut self) {
        self.value = 0; // Clear state
    }

    /// Returns the current value.
    pub fn get(&self) -> u64 {
        self.value // Return current state
    }
}

The code blocks run in isolation. Each block gets its own compilation unit. You need to import types inside each block. The use my_crate::Counter; line is required. Rust doesn't assume the block is inside the crate. This isolation prevents hidden dependencies.

You can control doc test behavior with attributes. Add ignore to skip a test. Add no_run to compile but not execute. Use compile_fail to show code that should not compile.

/// This function panics on empty input.
///
/// ```should_panic
/// my_crate::process(""); // This will panic
/// ```
pub fn process(s: &str) {
    assert!(!s.is_empty());
}

Treat doc tests like unit tests. If the example doesn't compile, your API is broken.

Controlling visibility with attributes

Not everything in your code needs documentation. Some items are internal helpers. Some are public but shouldn't appear in the API reference. Rust provides attributes to control what cargo doc shows.

Use #[doc(hidden)] to hide a public item from the documentation. The item remains public and usable, but it doesn't appear in the generated HTML. This is useful for internal implementation details that you don't want users to rely on.

/// Internal helper for parsing.
#[doc(hidden)]
pub fn _internal_parse(s: &str) -> Parsed {
    // Implementation...
    todo!()
}

Use #[doc(alias = "name")] to add search aliases. This helps users find items by alternative names. For example, if you have a function called calculate_mean, you can add an alias for average.

/// Calculates the arithmetic mean of a slice.
///
/// # Examples
///
/// ```
/// let values = vec![1.0, 2.0, 3.0];
/// assert_eq!(calculate_mean(&values), 2.0);
/// ```
#[doc(alias = "average")]
#[doc(alias = "avg")]
pub fn calculate_mean(values: &[f64]) -> f64 {
    values.iter().sum::<f64>() / values.len() as f64
}

Users searching for "average" will now find calculate_mean. This improves discoverability without cluttering the API.

Document public items. Hide the rest. Users only need the interface.

Pitfalls and warnings

cargo doc produces warnings for common issues. Treat these warnings as errors in your CI pipeline. Broken documentation hurts users.

If you document a private item, rustdoc warns you. You'll see a message like "documented item is private". Fix this by making the item public or removing the doc comment. Private items don't appear in the public API, so documenting them is wasted effort.

If you write a broken intra-doc link, rustdoc warns you. The warning points to the unresolved name. Fix the name or add the missing import. Broken links confuse users.

If you have a code block that doesn't compile, cargo test fails. The error message shows the compilation error. Fix the code or mark the block with ignore or compile_fail.

Convention aside: run cargo doc with --document-private-items when debugging. This shows all items, including private ones. It helps you verify that internal docs are correct. Don't use this for production builds. It exposes implementation details.

Decision matrix

Use cargo doc --open when you want to preview your documentation immediately in the browser. Use cargo doc --no-deps when you only care about your crate's docs and want to skip the slow compilation of dependency documentation. Use cargo doc --package my-crate when working in a workspace and need docs for a specific member. Use cargo test --doc when you want to run the examples inside your doc comments without building the full HTML site. Use cargo doc --document-private-items when debugging or writing internal docs that you don't want to expose in the public API.

Use --no-deps to keep your focus on your code, not the ecosystem.

Where to go next