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 undercargo 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 want | Use |
|---|---|
| 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 them | prefix 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 oflib.rs). Both are Markdown and render withcargo doc.- Code blocks in doc comments are doctests:
cargo testcompiles 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
useitems) and run as a separatecargo testphase; 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 withno_run(compile only),should_panic,compile_fail, andignore(last resort). - Adopt the conventional sections
# Examples/# Errors/# Panics/# Safety, use[`Item`]intra-doc links, and enforce coverage with#![warn(missing_docs)].