Code Examples
Code examples are the core of technical documentation. Readers trust your prose because your code works. This chapter covers creating, formatting, and maintaining code examples that are accurate, instructive, and maintainable.
Principles of Good Code Examples
Complete over clever. A reader should be able to copy an example and run it. Include imports, setup, and necessary context. An elegant one-liner that requires invisible context is less useful than a verbose example that works.
Realistic over minimal. Examples should resemble real code readers will write. Use realistic variable names, realistic data, and realistic error handling. Minimal examples teach syntax; realistic examples teach practice.
Focused over comprehensive. Each example should illustrate one concept. Avoid combining multiple new ideas in a single example. If an example requires understanding A, B, and C, ensure A and B were introduced in previous examples.
Consistent over varied. Use the same patterns throughout the book. If chapter 3 uses result as a variable name for operation results, chapter 7 should too. Variation confuses readers who are pattern-matching.
Code Block Formatting
mdBook uses standard Markdown fenced code blocks with language identifiers:
```rust
fn main() {
println!("Hello, world!");
}
```
Always specify the language. This enables syntax highlighting and signals the technology to readers.
Supported Language Identifiers
Common identifiers:
| Language | Identifier |
|---|---|
| Rust | rust |
| JavaScript | javascript or js |
| TypeScript | typescript or ts |
| Python | python or py |
| Go | go |
| Java | java |
| C | c |
| C++ | cpp |
| Shell/Bash | bash or shell |
| JSON | json |
| YAML | yaml |
| TOML | toml |
| SQL | sql |
| HTML | html |
| CSS | css |
For configuration or output that isn't a programming language, use text or omit the identifier.
Line Highlighting
mdBook supports line highlighting to draw attention to specific parts:
```rust,hl_lines=3 4
fn main() {
let name = "World";
// These lines are highlighted
println!("Hello, {}!", name);
}
```
Use highlighting to focus attention on new concepts within familiar boilerplate.
Hiding Lines
Hide setup code that distracts from the lesson:
```rust
# fn main() {
let x = 5;
println!("{}", x);
# }
```
Lines starting with # are hidden in the output but included in compilation. This keeps examples focused while remaining complete.
Prompting for Code Examples
When requesting code examples from Claude Code, be specific:
Weak prompt:
Show how to handle errors.
Strong prompt:
Create a code example showing HTTP error handling in [framework].
Requirements:
- Complete, runnable example
- Include imports
- Handle both client errors (4xx) and server errors (5xx)
- Show the custom error type definition
- Include a handler function that returns these errors
- Use realistic error messages
Context: This follows an example showing basic request handling.
The reader hasn't seen error handling yet.
Requesting Incremental Examples
For complex topics, request a series of examples that build on each other:
Create a progression of 4 code examples teaching async/await:
Example 1: Single async function call
- Simplest possible async operation
- Show the await point explicitly
Example 2: Sequential async calls
- Two operations that must happen in order
- Demonstrate value passing between awaits
Example 3: Concurrent async calls
- Two operations that can run simultaneously
- Show the concurrency primitive used
Example 4: Error handling in async code
- Async operation that can fail
- Proper error propagation
Each example should be complete and runnable.
Each should build on concepts from the previous example.
Code Example Types
Conceptual Examples
Illustrate a concept in isolation:
#![allow(unused)] fn main() { // Ownership moves when assigned let s1 = String::from("hello"); let s2 = s1; // s1 is no longer valid // This would fail to compile: // println!("{}", s1); // error: value borrowed after move }
Conceptual examples can be short. Their purpose is understanding, not replication.
Procedural Examples
Show how to accomplish a task:
use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; fn read_lines<P>(filename: P) -> io::Result<Vec<String>> where P: AsRef<Path>, { let file = File::open(filename)?; let reader = io::BufReader::new(file); reader.lines().collect() } fn main() -> io::Result<()> { let lines = read_lines("input.txt")?; for line in lines { println!("{}", line); } Ok(()) }
Procedural examples should be complete and directly usable.
Anti-Pattern Examples
Show what not to do:
#![allow(unused)] fn main() { // DON'T: This creates a race condition let counter = Rc::new(RefCell::new(0)); let c1 = counter.clone(); let c2 = counter.clone(); thread::spawn(move || { *c1.borrow_mut() += 1; // Not thread-safe! }); thread::spawn(move || { *c2.borrow_mut() += 1; // Not thread-safe! }); }
Always label anti-patterns clearly. Follow with the correct approach:
#![allow(unused)] fn main() { // DO: Use Arc and Mutex for thread-safe shared state let counter = Arc::new(Mutex::new(0)); let c1 = counter.clone(); let c2 = counter.clone(); thread::spawn(move || { let mut num = c1.lock().unwrap(); *num += 1; }); thread::spawn(move || { let mut num = c2.lock().unwrap(); *num += 1; }); }
Before/After Examples
Show refactoring improvements:
Before:
#![allow(unused)] fn main() { fn process(data: Option<String>) -> String { if data.is_some() { let s = data.unwrap(); if s.len() > 0 { s.to_uppercase() } else { "empty".to_string() } } else { "none".to_string() } } }
After:
#![allow(unused)] fn main() { fn process(data: Option<String>) -> String { match data { Some(s) if !s.is_empty() => s.to_uppercase(), Some(_) => "empty".to_string(), None => "none".to_string(), } } }
Before/after pairs make improvements concrete and memorable.
External Code Inclusion
For longer examples, store code in separate files and include it:
```rust
{{#include ../examples/ch05/complete_server.rs}}
```
The {{#include path}} directive pulls the entire file into the code block.
Including Specific Sections
Use anchor comments to include portions:
In complete_server.rs:
fn main() { // ANCHOR: setup let config = Config::load(); let db = Database::connect(&config.database_url); // ANCHOR_END: setup // ANCHOR: server let server = Server::new(config.port); server.run(db); // ANCHOR_END: server }
In your markdown, reference anchors with {{#include path:anchor}}:
First, set up the configuration:
```rust
{{#include ../examples/ch05/complete_server.rs:setup}}
```
Then start the server:
```rust
{{#include ../examples/ch05/complete_server.rs:server}}
```
This keeps examples maintainable while showing only relevant portions.
Validating Code Examples
Code examples must work. Establish a validation process:
Inline Validation
For short examples, test manually during review:
I've written a code example for error handling. Before I include
it in the chapter, verify it compiles and runs correctly.
[paste example]
If there are issues, fix them and explain what was wrong.
Automated Validation
For books with many examples, create a validation script:
#!/bin/bash
# scripts/validate-examples.sh
set -e
echo "Validating Rust examples..."
for dir in examples/*/; do
if [ -f "$dir/Cargo.toml" ]; then
echo "Checking $dir"
(cd "$dir" && cargo check)
fi
done
echo "All examples valid!"
Run validation before committing and as part of CI.
Testing Examples
Where possible, add tests to example code:
#![allow(unused)] fn main() { fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_add() { assert_eq!(add(2, 2), 4); assert_eq!(add(-1, 1), 0); } } }
Tests prove examples work and catch regressions when updating.
Common Code Example Problems
Missing Imports
Problem: Examples assume imports exist.
// Won't compile alone fn main() { let file = File::open("test.txt")?; // File not imported }
Solution: Always include necessary imports or use hidden lines:
use std::fs::File; use std::io; fn main() -> io::Result<()> { let file = File::open("test.txt")?; Ok(()) }
Unrealistic Error Handling
Problem: Examples use .unwrap() everywhere.
fn main() { let file = File::open("config.json").unwrap(); let config: Config = serde_json::from_reader(file).unwrap(); // ... }
Solution: Show proper error handling for examples readers will copy:
fn main() -> Result<(), Box<dyn Error>> { let file = File::open("config.json") .context("Failed to open config file")?; let config: Config = serde_json::from_reader(file) .context("Failed to parse config")?; // ... Ok(()) }
Reserve .unwrap() for examples explicitly teaching happy-path concepts.
Context-Dependent Examples
Problem: Example requires context established elsewhere.
#![allow(unused)] fn main() { // What is `db`? Where does it come from? let users = db.query("SELECT * FROM users")?; }
Solution: Show context or use comments:
#![allow(unused)] fn main() { // Assuming `db` is a database connection from setup code // (see Chapter 3 for database setup) let users = db.query("SELECT * FROM users")?; }
Obsolete Examples
Problem: API changes make examples outdated.
Solution: Document the version. Use Cargo.toml in example projects:
[dependencies]
some-crate = "2.1" # Examples tested with this version
Requesting Code Reviews from Claude Code
After writing examples, request review:
Review all code examples in chapter 5. For each example, check:
1. Does it compile without modification?
2. Are all imports shown or properly hidden?
3. Does error handling match our book's conventions?
4. Would copying this example actually teach the intended concept?
5. Are variable names consistent with earlier chapters?
List any issues found with specific corrections.
Address issues before finalizing the chapter.
Next Steps
With solid code examples in place, the next chapter covers quality control — systematic approaches to ensuring comprehensive, accurate documentation.