Clear code beats clear comments. However, when the why isn't obvious, comment it plainly - or link to where you can read more context.
| Purpose | Use // comment |
Use /// doc or //! crate doc |
|---|---|---|
| Describe Why | ✅ Yes - explains tricky reasoning | ❌ Not for documentation |
| Describe API | ❌ Not useful | ✅ Yes - public interfaces, usage, details, errors, panics |
| Maintainable | 🚨 Often becomes obsolete and hard to reason | ✅ Tied to code, appears in generated docs and can run test cases |
| Visibility | Local development only | Exported to users and tools like cargo doc |
Use // comments (double slashed) when something can't be expressed clearly in code, like:
- Safety Guarantees, some of which can be better expressed with code conditionals.
- Workarounds or Optimizations.
- Legacy or platform-specific behaviors. Some of them can be expressed with
#[cfg(..)]. - Links to Design Docs or ADRs.
- Assumptions or gotchas that aren't obvious.
Name your comments! For example, a comment regarding a safety guarantee should start with
// SAFETY: ....
// SAFETY: `ptr` is guaranteed to be non-null and aligned by caller
unsafe { std::ptr::copy_nonoverlapping(src, dst, len); }// CONTEXT: Reuse root cert store across subgraphs to avoid duplicate OS calls:
// [ADR-12](link/to/adr-12): TLS Performance on MacOSAvoid comments that:
- Restate obvious things (
// increment i by 1 for the next loop). - Can grow stale over time.
TODOs without actions (links to some versioned issue).- Could be replaced by better naming or smaller functions.
fn compute(counter: &mut usize) {
// increment by 1
*counter += 1;
}// Originally written in 2028 for some now-defunct platformComments as a "living documentation" is a dangerous myth, as comments are not free:
- They rot - nobody compiles comments.
- They mislead - readers usually assume they are true with no critique, e.g. "the other developer knows this code better than I do".
- They go stale - unless maintained with the code, they become irrelevant.
- They are noisy - comments can clutter your code with multiple unnecessary lines.
If something deserves to live beyond a PR, put it in:
- An ADR (Architectural Design Record).
- A Design Document.
- Document it in code by using types, doc comments, examples, renaming code blocks into cleaner functions.
- Add tests to cover and explain the change.
Instead of long commented blocks, break logic into named helper functions:
fn save_user(&self) -> Result<(), MyError> {
// check if the user is authenticated
if self.is_authenticated() {
// serialize user data
let data = serde_json::to_string(self)?;
// write to file
std::fs::write(self.path(), data)?;
}
}✅ Extract for clarity:
fn save_auth_user(&self) -> Result<PathBuf, MyError> {
if self.is_authenticated() {
let path = self.path();
let serialized_user = serde_json::to_string(self)?;
std::fs::write(path, serialized_user)?;
Ok(path)
} else {
Err(MyError::UserNotAuthenticated)
}
}Don't leave // TODO: scattered around the codebase with no owner. Instead:
- File Github Issue or Jira Ticket. (Prefer github issues on public repositories).
- Reference the issue in the code:
// TODO(issue #42): Remove workaround after bugfixThis makes TODOs trackable, actionable and visible to everyone.
Use /// doc comments to document:
- All public functions, structs, traits, enums.
- Their purpose, their usage and their behaviors.
- Anything developers need to understand how to use it correctly.
- Add context that related to
ErrorsandPanics. - Plenty of examples.
/// Loads [`User`] profile from disk
///
/// # Error
/// - Returns [`MyError`] if the file is missing [`MyError::FileNotFound`].
/// - Returns [`MyError`] if the content is an invalid Json, [`MyError::InvalidJson`].
fn load_user(path: &Path) -> Result<User, MyError> {...}Doc comments can also include examples, links and even tests:
/// Returns the square of the integer part of any number.
/// Square is limited to `u128`.
///
/// # Examples
///
/// ```rust
/// assert_eq!(square(4.3), 16)
/// ```
fn square(x: impl ToInt) -> u128 { ... }Rust provides first-class documentation tooling via rustdoc, which makes documenting your code a key part of writing idiomatic and maintainable rust. There are doc specific lints to help with documentation, like:
| Lint | Description |
|---|---|
| missing_docs | Warns that a public functions, struct, const, enum has missing documentation |
| broken_intra_doc_links | Detects if an internal documentation link is broken. Specially useful when things are renamed. |
| empty_docs | Disallow empty docs - preventing bypass of missing_docs |
| missing_panics_doc | Warns that documentation should have a # Panics section if function can panic |
| missing_errors_doc | Warns that documentation should have a # Errors section if function returns a Result explaining Err conditions |
| missing_safety_doc | Warns that documentation should have a # Safety section if public facing functions have visible unsafe blocks |
| Style | Used for | Scope | Example |
|---|---|---|---|
/// |
Line doc comment | Public items like struct, fn, enum, consts | Documenting, giving context and usage to fn, struct, enum, etc |
//! |
Module level doc comment | Modules or entire crates | Explaining crate/module purpose with common use cases and quickstart |
Use /// for functions, structs, traits, enums, const, etc:
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}- ✅ Write clear and descriptive What it does and how to use it.
- ✅ Use
# Examplessection to better explain how to use it. - ✅ Prefer writing examples that can be tested via
cargo test, even if you have to hide their output with starting#:
/// ```
/// let result = my_crate::add(2, 3);
/// # assert_eq!(result, 5);
/// ```
- ✅ Use
# Panics,# Errorsand# Safetysections when relevant. - Add relevant context to the type.
Use //! when you want to document the purpose of a module or a crate. It is places at the top of a lib.rs or mod.rs file, for example engine/mod.rs:
//! This module implements a custom chess engine.
//!
//! It handles board state, move generation and check detection.
//!
//! # Example
//! ```
//! let board = chess::engine::Board::default();
//! assert!(board.is_valid());
//! ```
📦 Crate-Level (lib.rs)
-
//!doc at top explains what the crate does, and what problems it solves. - Includes crate-level
# Examplesor pointers to modules. 📁 Modules (mod.rs or inline) -
//!doc explains what this module is for, its exports, and invariants. - Avoid repeating doc comments on re-exported items unless clarification is needed. 🧱 Structs, Enums, Traits
///doc explains:- The role this type plays.
- Invariants or expectations.
- Example construction or usage.
- Consider using
#[non_exhaustive]if external users may match on it. 🔧 Functions and Methods ///doc covers:- What it does.
- Parameters and their meaning.
- Return value behavior.
- Edge cases (
# Panics,# Errors). - Usage example,
# Examples. 📑 Traits
- Explain the purpose of the trait (marker? dynamic dispatch?).
- Doc for each method — include when/why to implement it.
- Document clearly default implemented methods and when to override. 📦 Public Constants
- Document what they configure and when you'd want to use them.
- ✅ Use examples generously — they double as test cases.
- ✅ Prefer clarity over formality — it’s for humans, not machines.
- ✅ Prefer doc comments to explain usage, and leave implementation details to code comments if needed.
- ✅ Use
cargo doc --opento check your output often. - ✅ Add
#![deny(missing_docs)]and other relevant doc lints in top-level modules if you want to enforce full doc coverage.