746: Documentation Tests: rustdoc Examples
Difficulty: 2 Level: Intermediate Code examples in `///` doc comments are compiled and executed as tests โ documentation can never go out of date.The Problem This Solves
API documentation that lies is worse than no documentation. In most languages, code examples in docs are prose โ they drift as the API evolves and nobody notices until a confused user opens an issue. Rust solves this at the tooling level: every ```` ```rust ```` block in a `///` comment is compiled and run as a test by `cargo test`. If the example breaks, the build breaks. This makes doc tests the ideal place to show intended usage โ they serve as both human-readable documentation and machine-verified examples. The pattern is especially valuable for public library APIs where the examples in the crate documentation become the first thing users try to copy-paste. Doc tests also catch a subtle class of regression: API that compiles but produces wrong output. By `assert_eq!`-ing the expected values, you're testing behavior, not just compilation. Combined with regular unit tests in `#[cfg(test)]` modules, doc tests provide a complementary layer focused on user-facing behavior.The Intuition
Rust's `rustdoc` parses `///` comments, extracts code blocks, wraps each one in a test harness, and compiles them as if they were in a `#[test]` function. The `# use example::func;` lines (prefixed with `#`) are included in compilation but hidden in rendered documentation โ a clean way to add setup without cluttering the visible example. `cargo test` runs doc tests alongside unit tests.How It Works in Rust
/// Clamps `x` to the inclusive range `[lo, hi]`.
///
/// # Examples
///
/// ```
/// # use example::clamp; // hidden in docs, needed for compilation
/// assert_eq!(clamp(0, 10, -5), 0);
/// assert_eq!(clamp(0, 10, 5), 5);
/// assert_eq!(clamp(0, 10, 15), 10);
/// ```
pub fn clamp(lo: i32, hi: i32, x: i32) -> i32 {
x.max(lo).min(hi)
}
/// Panics on invalid input โ shown with `should_panic`.
///
/// ```should_panic
/// example::factorial(0); // this line panics โ test passes if it does!
/// ```
pub fn factorial(n: u64) -> u64 {
if n == 0 { panic!("factorial(0) is undefined") }
(1..=n).product()
}
/// Returns `Err` if divisor is zero.
///
/// ```
/// # use example::safe_div;
/// assert_eq!(safe_div(10, 2), Ok(5));
/// assert_eq!(safe_div(10, 0), Err("division by zero"));
/// ```
pub fn safe_div(a: i64, b: i64) -> Result<i64, &'static str> {
if b == 0 { Err("division by zero") } else { Ok(a / b) }
}
Key patterns: `# use ...` imports are hidden but required; `should_panic` tests that panicking behavior is documented AND verified; `Ok(5)` and `Err(...)` in assertions test the full return type shape.
What This Unlocks
- Self-verifying API docs โ every public function's usage example is a live test; refactoring is safe because broken examples fail `cargo test`.
- Complementary test layers โ doc tests cover "does this do what it says on the tin?"; unit tests in `#[cfg(test)]` cover edge cases and internals; together they give full coverage of behavior.
- `should_panic` and `compile_fail` โ doc tests support special attributes (`should_panic`, `compile_fail`, `no_run`) that let you document and verify panic conditions, type errors, and untestable-at-runtime behavior.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Executable doc examples | `odoc` doesn't run examples by default | `cargo test` always runs doc tests |
| Hidden setup lines | Not supported | `# use crate::foo;` โ `#` prefix hides line in rendered docs |
| Expected-panic testing | Manual `try`/`catch` in tests | ```` ```should_panic ```` attribute on the code block |
| Doc test discovery | Requires explicit `ocamldoc` setup | Automatic โ any `///` block is found by `rustdoc` |
/// 746: Documentation Tests โ rustdoc examples that are compiled and run
/// Clamps `x` to the inclusive range `[lo, hi]`.
///
/// # Examples
///
/// ```
/// # use example::clamp;
/// assert_eq!(clamp(0, 10, -5), 0);
/// assert_eq!(clamp(0, 10, 5), 5);
/// assert_eq!(clamp(0, 10, 15), 10);
/// // Boundaries are inclusive
/// assert_eq!(clamp(0, 10, 0), 0);
/// assert_eq!(clamp(0, 10, 10), 10);
/// ```
pub fn clamp(lo: i32, hi: i32, x: i32) -> i32 {
x.max(lo).min(hi)
}
/// Repeats `s` exactly `n` times.
///
/// # Examples
///
/// ```
/// # use example::repeat;
/// assert_eq!(repeat("ab", 3), "ababab");
/// assert_eq!(repeat("x", 0), "");
/// assert_eq!(repeat("", 5), "");
/// ```
pub fn repeat(s: &str, n: usize) -> String {
s.repeat(n)
}
/// Splits `s` on the first occurrence of `delim`.
///
/// Returns `None` if `delim` is not found.
///
/// # Examples
///
/// ```
/// # use example::split_once_char;
/// assert_eq!(split_once_char("key:value", ':'), Some(("key", "value")));
/// assert_eq!(split_once_char("no-delim", ':'), None);
/// assert_eq!(split_once_char("a:b:c", ':'), Some(("a", "b:c")));
/// ```
pub fn split_once_char(s: &str, delim: char) -> Option<(&str, &str)> {
s.split_once(delim)
}
/// Returns `Err` if divisor is zero.
///
/// # Errors
///
/// Returns `Err("division by zero")` when `b == 0`.
///
/// # Examples
///
/// ```
/// # use example::safe_div;
/// assert_eq!(safe_div(10, 2), Ok(5));
/// assert_eq!(safe_div(10, 0), Err("division by zero"));
/// assert_eq!(safe_div(-9, 3), Ok(-3));
/// ```
pub fn safe_div(a: i64, b: i64) -> Result<i64, &'static str> {
if b == 0 { Err("division by zero") } else { Ok(a / b) }
}
/// Panics on invalid input โ shown in doc test with `should_panic`.
///
/// # Panics
///
/// Panics if `n` is zero.
///
/// ```should_panic
/// example::factorial(0); // panics!
/// ```
///
/// # Examples
///
/// ```
/// # use example::factorial;
/// assert_eq!(factorial(1), 1);
/// assert_eq!(factorial(5), 120);
/// ```
pub fn factorial(n: u64) -> u64 {
if n == 0 { panic!("factorial(0) is undefined in this implementation") }
(1..=n).product()
}
fn main() {
println!("clamp(0,10,-5) = {}", clamp(0, 10, -5));
println!("repeat(\"hi\",3) = {}", repeat("hi", 3));
println!("split_once_char(\"k:v\",':') = {:?}", split_once_char("k:v", ':'));
println!("safe_div(10,2) = {:?}", safe_div(10, 2));
println!("safe_div(10,0) = {:?}", safe_div(10, 0));
println!("factorial(5) = {}", factorial(5));
}
// Regular unit tests complement doc tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clamp_edge_cases() {
assert_eq!(clamp(i32::MIN, i32::MAX, 0), 0);
assert_eq!(clamp(5, 5, 100), 5); // lo == hi
}
#[test]
fn repeat_unicode() {
assert_eq!(repeat("๐ฆ", 3), "๐ฆ๐ฆ๐ฆ");
}
#[test]
fn safe_div_negative() {
assert_eq!(safe_div(-10, -2), Ok(5));
}
}
(* 746: Documentation Tests โ OCaml odoc examples
ODDoc supports @example annotations but they are NOT executed.
This is a key advantage Rust has over OCaml. *)
(**
Clamp a value within [lo, hi].
@example
{[
let _ = clamp 0 10 (-5) (* = 0 *)
let _ = clamp 0 10 5 (* = 5 *)
let _ = clamp 0 10 15 (* = 10 *)
]}
*)
let clamp lo hi x = max lo (min hi x)
(**
Repeat a string n times.
@example
{[
let _ = repeat "ab" 3 (* = "ababab" *)
let _ = repeat "x" 0 (* = "" *)
]}
*)
let repeat s n =
let buf = Buffer.create (String.length s * n) in
for _ = 1 to n do Buffer.add_string buf s done;
Buffer.contents buf
(**
Split a string by delimiter (first occurrence).
@example
{[
let _ = split_once ':' "key:value" (* = Some ("key", "value") *)
let _ = split_once ':' "nodelim" (* = None *)
]}
*)
let split_once delim s =
match String.index_opt s delim with
| None -> None
| Some i ->
let left = String.sub s 0 i in
let right = String.sub s (i + 1) (String.length s - i - 1) in
Some (left, right)
let () =
assert (clamp 0 10 (-5) = 0);
assert (repeat "ab" 3 = "ababab");
assert (split_once ':' "key:value" = Some ("key", "value"));
Printf.printf "OCaml examples verified (manually โ odoc doesn't run them)\n"