๐Ÿฆ€ Functional Rust

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

Key Differences

ConceptOCamlRust
Executable doc examples`odoc` doesn't run examples by default`cargo test` always runs doc tests
Hidden setup linesNot supported`# use crate::foo;` โ€” `#` prefix hides line in rendered docs
Expected-panic testingManual `try`/`catch` in tests```` ```should_panic ```` attribute on the code block
Doc test discoveryRequires explicit `ocamldoc` setupAutomatic โ€” 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"