746-doc-test-patterns — Doc Test Patterns
Tutorial Video
Text description (accessibility)
This video demonstrates the "746-doc-test-patterns — Doc Test Patterns" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Documentation that diverges from reality is worse than no documentation: it misleads users and erodes trust. Key difference from OCaml: 1. **First
Tutorial
The Problem
Documentation that diverges from reality is worse than no documentation: it misleads users and erodes trust. Rust's doc tests solve this by compiling and running every /// code example as a test. If you change the function signature or behavior, the doc test fails — documentation can never silently go out of date. This is used pervasively in the Rust standard library, the serde crate, and virtually every published Rust crate.
🎯 Learning Outcomes
/// doc comments using fenced ` blockscargo test runs all doc examples as real test cases# prefix to hide boilerplate lines in rendered docs while keeping them in the testno_run (compiles but skips execution) or ignore (skips entirely)Code Example
/// Clamps `x` to the inclusive range `[lo, hi]`.
///
/// # Examples
///
///Key Differences
cargo test --doc runs doc examples with no additional tooling; OCaml requires mdx or external tooling.mdx interprets examples via the REPL without full type checking.#-prefix hides boilerplate while keeping it executable; OCaml's mdx has no equivalent line-hiding syntax.cargo test output alongside unit tests; OCaml doc tests require a separate mdx invocation.OCaml Approach
OCaml does not have a built-in doc-test runner. The mdx (Markdown eXtended) tool runs OCaml code blocks in markdown documentation. ocamldoc generates HTML from (** ... *) comments but does not execute examples. The doctests opam package provides pytest-style doc testing. Jane Street uses expect_test for inline tests with expected output embedded in source comments.
Full Source
#![allow(clippy::all)]
//! # Documentation Tests
//!
//! Code examples in `///` doc comments are compiled and executed as tests.
//! Documentation can never go out of date.
/// Clamps `x` to the inclusive range `[lo, hi]`.
///
/// # Examples
///
/// ```
/// use example_746_doc_test_patterns::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_746_doc_test_patterns::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_746_doc_test_patterns::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)
}
/// Safe division that returns `Err` if divisor is zero.
///
/// # Errors
///
/// Returns `Err("division by zero")` when `b == 0`.
///
/// # Examples
///
/// ```
/// use example_746_doc_test_patterns::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)
}
}
/// Computes factorial of n.
///
/// # Panics
///
/// Panics if `n` is zero (this implementation treats 0! as undefined).
///
/// # Examples
///
/// ```
/// use example_746_doc_test_patterns::factorial;
/// assert_eq!(factorial(1), 1);
/// assert_eq!(factorial(5), 120);
/// ```
///
/// Attempting to compute factorial(0) will panic:
///
/// ```should_panic
/// example_746_doc_test_patterns::factorial(0);
/// ```
pub fn factorial(n: u64) -> u64 {
if n == 0 {
panic!("factorial(0) is undefined in this implementation")
}
(1..=n).product()
}
/// Alternative factorial that handles 0 correctly.
///
/// # Examples
///
/// ```
/// use example_746_doc_test_patterns::factorial_safe;
/// assert_eq!(factorial_safe(0), 1);
/// assert_eq!(factorial_safe(1), 1);
/// assert_eq!(factorial_safe(5), 120);
/// assert_eq!(factorial_safe(10), 3628800);
/// ```
pub fn factorial_safe(n: u64) -> u64 {
if n == 0 {
1
} else {
(1..=n).product()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clamp_edge_cases() {
assert_eq!(clamp(i32::MIN, i32::MAX, 0), 0);
assert_eq!(clamp(5, 5, 100), 5); // lo == hi
}
#[test]
fn test_repeat_unicode() {
assert_eq!(repeat("🦀", 3), "🦀🦀🦀");
}
#[test]
fn test_safe_div_negative() {
assert_eq!(safe_div(-10, -2), Ok(5));
}
#[test]
fn test_split_empty_string() {
assert_eq!(split_once_char("", ':'), None);
}
#[test]
fn test_factorial_safe_large() {
assert_eq!(factorial_safe(12), 479001600);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clamp_edge_cases() {
assert_eq!(clamp(i32::MIN, i32::MAX, 0), 0);
assert_eq!(clamp(5, 5, 100), 5); // lo == hi
}
#[test]
fn test_repeat_unicode() {
assert_eq!(repeat("🦀", 3), "🦀🦀🦀");
}
#[test]
fn test_safe_div_negative() {
assert_eq!(safe_div(-10, -2), Ok(5));
}
#[test]
fn test_split_empty_string() {
assert_eq!(split_once_char("", ':'), None);
}
#[test]
fn test_factorial_safe_large() {
assert_eq!(factorial_safe(12), 479001600);
}
}
Deep Comparison
OCaml vs Rust: Documentation Tests
Doc Comment Examples
OCaml (odoc)
(**
Clamp a value within [lo, hi].
@example
{[
let _ = clamp 0 10 (-5) (* = 0 *)
let _ = clamp 0 10 5 (* = 5 *)
]}
*)
let clamp lo hi x = max lo (min hi x)
Note: OCaml's odoc does NOT execute these examples.
Rust (rustdoc)
/// Clamps `x` to the inclusive range `[lo, hi]`.
///
/// # Examples
///
///
/// use my_crate::clamp; /// assert_eq!(clamp(0, 10, -5), 0); /// assert_eq!(clamp(0, 10, 5), 5); ///
pub fn clamp(lo: i32, hi: i32, x: i32) -> i32 {
x.max(lo).min(hi)
}
**These examples are compiled and run by cargo test!**
Hidden Setup Lines (Rust Only)
/// # Examples
///
///
/// # use my_crate::helper; // hidden in rendered docs /// let result = helper(); /// assert!(result.is_ok()); ///
The # prefix includes the line in compilation but hides it in documentation.
Testing Panics
OCaml
let test_panic () =
try
let _ = factorial 0 in
failwith "expected exception"
with Invalid_argument _ -> ()
Rust
/// should_panic
/// my_crate::factorial(0); // this line panics
///
Key Differences
| Feature | OCaml | Rust |
|---|---|---|
| Doc examples executed | ❌ No | ✅ Yes, by cargo test |
| Hidden setup lines | ❌ No | ✅ # use ... syntax |
| Panic testing | Manual try/catch | should_panic attribute |
| Compile-fail tests | ❌ No | ✅ compile_fail attribute |
| Discovery | Manual odoc setup | Automatic with rustdoc |
Why Rust's Approach Is Better
should_panic proves the documented behaviorExercises
/// doc example to split_once_char that shows the ? operator usage in a function that returns Option, including the hidden fn main() -> Option<()> wrapper.parse_ip function with doc examples for IPv4, IPv6, and malformed inputs, using no_run for any example that requires network access.Examples section to a Config::from_str function with a multi-step example that shows parsing, validation, and accessing fields — verify it compiles and runs with cargo test --doc.