Doctests
Doctests are code examples in /// doc comments that cargo test compiles and runs. They keep documentation honest and demonstrate API usage inline with rustdoc.
Recipe
/// Adds two integers.
///
/// ```
/// let sum = my_crate::add(2, 2);
/// assert_eq!(sum, 4);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}When to reach for this: Public API examples that must stay synchronized with code changes.
Working Example
/// Parses a user id from text.
///
/// # Errors
///
/// Returns `Err` when the input is not a positive integer.
///
/// ```
/// use my_crate::parse_user_id;
///
/// let id = parse_user_id("42")?;
/// assert_eq!(id, 42);
/// # Ok::<(), my_crate::Error>(())
/// ```
pub fn parse_user_id(s: &str) -> Result<u64, Error> {
// ...
todo!()
}cargo test --doc
cargo test --doc parse_user_idWhat this demonstrates:
- Examples are full Rust snippets rustdoc wraps in a fn
?in examples needs trailing# Ok::<(), E>(())hidden linecargo test --docruns only doctests- Failures point to the doc comment line
Deep Dive
doctest Attributes
/// ```no_run
/// let client = Client::connect("postgres://...").await?;
/// ```
///
/// ```ignore
/// // pseudo-code not compiled
/// ```
///
/// ```should_panic
/// panic!("bad");
/// ```| Marker | Behavior |
|---|---|
no_run | Compile but do not run |
ignore | Skip compile (display only) |
should_panic | Expect panic |
edition2024 | Set edition for snippet |
Hidden Lines
Prefix with # to hide setup from rendered docs:
/// ```
/// # use my_crate::Widget;
/// let w = Widget::default();
/// assert!(w.ready());
/// ```Gotchas
- Missing hidden Ok for Result examples - doctest fails to compile. Fix: add
# Ok::<(), E>(())tail. - Async examples without runtime - doctests are sync unless using
asyncdoctest unstable features. Fix: useno_runor show sync API; link async guide. - Examples depend on env - flaky CI. Fix: mock or use fixed strings; avoid network in doctests.
- Too much setup in docs - noisy rendered output. Fix: hide with
#lines. - ignore everywhere - examples rot. Fix: prefer compilable snippets; reserve
ignorefor pseudocode.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
examples/ directory | Runnable multi-file demos | One-liner API proof |
| mdbook | Book-length narrative | Inline API rustdoc |
| Integration tests only | Complex Axum apps | Simple pure functions |
FAQs
Do private items get doctests?
Doctests run on public items by default. Private module docs can have doctests but are not shown in public docs.
How do I test async in doctests?
Prefer no_run with async block in docs or document with link to integration test. Full async doctests need nightly attributes.
Can doctests import external crates?
Yes if the crate is a dependency; dev-deps are not available to doctests on the lib.
Why doctest vs unit test?
Doctests document usage; unit tests cover edge cases without cluttering public docs.
How do I disable a doctest?
Use ```ignore or remove the code block; or #[doc(hidden)] on internal APIs.
Do workspaces run all doctests?
cargo test --doc --workspace runs per crate; filter with -p.
What about #[cfg] gated APIs?
Use ```cfg(feature = "foo") on the code block (rustdoc cfg) so examples match feature docs.
How do I show stderr output?
Doctests capture output; use unit tests for println! verification or document expected behavior in prose.
Can I name doctest functions?
Rustdoc wraps snippets in one function; split into multiple code blocks for multiple scenarios.
How does docs.rs run doctests?
docs.rs builds docs; doctest execution happens in your CI via cargo test --doc.
Related
- Testing Basics -
cargo test - Unit vs Integration Tests - other test types
- Documentation Lints -
missing_docs - Testing Best Practices - doc test policy
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.