Writing Good Rust Documentation
This consolidated skill covers all aspects of writing high-quality rustdoc:
- •Structure - Inverted pyramid principle
- •Links - Intra-doc link patterns
- •Constants - Human-readable numeric literals
- •Formatting - Markdown tables and cargo rustdoc-fmt
When to Use
Proactively (While Writing Code)
- •Writing new code that includes
///or//!doc comments - •Creating new modules, traits, structs, or functions
- •Adding links to other types or modules in documentation
- •Defining byte/u8 constants
Retroactively (Fixing Issues)
- •
/fix-intradoc-links- Fix broken links, convert inline to reference-style - •
/fix-comments- Fix constant conventions in doc comments - •
/fix-md-tables- Fix markdown table formatting - •
/docs- Full documentation check and fix
Voice & Tone
r3bl is serious & meaningful & precise. r3bl is also fun.
Documentation should be rigorous about content, playful about presentation:
| Aspect | Serious & Precise | Fun |
|---|---|---|
| Technical accuracy | Correct terminology, proper distinctions | — |
| Links | Intra-doc links, authoritative sources | — |
| Visual aids | ASCII diagrams, tables | Emoji for scannability |
| Language | Clear, unambiguous | Literary references, personality |
Examples
Emoji for visual scanning (semantic, not decorative):
//! 🐧 **Linux**: Uses `epoll` for I/O multiplexing //! 🍎 **macOS**: Uses `kqueue` (with PTY limitations) //! 🪟 **Windows**: Uses IOCP for async I/O
Severity with visual metaphors:
//! 1. 🐢 **Multi-threaded runtime**: Reduced throughput but still running //! 2. 🧊 **Single-threaded runtime**: Total blockage — nothing else runs
Literary references with layered meaning:
//! What's in a name? 😛 The three core properties:
The 😛 is a visual pun on "tongue in cheek" — Shakespeare's Juliet argues names don't matter, but here we use the quote to explain why RRT's name does matter. The emoji signals the irony.
Rule: Emoji must have semantic meaning (OS icons, severity levels). Never use random 🚀✨🎉 for "excitement."
Part 1: Structure (Inverted Pyramid)
Structure documentation with high-level concepts at the top, details below:
╲────────────╱
╲ ╱ High-level concepts - Module/trait/struct documentation
╲────────╱
╲ ╱ Mid-level details - Method group documentation
╲────╱
╲ ╱ Low-level specifics - Individual method documentation
╲╱
Avoid making readers hunt through method docs for the big picture.
Placement Guidelines
| Level | What to Document | Example Style |
|---|---|---|
| Module/Trait | Why, when, conceptual examples, workflows, ASCII diagrams | Comprehensive |
| Method | How to call, exact types, parameters | Brief (IDE tooltips) |
Reference Up, Not Down
/// See the [module-level documentation] for complete usage examples.
///
/// [module-level documentation]: mod@crate::example
pub fn some_method(&self) -> Result<()> { /* ... */ }
Part 2: Intra-doc Links
Golden Rules
- •Use
crate::paths (notsuper::) - absolute paths are stable - •Use reference-style links - keep prose clean
- •Place all link definitions at bottom of comment block
- •Include
()for functions/methods - distinguishes from types
Link Source Priority
When deciding local vs external links, follow this priority:
| Priority | Source | Link Style | Example |
|---|---|---|---|
| 1 | Code in this monorepo | crate:: path | [Foo]: crate::module::Foo |
| 2 | Dependency in Cargo.toml | Crate path | [mio]: mio |
| 3 | OS/CS/hardware terms | External URL | [epoll]: https://man7.org/... |
| 4 | Non-dependency crates | docs.rs URL | [rayon]: https://docs.rs/rayon |
Key principle: If it's in Cargo.toml, use local links (validated, offline-capable, version-matched).
Link All Symbols for Refactoring Safety
Every codebase symbol in backticks must be a link. This isn't just style—it's safety.
When you rename, move, or delete a symbol:
- •With links:
cargo docfails with a clear error pointing to the stale reference - •Without links: The docs silently rot, referencing symbols that no longer exist
| Docs say | Symbol renamed to | With link | Without link |
|---|---|---|---|
[`Parser`] | Tokenizer | ❌ Build error | ✅ Silently stale |
[`process()`] | handle() | ❌ Build error | ✅ Silently stale |
Rule: If it's a symbol from your codebase and it's in backticks, make it a link.
// ❌ Bad: Will silently rot when Parser is renamed /// Uses `Parser` for tokenization. // ✅ Good: cargo doc will catch if Parser is renamed /// Uses [`Parser`] for tokenization. /// /// [`Parser`]: crate::Parser
Quick Reference
| Link To | Pattern |
|---|---|
| Struct | [Foo]: crate::Foo |
| Function | [process()]: crate::process |
| Method | [run()]: Self::run |
| Module | [parser]: mod@crate::parser |
| Section heading | [docs]: mod@crate::module#section-name |
| Dependency crate | [tokio::spawn()]: tokio::spawn |
✅ Good: Reference-Style Links
/// This struct uses [`Position`] to track cursor location. /// /// The [`render()`] method updates the display. /// /// [`Position`]: crate::Position /// [`render()`]: Self::render
❌ Bad: Inline Links
/// This struct uses [`Position`](crate::Position) to track cursor location.
❌ Bad: No Links
/// This struct uses `Position` to track cursor location.
Linking to Dependency Crates
For crates listed in your Cargo.toml dependencies, use direct intra-doc links instead of
external hyperlinks to docs.rs. Rustdoc automatically resolves these when the dependency is built.
| Link To | Pattern |
|---|---|
| Crate root | [crossterm]: ::crossterm |
| Type in crate | [mio::Poll]: mio::Poll |
| Function in crate | [tokio::io::stdin()]: tokio::io::stdin |
| Macro in crate | [tokio::select!]: tokio::select |
✅ Good: Direct Dependency Links
//! **UI freezes** on terminal resize when using [`tokio::io::stdin()`]. //! Internally, cancelling a [`tokio::select!`] branch doesn't stop the read. //! However, the use of [Tokio's stdin] caused the first two issues. //! //! [`tokio::select!`]: tokio::select //! [`tokio::io::stdin()`]: tokio::io::stdin //! [Tokio's stdin]: tokio::io::stdin
/// Uses [`mio::Poll`] to efficiently wait on file descriptor events. /// /// [`mio::Poll`]: mio::Poll
//! Use [`crossterm`]'s `enable_raw_mode` for terminal input. //! //! [`crossterm`]: ::crossterm
❌ Bad: External docs.rs Links for Dependencies
/// Uses [mio::Poll](https://docs.rs/mio/latest/mio/struct.Poll.html) to wait.
Don't use docs.rs URLs for crates that are already in your Cargo.toml.
Why direct links are better for dependencies:
- •Clickable in local
cargo docoutput (works offline) - •Version-matched to your actual dependency version
- •Validated by rustdoc (broken links caught at build time)
- •Consistent style with internal crate links
✅ OK: External docs.rs Links for Non-Dependencies
For crates that are not in your Cargo.toml, external links are fine:
/// This is similar to how [rayon](https://docs.rs/rayon) handles parallel iteration.
Since rayon isn't a dependency, there's no local documentation to link to.
✅ OK: External Links for OS/CS/Hardware Terminology
For operating system concepts, computer science terminology, or hardware references that aren't Rust crates, use external URLs (man pages, Wikipedia, specs):
//! Uses [`epoll`] for efficient I/O multiplexing on Linux. //! Implements the [`Actor`] pattern for message passing. //! Reads from [`stdin`] which is a [`file descriptor`]. //! //! [`epoll`]: https://man7.org/linux/man-pages/man7/epoll.7.html //! [`Actor`]: https://en.wikipedia.org/wiki/Actor_model //! [`stdin`]: std::io::stdin //! [`file descriptor`]: https://en.wikipedia.org/wiki/File_descriptor
Common external link targets:
| Type | URL Pattern | Example |
|---|---|---|
| Linux syscalls/APIs | man7.org/linux/man-pages/ | epoll, signalfd, io_uring |
| BSD APIs | man.freebsd.org/ | kqueue |
| CS concepts | en.wikipedia.org/wiki/ | Actor model, Reactor pattern |
| Specs/RFCs | Official spec sites | ANSI escape codes, UTF-8 |
Key distinction:
- •
mio(Rust crate in Cargo.toml) →[mio]: mio(local) - •
epoll(Linux kernel API) →[epoll]: https://man7.org/...(external)
Note: The link source priority is also documented in
link-patterns.md. This redundancy is intentional—SKILL.md content is loaded when the skill triggers, ensuring reliable application during doc generation. Supporting files require explicit reads and serve as detailed reference.
Part 3: Constant Conventions
Use human-readable numeric literals for byte constants:
| Type | Format | Example |
|---|---|---|
Bitmasks (used in &, |, ^) | Binary | 0b0110_0000 |
| Printable ASCII | Byte literal | b'[' |
| Non-printable bytes | Decimal | 27 |
| Comments | Show hex | // (0x1B in hex) |
✅ Good: Human-Readable
/// ESC byte (0x1B in hex). pub const ANSI_ESC: u8 = 27; /// CSI bracket byte: `[` (91 decimal, 0x5B hex). pub const ANSI_CSI_BRACKET: u8 = b'['; /// Mask to convert control character to lowercase (0x60 in hex). pub const CTRL_TO_LOWERCASE_MASK: u8 = 0b0110_0000;
❌ Bad: Hex Everywhere
pub const ANSI_ESC: u8 = 0x1B; pub const ANSI_CSI_BRACKET: u8 = 0x5B; pub const CTRL_TO_LOWERCASE_MASK: u8 = 0x60;
For detailed conventions, see constant-conventions.md in this skill.
Part 4: Formatting
Run cargo rustdoc-fmt
# Format specific file cargo rustdoc-fmt path/to/file.rs # Format all git-changed files cargo rustdoc-fmt # Format entire workspace cargo rustdoc-fmt --workspace
What it does:
- •Formats markdown tables with proper column alignment
- •Converts inline links to reference-style
- •Preserves code examples
If not installed:
cd build-infra && cargo install --path . --force
Verify Documentation Builds
cargo doc --no-deps cargo test --doc
Code Examples in Docs
Golden Rule: Don't use ignore unless absolutely necessary.
| Scenario | Use |
|---|---|
| Example compiles and runs | ``` (default) |
| Compiles but shouldn't run | ```no_run |
| Can't make it compile | Link to real code instead |
| Macro syntax | ```ignore with HTML comment explaining why |
Linking to Test Modules and Functions
/// See [`test_example`] for actual usage. /// /// [`test_example`]: crate::tests::test_example
Make test module visible to docs:
#[cfg(any(test, doc))] pub mod tests;
Platform-Specific Test Modules
When you see this warning:
"unresolved link to
crate::path::test_module"And the module is
#[cfg(test)]only
Don't give up on links — Add conditional visibility instead of using plain text:
// Before (links won't resolve): #[cfg(test)] mod backend_tests; // After (links resolve in docs): #[cfg(any(test, doc))] pub mod backend_tests; // For platform-specific test modules: #[cfg(all(any(test, doc), target_os = "linux"))] pub mod linux_only_tests;
Apply at all levels — If linking to a nested test module, both parent and child modules need
the visibility change. See organize-modules skill for complete patterns.
Checklist
Before committing documentation:
- • High-level concepts at module/trait level (inverted pyramid)
- • All links use reference-style with
crate::paths - • All link definitions at bottom of comment blocks
- • Constants use binary/byte literal/decimal (not hex)
- • Hex shown in comments for cross-reference
- • Markdown tables formatted (
cargo rustdoc-fmt) - • No broken links (
cargo doc --no-deps) - • All code examples compile (
cargo test --doc)
Supporting Files
| File | Content | When to Read |
|---|---|---|
link-patterns.md | Link source rubric + 15 detailed patterns | Choosing local vs external links, modules, private types, test functions, fragments |
constant-conventions.md | Full human-readable constants guide | Writing byte constants, decision guide |
examples.md | 5 production-quality doc examples | Need to see inverted pyramid in action |
rustdoc-formatting.md | cargo rustdoc-fmt deep dive | Installing, troubleshooting formatter |
Related Commands
| Command | Purpose |
|---|---|
/docs | Full documentation check (invokes this skill) |
/fix-intradoc-links | Fix only link issues |
/fix-comments | Fix only constant conventions |
/fix-md-tables | Fix only markdown tables |
Related Skills
- •
check-code-quality- Includes doc verification step - •
organize-modules- Re-export chains, conditional visibility for doc links - •
run-clippy- May suggest doc improvements