Skip to content
← All articles

Comments, doc comments, and how to read docs.rs

Rust's documentation story is one of its genuinely best ideas: doc comments are Markdown, they compile to a website, and their examples run as tests. Knowing how to read the output is the difference between needing help and not.

You are going to spend more time reading Rust documentation than writing Rust. That is not a criticism of your ability; it is what programming in a language with a large, precise standard library looks like. So it is worth ten minutes on how that documentation is produced, because knowing how it is produced tells you how to read it.

Four kinds of comment

// A line comment. Ignored by the compiler entirely.

/* A block comment.
   These nest properly, unlike C's:
   /* this inner one is fine */
   and the outer one still closes here. */

/// An OUTER doc comment. Documents the item that follows it.
fn parse(input: &str) -> u32 { … }

//! An INNER doc comment. Documents the thing it is INSIDE.
//! Goes at the top of a module or a file.

The two you will actually think about are /// and //!, and the distinction is only about direction. /// documents what comes next. //! documents what contains it — which is why it appears at the top of a file, before anything else, describing the module as a whole.

There is a rarer block form, /** … */ and /*! … */, with identical meaning. Almost nobody uses it.

💡/// is not a comment in the way // is. What is it actually, and what is the observable consequence? click to reveal

A doc comment is syntactic sugar for an attribute. These two are the same thing to the compiler:

/// Parses a decimal integer.
fn parse(s: &str) -> u32 { … }

#[doc = "Parses a decimal integer."]
fn parse(s: &str) -> u32 { … }

Which means a doc comment is part of the syntax tree, attached to an item — not whitespace that the lexer discards. And that has an observable consequence: a doc comment with nothing after it is a compile error, not a stray comment.

error: expected item after doc comment
 --> src/main.rs:4:1
  |
4 | /// dangling
  | ^^^^^^^^^^^^ this doc comment doesn't document anything

You will hit this the first time you comment out a function and leave its docs behind. Now you know what the message means: there is nothing left for the attribute to attach to. The fix is to delete it, turn it into //, or restore the item.

Doc comments are Markdown, and the examples are tests

The body of a doc comment is CommonMark. Headings, lists, tables, links, and fenced code blocks all work, and rustdoc renders them into the HTML you see on docs.rs.

Then comes the idea that other languages keep trying to copy:

/// Returns the sum of a slice.
///
/// # Examples
///
/// ```
/// let total = mylib::tally(&[1, 2, 3]);
/// assert_eq!(total, 6);
/// ```
pub fn tally(items: &[i32]) -> i32 {
    items.iter().sum()
}

Under cargo test, that example is compiled and run as a test. Not scanned, not type-checked in isolation — actually executed, with the assertions live.

The consequence is enormous and slightly under-appreciated: in the Rust ecosystem, documentation examples cannot rot. If someone changes tally to return Option<i32>, the doctest stops compiling and CI goes red. Every example on docs.rs for a maintained crate is an example that worked, against that version, on the day it was published. Coming from ecosystems where the README is aspirational fiction, this is a genuine change in how much you can trust what you read.

A few doctest details worth knowing when you read them:

  • Lines beginning with # inside the code fence are hidden from the rendered page but still compiled. That is how examples show you the interesting three lines without the boilerplate use statements.
  • ```no_run compiles but does not execute — for examples that would open a socket.
  • ```ignore does neither, and is a mild smell.
  • ```should_panic asserts that the example panics, which is how panic conditions get documented and verified at once.
  • ```text marks a block as not-Rust, so rustdoc stops trying to compile your shell transcript.

None of this works on this site. There is no cargo, so there is no cargo test, so there is no doctest runner; and there is no rustdoc, so nothing renders. Write doc comments here for the same reason you would write them anywhere — they are the right habit and they cost nothing — but be aware that the gate cannot check them, and that a dangling one is the only way a doc comment can affect your grade.

The conventional sections

Rustdoc does not enforce any structure, but the ecosystem has converged hard on a small set of # headings, and reading them in this order is how you read a docs.rs page quickly:

  • A one-line summary first. Rustdoc uses this line, and only this line, in index listings — so it must stand alone.
  • # Examples — almost always present, almost always the fastest way to understand the function.
  • # Panics — the conditions under which this function panics. If you are looking for “will this blow up on empty input”, this is where the answer is.
  • # Errors — for functions returning Result, what the error variants mean.
  • # Safety — for unsafe fn, the invariants the caller must uphold. Clippy’s missing_safety_doc lint is on by default and will demand this section.

Two pedantic lints, missing_panics_doc and missing_errors_doc, enforce the middle two. They are off by default and Track 18 turns them on, because by then you are writing library code and the absence of a # Panics section is a real defect.

💡You are reading the docs for a function you have never used, and you have thirty seconds. What do you look at, and in what order? click to reveal

The signature, first and mostly. In Rust the signature carries far more than in most languages: whether it takes &self, &mut self or self tells you whether calling it consumes the receiver; -> Option<T> versus -> T tells you whether it can fail to produce an answer; -> Result<T, E> names the error type; a where clause tells you the constraints. Half of all “how do I use this” questions are answered by reading the signature carefully once.

The one-line summary, second. It is written to stand alone and usually does.

The example, third. It compiled and ran, so it is not lying to you.

# Panics, fourth, if the function takes anything you did not construct yourself.

The prose paragraphs are fourth or fifth, not first. That inversion is the single biggest difference between people who read docs quickly and people who do not.

Two navigation habits worth building on docs.rs: press S to focus the search box from anywhere, and use the source link — reading the standard library’s implementation is often faster than reading three paragraphs about it, and it is right there.

When to write a comment at all

A short opinion, since you are about to write a lot of Rust.

Comments that restate the code are worse than nothing, because they are a second thing to keep correct and the compiler does not check them. // increment i above i += 1 is noise that will one day sit above i -= 1.

Comments that carry information the code cannot are the whole point:

  • Why, not what. “We scan backwards because the input is usually sorted descending” is not derivable from the loop.
  • The rejected alternative. “A HashMap here would be faster but the ordering is load-bearing downstream” saves the next person an afternoon.
  • The invariant. “Callers must hold the lock” — and in unsafe code this is not advice, it is the contract, and clippy will insist you write it.
  • The reference. A link to the RFC, the issue, the paper, the spec paragraph.

Doc comments have one extra job on top: they are the public face of your API, and they are read by people who will never see the body. Write the summary line for someone scanning a list of forty functions.