Skip to content
rusthacks.dev
Testing

Doc Tests in Rust and Keeping Examples Honest Step by Step

Code blocks in doc comments compile and run as tests. Write examples that stay correct, hide setup lines with a hash, mark intentional failures and choose the right attributes.

2 min read
Summarize in AI
Flat illustration for the post “Doc Tests in Rust and Keeping Examples Honest Step by Step”, composed around testing.

Documentation examples that no one runs are lies waiting to happen. Rust solves it by making every example a test. The cost is a slower cargo test and a few conventions for hiding boilerplate. This post covers what to write and what the attributes do. It is the third kind of test in the testing in Rust guide, and it supports writing the examples needed for public API design.

Rust 1.90.

A doc test

src/lib.rsrust
/// Splits `"host:port"` into its parts.
///
/// # Examples
///
/// ```
/// use my_crate::split_host_port;
///
/// let (host, port) = split_host_port("example.test:8080").unwrap();
/// assert_eq!(host, "example.test");
/// assert_eq!(port, 8080);
/// ```
pub fn split_host_port(s: &str) -> Option<(&str, u16)> {
    let (host, port) = s.rsplit_once(':')?;
    Some((host, port.parse().ok()?))
}

cargo test runs it and reports Doc-tests my_crate with 1 passed. If someone changes the function's behavior without updating the example, the test fails. The code is shown in the generated docs exactly as written.

Hide the setup

Lines that begin with # run but are not displayed:

rust
/// ```
/// # use my_crate::split_host_port;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let (host, _) = split_host_port("a:1").ok_or("no colon")?;
/// assert_eq!(host, "a");
/// # Ok(())
/// # }
/// ```

The reader sees two lines and the test is a complete program. This is how ? works in examples: the hidden wrapper returns a Result.

Attributes on the fence

Attribute on the opening fenceMeaning
no_runcompile but do not run, for network or disk
ignoreskip entirely, avoid it, since nothing checks it
should_panicpass if it panics
compile_failpass only if it fails to compile
text or bashnot Rust, not tested
edition2021compile with a specific edition

The compile_fail attribute is useful to show what the type system forbids, and it is fragile: any compile error passes it, including a typo. Add an error code after it, such as compile_fail,E0382, to pin the reason.

Panics and errors

Document the conditions in # Panics, # Errors and # Safety sections, as in safety comments. Give a doc test for the interesting case, not only the happy path.

Speed

Each doc test is compiled and linked separately. A crate with 200 doc tests can spend a long time on that. Rustdoc merges tests in edition 2024 to cut it down. Keep examples short, and move long scenarios into integration tests (integration tests).

Binary crates

Doc tests do not run for main.rs-only packages. Put the logic in a library crate to document and test it this way (library or binary).

Where doc tests stop

They show usage and cannot test private code or complex setups. Run just them with cargo test --doc. The rustdoc book documents all attributes under documentation tests.

Also inTooling
Taggeddoctests
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.