Documentation and Doctests

Rust treats documentation as a first-class part of the language, not an afterthought bolted on with a separate tool. Doc comments compile into a browsable website with cargo doc, and, uniquely, the code examples inside them are run as tests by cargo test. That last point is the killer feature: your documentation can't rot, because an example that stops compiling or returns the wrong answer fails CI like any other test. This tutorial covers the doc-comment syntax, how doctests work and how to control them, and the small conventions (# Examples, # Panics, intra-doc links) that make docs genuinely useful, building on the testing and error-handling tutorials.


/// and //!: Two Doc Comment Forms

Rust has two doc-comment syntaxes, and the distinction is what they document:

  • /// (outer) documents the item that follows it, a function, struct, or module.
  • //! (inner) documents the item that contains it, used at the top of a file or module to describe the whole crate or module.
//! This module handles temperature conversions.   <- documents the module itself

/// Converts Celsius to Fahrenheit.                 <- documents the function below
///
/// Multiplies by 9/5 and adds 32.
pub fn to_fahrenheit(c: f64) -> f64 {
    c * 9.0 / 5.0 + 32.0
}

Doc comments are Markdown, so **bold**, bullet lists, headings (#), and fenced code blocks all render in the generated docs. cargo doc --open builds the HTML and opens it. The convention: a //! at the top of lib.rs is your crate's front page, and every pub item gets a /// explaining what it does and why you'd use it, not restating the signature.


Doctests: Examples That Are Actually Tested

A fenced ```rust code block inside a doc comment is a doctest: cargo test compiles and runs it, exactly like a unit test. This is what keeps documentation honest, an example that drifts out of sync with the code fails the build.

/// Adds two numbers.
///
/// # Examples
///
/// ```
/// use mycrate::add;
///
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

When you run cargo test, that assert_eq! executes. If someone changes add to subtract, the doctest fails, the docs and the code can't silently disagree. Each doctest runs as its own little program (with an implicit fn main() wrapper), so a bare assert_eq! at the top level just works. This is the single biggest reason to write examples in Rust docs: they're verified, not aspirational.

Gotcha: doctests run against your crate's public API only, as if written by an external user, and each is compiled as a standalone program. Two consequences trip people up: (1) you must use mycrate::...; to bring items into scope (a doctest doesn't inherit the surrounding module's imports), and (2) doctests don't run under cargo test --lib, they're a separate phase you'll see as "Doc-tests mycrate". If your examples reference private items, they won't compile as doctests, which is the intended signal that examples should demonstrate the public interface.


Hiding Setup Lines and Controlling Execution

Real examples often need boilerplate (imports, setup) that would clutter the rendered docs. Prefixing a line with # hides it from the rendered output but still compiles/runs it, so the example stays complete and clean:

/// ```
/// # use mycrate::Database;      // hidden in docs, but runs
/// # let db = Database::in_memory();
/// let user = db.find_user(42);  // this is all the reader sees
/// assert!(user.is_some());
/// ```

The rendered docs show only the meaningful line, but the full snippet is compiled and tested. You also control how a block is treated with annotations after the ```:

/// ```no_run
/// // compiles (type-checked) but is NOT executed — for code that hits the network, etc.
/// # use mycrate::Client;
/// let resp = Client::new().get("https://example.com");
/// ```
///
/// ```should_panic
/// // the test PASSES only if this code panics
/// # use mycrate::parse;
/// parse("not a number");   // expected to panic
/// ```
///
/// ```ignore
/// // skipped entirely — use sparingly; it defeats the point of doctests
/// ```
  • no_run — compile-check but don't execute (network calls, long-running, side effects).
  • should_panic — the doctest passes only if the code panics (documenting a panic path).
  • ignore — skip completely; a last resort, since an unrun example can rot.
  • compile_fail — the test passes only if the code fails to compile (documenting a type-safety guarantee).

Documentation Conventions That Pay Off

Beyond syntax, a few conventions make docs genuinely useful and are worth adopting as a team standard. Rustdoc gives certain #-headings semantic weight by convention:

/// Parses a port number from a string.
///
/// # Examples
/// ```
/// # use mycrate::parse_port;
/// assert_eq!(parse_port("8080").unwrap(), 8080);
/// ```
///
/// # Errors
/// Returns `Err` if the string is not a valid `u16`.
///
/// # Panics
/// Panics if given an empty string.   (document panics so callers aren't surprised)
pub fn parse_port(s: &str) -> Result<u16, ParseError> { /* ... */ }

The conventional sections are # Examples, # Errors (what makes a Result-returning fn fail, from Error Handling in Practice), # Panics (conditions that panic), and # Safety (the precondition for an unsafe fn, from Unsafe Rust Basics). Two more high-value habits: intra-doc links let you write [`OtherType`] and rustdoc turns it into a hyperlink to that item's docs, no URLs to maintain; and #![warn(missing_docs)] at the crate root turns any undocumented pub item into a warning, enforcing that the public API stays documented.


Documentation Reference

You wantUse
Document the item below/// (outer doc comment)
Document the enclosing module/crate//! (inner doc comment)
A tested, runnable example```rust block with assert!/assert_eq!
Hide setup lines but still run themprefix with #
Compile-check but don't execute```no_run
Document (and test) a panic path```should_panic
Document that bad code won't compile```compile_fail
Link to another item's docs[`ItemName`] (intra-doc link)
Enforce docs on the public API#![warn(missing_docs)]

The throughline: Rust documentation is executable. Write # Examples blocks with real assertions and they become tests that keep the docs true forever; use #-hidden setup to stay readable; and lean on # Errors/# Panics/# Safety plus intra-doc links to make the generated site something the team actually reaches for.


Key Takeaways

  • /// documents the item that follows; //! documents the enclosing module/crate (crate front page at the top of lib.rs). Both are Markdown and render with cargo doc.
  • Code blocks in doc comments are doctests: cargo test compiles and runs them, so an example that drifts from the code fails the build, documentation that can't rot.
  • Doctests exercise the public API as an external user (you must use items) and run as a separate cargo test phase; referencing private items won't compile, which is the intended nudge to demonstrate the public interface.
  • Hide setup with a # prefix (still compiled/run, hidden in docs); control execution with no_run (compile only), should_panic, compile_fail, and ignore (last resort).
  • Adopt the conventional sections # Examples/# Errors/# Panics/# Safety, use [`Item`] intra-doc links, and enforce coverage with #![warn(missing_docs)].