Rust Documentation Practices: Creating Clear, Comprehensive, and Useful Docs
Documentation is a crucial aspect of software development, serving as a bridge between code authors and users. Well-written documentation helps users understand how to use your code, explains why certain design decisions were made, and guides contributors on how to extend or modify your project. Rust takes documentation seriously, providing first-class tools and conventions that make it easy to create clear, comprehensive, and useful documentation directly alongside your code.
In this comprehensive guide, we’ll explore Rust’s documentation ecosystem, from inline doc comments to full-fledged documentation websites. You’ll learn how to write effective documentation, leverage Rust’s documentation tools, and follow best practices that have emerged in the Rust community. By the end, you’ll have a solid understanding of how to create documentation that enhances the quality and usability of your Rust projects, whether you’re working on a small library or a large-scale application.
Doc Comments and rustdoc
At the core of Rust’s documentation system are doc comments and the rustdoc tool:
Basic Doc Comments
/// A function that adds two numbers and returns the result.
///
/// # Examples
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// A struct representing a point in 2D space.
pub struct Point {
/// The x-coordinate of the point.
pub x: f64,
/// The y-coordinate of the point.
pub y: f64,
}
impl Point {
/// Creates a new point at the specified coordinates.
///
/// # Arguments
///
/// * `x` - The x-coordinate
/// * `y` - The y-coordinate
///
/// # Returns
///
/// A new `Point` instance.
pub fn new(x: f64, y: f64) -> Self {
Point { x, y }
}
/// Calculates the distance from this point to another point.
///
/// # Arguments
///
/// * `other` - The other point
///
/// # Returns
///
/// The Euclidean distance between the two points.
pub fn distance(&self, other: &Point) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
}
Module-Level Documentation
//! # Geometry Module
//!
//! This module provides types and functions for working with
//! geometric shapes and calculations.
//!
//! ## Features
//!
//! - 2D point representation
//! - Distance calculations
//! - Area and perimeter calculations for common shapes
pub mod point;
pub mod shapes;
Generating Documentation
# Generate documentation for your crate
cargo doc
# Generate documentation and open it in a browser
cargo doc --open
# Generate documentation including private items
cargo doc --document-private-items
Markdown Features
Rust’s doc comments support a rich set of Markdown features:
Headers and Lists
/// # User Authentication
///
/// This module handles user authentication and authorization.
///
/// ## Features
///
/// * Password hashing and verification
/// * Token generation and validation
/// * Role-based access control
///
/// ## Usage
///
/// 1. Create a new user with `create_user`
/// 2. Authenticate the user with `authenticate`
/// 3. Check permissions with `has_permission`
pub mod auth {
// Module contents
}
Code Examples
/// Parses a configuration string into a structured configuration object.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let config_str = "name=test;value=42";
/// let config = my_crate::parse_config(config_str);
/// assert_eq!(config.name, "test");
/// assert_eq!(config.value, 42);
/// ```
///
/// Handling errors:
///
/// ```
/// let invalid_config = "invalid config string";
/// let result = my_crate::parse_config(invalid_config);
/// assert!(result.is_err());
/// ```
pub fn parse_config(config_str: &str) -> Result<Config, ParseError> {
// Implementation
}
Links and References
/// Represents a database connection.
///
/// See the [`Connection::execute`] method for running SQL queries.
///
/// For more complex queries, use the [`query`](Connection::query) method.
///
/// # Related
///
/// * [`Transaction`] - For handling database transactions
/// * [`Pool`] - For connection pooling
pub struct Connection {
// Fields
}
impl Connection {
/// Executes an SQL statement.
///
/// See [SQLite documentation](https://www.sqlite.org/lang_expr.html)
/// for more information on SQL syntax.
pub fn execute(&self, sql: &str) -> Result<(), Error> {
// Implementation
}
/// Executes a query and returns the results.
pub fn query(&self, sql: &str) -> Result<QueryResult, Error> {
// Implementation
}
}
Documentation Best Practices
Based on experience from real-world Rust projects, here are some documentation best practices:
1. Document the “Why”, Not Just the “What”
/// Processes a batch of items with a specified chunk size.
///
/// # Arguments
///
/// * `items` - The items to process
/// * `chunk_size` - The size of each chunk
///
/// # Why Chunking?
///
/// Processing items in chunks helps to:
/// - Reduce memory usage by not loading all items at once
/// - Improve responsiveness by yielding to the event loop between chunks
/// - Allow for parallel processing of chunks
///
/// The default chunk size of 1000 was chosen based on benchmarks
/// showing optimal performance on typical workloads.
pub fn process_in_chunks<T>(items: &[T], chunk_size: usize) -> Result<(), Error> {
// Implementation
}
2. Include Examples for Different Use Cases
/// A builder for configuring HTTP requests.
///
/// # Examples
///
/// Basic GET request:
///
/// ```
/// let response = HttpRequestBuilder::new()
/// .method("GET")
/// .url("https://api.example.com/users")
/// .send()
/// .await?;
/// ```
///
/// POST request with JSON body:
///
/// ```
/// let response = HttpRequestBuilder::new()
/// .method("POST")
/// .url("https://api.example.com/users")
/// .header("Content-Type", "application/json")
/// .body(r#"{"name": "Alice", "email": "[email protected]"}"#)
/// .send()
/// .await?;
/// ```
///
/// Handling authentication:
///
/// ```
/// let response = HttpRequestBuilder::new()
/// .method("GET")
/// .url("https://api.example.com/protected")
/// .bearer_token("your-token-here")
/// .send()
/// .await?;
/// ```
pub struct HttpRequestBuilder {
// Fields
}
3. Document Error Cases
/// Reads a file and parses it as JSON.
///
/// # Arguments
///
/// * `path` - The path to the file
///
/// # Returns
///
/// The parsed JSON value.
///
/// # Errors
///
/// This function will return an error if:
///
/// - The file does not exist
/// - The process lacks permissions to read the file
/// - The file contains invalid UTF-8
/// - The file contains invalid JSON
///
/// # Examples
///
/// ```
/// let config = read_json_file("config.json")?;
/// ```
pub fn read_json_file(path: &str) -> Result<serde_json::Value, ReadJsonError> {
// Implementation
}
/// Errors that can occur when reading and parsing a JSON file.
#[derive(Debug, thiserror::Error)]
pub enum ReadJsonError {
/// An I/O error occurred.
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
/// The file contains invalid UTF-8.
#[error("UTF-8 error: {0}")]
Utf8(#[from] std::string::FromUtf8Error),
/// The file contains invalid JSON.
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
4. Use Consistent Formatting
/// Represents a user in the system.
///
/// # Fields
///
/// * `id` - The unique identifier for the user
/// * `username` - The user's username
/// * `email` - The user's email address
/// * `created_at` - When the user was created
///
/// # Examples
///
/// Creating a new user:
///
/// ```
/// let user = User {
/// id: 1,
/// username: "alice".to_string(),
/// email: "[email protected]".to_string(),
/// created_at: Utc::now(),
/// };
/// ```
pub struct User {
pub id: u64,
pub username: String,
pub email: String,
pub created_at: DateTime<Utc>,
}
/// Represents a role that can be assigned to users.
///
/// # Fields
///
/// * `id` - The unique identifier for the role
/// * `name` - The name of the role
/// * `permissions` - The permissions granted by this role
///
/// # Examples
///
/// Creating a new role:
///
/// ```
/// let role = Role {
/// id: 1,
/// name: "admin".to_string(),
/// permissions: vec!["read", "write", "delete"].into_iter().map(String::from).collect(),
/// };
/// ```
pub struct Role {
pub id: u64,
pub name: String,
pub permissions: HashSet<String>,
}
Testing Documentation
Rust’s documentation system includes built-in testing to ensure examples are correct:
Doctests
/// Reverses a string.
///
/// # Examples
///
/// ```
/// let reversed = my_crate::reverse_string("hello");
/// assert_eq!(reversed, "olleh");
/// ```
pub fn reverse_string(s: &str) -> String {
s.chars().rev().collect()
}
Running Doctests
# Run doctests
cargo test --doc
# Run a specific doctest
cargo test --doc reverse_string
Hiding Code in Doctests
/// Connects to a database.
///
/// # Examples
///
/// ```
/// # use my_crate::{Database, DatabaseConfig};
/// # let config = DatabaseConfig::new("localhost", 5432, "user", "password");
/// let db = Database::connect(config)?;
/// # Ok::<(), my_crate::Error>(())
/// ```
pub fn connect(config: DatabaseConfig) -> Result<Database, Error> {
// Implementation
}
Specifying Behavior for Doctests
/// This function always panics to demonstrate error handling.
///
/// # Examples
///
/// ```should_panic
/// my_crate::always_panics();
/// ```
pub fn always_panics() {
panic!("This function always panics");
}
/// This function is meant to be used in an async context.
///
/// # Examples
///
/// ```
/// # async fn run() -> Result<(), my_crate::Error> {
/// let result = my_crate::async_function().await?;
/// # Ok(())
/// # }
/// ```
pub async fn async_function() -> Result<String, Error> {
// Implementation
}
/// This code is not meant to be run, just to illustrate usage.
///
/// ```no_run
/// let result = my_crate::expensive_operation();
/// ```
pub fn expensive_operation() -> Result<(), Error> {
// Implementation
}
/// This is a compile-fail test to demonstrate a type error.
///
/// ```compile_fail
/// let x: String = 42; // Type error: expected String, found integer
/// ```
pub fn demonstrate_type_system() {
// Implementation
}
Crate-Level Documentation
Comprehensive crate-level documentation helps users understand your library:
README as Crate Documentation
//! # My Awesome Crate
//!
//! This crate provides utilities for working with awesome things.
//!
//! ## Features
//!
//! - Feature 1: Does something awesome
//! - Feature 2: Does something even more awesome
//!
//! ## Quick Start
//!
//! ```
//! use my_crate::do_awesome_thing;
//!
//! let result = do_awesome_thing();
//! assert!(result.is_awesome());
//! ```
//!
//! ## Modules
//!
//! - [`awesome`]: Core awesome functionality
//! - [`more_awesome`]: Even more awesome functionality
//!
//! ## License
//!
//! This project is licensed under the MIT License - see the LICENSE file for details.
pub mod awesome;
pub mod more_awesome;
pub use awesome::do_awesome_thing;
Using External Markdown Files
#![doc = include_str!("../README.md")]
// Rest of the crate
Advanced Documentation Features
Rust’s documentation system includes several advanced features:
Documentation Attributes
#[doc(hidden)]
pub fn internal_function() {
// This function won't appear in the generated documentation
}
#[doc(alias = "add")]
#[doc(alias = "sum")]
pub fn add_numbers(a: i32, b: i32) -> i32 {
// This function can be found by searching for "add" or "sum"
a + b
}
#[doc(cfg(feature = "advanced"))]
pub fn advanced_feature() {
// This function will be marked as requiring the "advanced" feature
}
Intra-Doc Links
/// A collection of items.
///
/// See [`Collection::add`] for adding items and [`Collection::remove`] for removing them.
pub struct Collection<T> {
items: Vec<T>,
}
impl<T> Collection<T> {
/// Creates a new, empty collection.
pub fn new() -> Self {
Collection { items: Vec::new() }
}
/// Adds an item to the collection.
///
/// # Examples
///
/// ```
/// use my_crate::Collection;
///
/// let mut collection = Collection::new();
/// collection.add(42);
/// ```
pub fn add(&mut self, item: T) {
self.items.push(item);
}
/// Removes an item from the collection.
///
/// Returns `true` if the item was found and removed.
///
/// See also: [`Collection::add`].
pub fn remove(&mut self, index: usize) -> Option<T> {
if index < self.items.len() {
Some(self.items.remove(index))
} else {
None
}
}
}
Documentation for Macros
/// Creates a new vector with the given elements.
///
/// # Examples
///
/// ```
/// let v = my_crate::vec![1, 2, 3];
/// assert_eq!(v, vec![1, 2, 3]);
/// ```
///
/// # Panics
///
/// This macro does not panic.
#[macro_export]
macro_rules! vec {
() => {
Vec::new()
};
($($x:expr),+ $(,)?) => {
{
let mut v = Vec::new();
$(v.push($x);)*
v
}
};
}
Documentation Organization
Organizing your documentation helps users find what they need:
Logical Module Structure
//! # API Module
//!
//! This module provides the public API for the crate.
/// Client for interacting with the API.
pub struct Client {
// Fields
}
pub mod endpoints;
pub mod models;
pub mod errors;
// Re-exports for convenience
pub use endpoints::*;
pub use models::*;
pub use errors::*;
Feature-Based Organization
//! # Features
//!
//! This crate provides several optional features:
//!
//! - `http`: HTTP client functionality
//! - `websocket`: WebSocket client functionality
//! - `json`: JSON serialization and deserialization
//! - `xml`: XML serialization and deserialization
#[cfg(feature = "http")]
pub mod http;
#[cfg(feature = "websocket")]
pub mod websocket;
#[cfg(feature = "json")]
pub mod json;
#[cfg(feature = "xml")]
pub mod xml;
Documentation Websites
For larger projects, a dedicated documentation website can be helpful:
Using mdBook
# Install mdBook
cargo install mdbook
# Create a new book
mdbook init my-book
# Build the book
cd my-book
mdbook build
# Serve the book locally
mdbook serve --open
Integrating API Documentation
# API Documentation
This project's API documentation is generated from the source code.
You can view the API documentation [here](./api/index.html).
## Key Types
- [`Client`](./api/struct.Client.html): The main client for interacting with the API
- [`Error`](./api/enum.Error.html): Error types returned by the API
# Generate API documentation
cargo doc --no-deps --target-dir book/src/api
Custom Documentation Themes
/* book/theme/css/custom.css */
:root {
--sidebar-width: 300px;
--page-padding: 15px;
--content-max-width: 750px;
--menu-bar-height: 50px;
--rust-orange: #f74c00;
}
.sidebar {
background-color: #f5f5f5;
}
h1, h2, h3, h4, h5, h6 {
color: var(--rust-orange);
}
# book.toml
[book]
title = "My Awesome Rust Project"
authors = ["Your Name"]
description = "Documentation for My Awesome Rust Project"
[output.html]
additional-css = ["theme/css/custom.css"]
git-repository-url = "https://github.com/username/my-project"
edit-url-template = "https://github.com/username/my-project/edit/main/docs/{path}"
Documentation for Different Audiences
Different users have different documentation needs:
User-Facing Documentation
/// # User Authentication
///
/// This module provides functions for authenticating users.
///
/// ## For API Users
///
/// If you're using this API, you'll primarily interact with:
///
/// - [`authenticate`]: Authenticate a user with username and password
/// - [`validate_token`]: Validate an authentication token
///
/// ## Example
///
/// ```
/// use my_crate::auth;
///
/// let token = auth::authenticate("username", "password")?;
/// // Use the token for subsequent requests
/// let is_valid = auth::validate_token(&token);
/// ```
pub mod auth {
// Module contents
}
Developer-Facing Documentation
/// Internal helper for password hashing.
///
/// # Note for Contributors
///
/// This function uses Argon2id for password hashing with the following parameters:
///
/// - Memory: 64 MiB
/// - Iterations: 3
/// - Parallelism: 4
///
/// These parameters were chosen based on the OWASP recommendations as of 2025.
///
/// # Security Considerations
///
/// When modifying this function, ensure that:
///
/// 1. The salt is always randomly generated
/// 2. The hash parameters meet current security standards
/// 3. The function never returns the original password
#[doc(hidden)]
pub(crate) fn hash_password(password: &str) -> Result<String, Error> {
// Implementation
}
Versioned Documentation
For libraries that evolve over time, versioned documentation is important:
Version-Specific Documentation
/// Processes a batch of items.
///
/// # Examples
///
/// ```
/// let items = vec![1, 2, 3];
/// my_crate::process_batch(&items);
/// ```
///
/// # Deprecated
///
/// This function is deprecated in favor of [`process_batch_with_options`].
///
/// Will be removed in version 2.0.0.
#[deprecated(
since = "1.5.0",
note = "Use process_batch_with_options instead"
)]
pub fn process_batch(items: &[i32]) {
process_batch_with_options(items, &Default::default());
}
/// Processes a batch of items with the specified options.
///
/// # Examples
///
/// ```
/// let items = vec![1, 2, 3];
/// let options = my_crate::BatchOptions::default();
/// my_crate::process_batch_with_options(&items, &options);
/// ```
///
/// # Available since
///
/// Version 1.5.0
pub fn process_batch_with_options(items: &[i32], options: &BatchOptions) {
// Implementation
}
Documenting Breaking Changes
//! # Changelog
//!
//! ## 2.0.0
//!
//! ### Breaking Changes
//!
//! - Removed `process_batch` function, use `process_batch_with_options` instead
//! - Changed `BatchOptions::new` to take required parameters
//! - `Error` enum now implements `std::error::Error` trait
//!
//! ### New Features
//!
//! - Added `parallel` option to `BatchOptions`
//! - Added `retry` functionality
//!
//! ## 1.5.0
//!
//! ### Deprecations
//!
//! - Deprecated `process_batch` function, will be removed in 2.0.0
//!
//! ### New Features
//!
//! - Added `process_batch_with_options` function
//! - Added `BatchOptions` struct
Best Practices for Documentation Maintenance
Keeping documentation up-to-date is as important as writing it in the first place:
Documentation Reviews
Include documentation in code reviews:
# Pull Request Checklist
- [ ] Code follows the project's style guidelines
- [ ] Tests have been added or updated
- [ ] Documentation has been added or updated
- [ ] Public API changes are documented
- [ ] Examples are up-to-date
- [ ] Deprecated features are marked
Documentation Tests in CI
# .github/workflows/docs.yml
name: Documentation
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Check documentation
run: cargo doc --no-deps --document-private-items
- name: Run doctests
run: cargo test --doc
Documentation Linting
# Install clippy
rustup component add clippy
# Run clippy with documentation lints
cargo clippy -- -W clippy::missing_docs_in_private_items -W clippy::doc_markdown
Conclusion
Documentation is a crucial aspect of software development in Rust. The language provides a rich set of tools and conventions for creating clear, comprehensive, and useful documentation directly alongside your code. By leveraging these tools and following best practices, you can create documentation that enhances the quality and usability of your Rust projects.
The key takeaways from this exploration of Rust documentation practices are:
- Doc comments provide a convenient way to write documentation alongside your code
- rustdoc generates beautiful HTML documentation from your doc comments
- Doctests ensure that your examples are correct and up-to-date
- Markdown features allow for rich formatting and organization
- Documentation best practices help you create documentation that is truly useful
Remember that good documentation is not just about describing what your code does, but also why it does it that way and how users can effectively work with it. By investing time in documentation, you’re not only helping others understand your code but also creating a valuable resource for your future self.