When copy-paste breaks your docs
You write a tutorial chapter. You paste a code snippet into your markdown. You publish the docs. Weeks later, you refactor the code. You fix a bug. You update the source. You forget the tutorial. The tutorial now shows broken code. A reader copies the snippet, gets an error, and assumes Rust is broken. You need the documentation to stay synchronized with the source code without manual copy-pasting.
The single source of truth
The {{#rustdoc_include}} directive solves this by pulling code from real files into your documentation at build time. You write the code in a Rust file. The directive tells the documentation builder to read that file and inject its contents into your markdown. If the code changes, the docs change automatically. The code in your docs is the code in your repository.
You can include an entire file or extract a specific region using anchor tags. Anchors let you grab a function, a loop, or a configuration block without exposing the rest of the file. This keeps examples focused while maintaining a single source of truth.
Minimal example
Create a file in your project structure. Write the code you want to show. Add the directive to your markdown file pointing at that file.
// listings/ch01-01-installation/src/main.rs
/// Entry point for the installation example.
fn main() {
// Print a greeting to verify the setup.
println!("Hello, world!");
}
In your markdown file, add the include directive. The path is relative to your project root.
{{#rustdoc_include listings/ch01-01-installation/src/main.rs}}
The builder reads listings/ch01-01-installation/src/main.rs. It injects the content as a code block. The output matches the file exactly.
Keep your docs honest. Let the code speak for itself.
How anchors work
Large files often contain more code than you want to show. Anchors let you extract a specific region. Wrap the code you want to include with // ANCHOR: tag and // ANCHOR_END: tag comments. Reference the tag in the directive using a colon.
// src/lib.rs
/// Sets up the configuration.
// ANCHOR: config_setup
fn setup_config() {
// Load environment variables.
let env = load_env();
// Parse the configuration file.
let config = parse_config(&env);
config
}
// ANCHOR_END: config_setup
/// Runs the application loop.
// ANCHOR: app_loop
fn run_app(config: Config) {
// Start the server.
let server = start_server(config);
// Wait for shutdown signal.
server.wait();
}
// ANCHOR_END: app_loop
Reference the anchor in the markdown. The builder extracts only the region between the anchor comments.
{{#rustdoc_include src/lib.rs:config_setup}}
The output contains only the setup_config function and its surrounding anchor comments. The run_app function stays hidden.
The directive is a promise. The text in your docs is the text in your code.
Testing your examples
Code in included files is real code. You can test it. This is the biggest advantage over inline code blocks. Inline blocks are just text. The builder might not compile them. Included files live in your project structure. You can add Cargo.toml files. You can run cargo test.
Structure your listings directory as a Cargo workspace. Each example gets its own crate. Add tests to verify behavior. When the build runs, the tests run. If a test fails, the build fails. Broken examples never reach readers.
// listings/ch02-variables/src/main.rs
/// Demonstrates variable mutability.
fn main() {
// Immutable by default.
let x = 5;
// Mutable requires keyword.
let mut y = 10;
y = 15;
}
#[cfg(test)]
mod tests {
// Verify mutability rules compile correctly.
#[test]
fn test_mutability() {
let x = 5;
let mut y = 10;
y = 15;
assert_eq!(y, 15);
}
}
Run cargo test in the listings directory. The tests pass. The docs show code that works.
Test your examples. If the code doesn't compile, the docs shouldn't publish.
Realistic workflow
A typical documentation project uses a listings directory to hold all examples. Each chapter gets a subdirectory. Each subdirectory contains a src folder with Rust files. The markdown files live alongside or reference these listings.
listings/
ch01-00-intro/
src/
main.rs
ch02-variables/
src/
main.rs
Cargo.toml
The Cargo.toml at the root of listings can define a workspace. This lets you run tests for all examples at once.
# listings/Cargo.toml
[workspace]
members = [
"ch01-00-intro",
"ch02-variables",
]
In your markdown, reference the listings. Use anchors to keep examples concise.
## Variables
Rust variables are immutable by default.
{{#rustdoc_include listings/ch02-variables/src/main.rs:main}}
To make a variable mutable, add the `mut` keyword.
{{#rustdoc_include listings/ch02-variables/src/main.rs:mut_example}}
This structure scales. You can add hundreds of examples. The build process keeps them all in sync. The tests keep them all correct.
Convention aside: the listings directory name is a community standard for book-style documentation. It signals that the code inside is meant for inclusion, not for direct execution by the reader. Stick to this name to match reader expectations.
Organize your listings. A flat structure becomes unmanageable fast.
Pitfalls and errors
Path resolution is the most common issue. The directive expects a path relative to the project root. If you move the markdown file, the path might break. The builder rejects the build with a "File not found" error. Check the path. Ensure the file exists.
Anchor mismatches cause build failures. If you type the anchor tag wrong, or if you delete the anchor from the source, the builder cannot extract the region. The builder rejects the build with an "Anchor not found" error. Verify the tag name matches exactly. Case matters.
Formatting can drift. If you edit the source file manually without running cargo fmt, the included code might have inconsistent indentation. The docs will show messy code. Run cargo fmt on your listings before building. The community expects consistent formatting.
Nested includes are usually not supported. The directive reads a file and injects text. It does not recursively process directives inside the included file. Keep includes flat.
Trust the path. If the builder can't find the file, your readers can't run the example.
Decision matrix
Use {{#rustdoc_include}} when the example must compile and match the source code exactly. Use {{#rustdoc_include}} with anchors when you have a large file but only need a specific function or block in the docs. Reach for inline code blocks when the example is trivial, pseudo-code, or depends on context that doesn't exist in a real file. Pick inline blocks for snippets that change frequently and don't need to be tested as part of the build. Use a listings directory structure when you have many examples and want to run tests across all of them.
Single source of truth beats copy-paste every time.