Comments #
Comments in Rust are more than just notes the compiler ignores. Rust draws a clear distinction between ordinary comments and documentation comments — the latter are processed by the rustdoc tool to produce ready-to-use HTML documentation, complete with code examples that can be tested automatically. This means documentation in the Rust ecosystem isn’t a separate job done in a wiki or README file; it’s an integral part of the code itself. This article covers all types of Rust comments, how to write effective documentation, and — just as importantly — when you shouldn’t write comments at all.
The Four Types of Rust Comments #
Before diving into the details, it helps to see the big picture. Rust has four types of comments, each with a different purpose:
flowchart TD
K[Rust Comments]
K --> B[Ordinary Comments\nCompletely ignored by the compiler]
K --> D[Documentation Comments\nProcessed by rustdoc → HTML]
B --> B1["// single-line comment"]
B --> B2["/* block comment */"]
D --> D1["/// documents the item below it\nfunction, struct, enum, trait, field"]
D --> D2["//! documents the item that contains it\nmodule, crate, file"]| Syntax | Type | Processed by rustdoc | Used for |
|---|---|---|---|
// | Line comment | No | Internal notes, logic explanations |
/* */ | Block comment | No | Temporarily disabling code blocks |
/// | Outer doc comment | Yes | Functions, structs, enums, traits, fields |
//! | Inner doc comment | Yes | Modules, crates, lib.rs / main.rs files |
Line Comments (//)
#
Line comments are the most commonly used in everyday work. Everything after // to the end of the line is completely ignored by the compiler.
fn main() {
// This is a line comment — the compiler doesn't see this line at all
let suhu = 36; // comments can also sit at the end of a code line
// Comments can span multiple lines
// by adding // to every line
// There's no way to "continue" a line comment onto the next line
let tekanan = 101;
println!("Temperature: {}°C, Pressure: {} kPa", suhu, tekanan);
}
Comments for Temporarily Disabling Code #
One practical use of line comments is disabling code while debugging — writing a temporary alternative without deleting the original code.
fn hitung_diskon(harga: f64, pelanggan_vip: bool) -> f64 {
// Old implementation — flat 10%
// harga * 0.9
// New implementation — VIPs get 20%, regular customers 5%
if pelanggan_vip {
harga * 0.8
} else {
harga * 0.95
}
}
Commented-out code left behind for a long time becomes confusing “dead code”. If old code is no longer relevant, just delete it — version control (Git) keeps its history. Commented code should only exist temporarily during active development.
Block Comments (/* */)
#
Block comments wrap text between /* and */. Unlike line comments, block comments can sit in the middle of an expression or disable many lines at once.
fn main() {
/* This is a block comment
spanning multiple lines
without needing // on every line */
let x = 5;
// Block comments can be used in the middle of an expression
// This is rare but syntactically valid
let hasil = /* initial value */ 10 + /* additional */ 5;
println!("{}", hasil); // 15
}
Nested Block Comments #
Unlike many other languages (C, Java, Go), Rust supports nested block comments — block comments inside block comments. This is very useful when you want to disable code that already contains a block comment.
fn main() {
/*
Disable this entire block for a while:
/*
This is a block comment inside a block comment.
In C/Java this would cause a syntax error.
In Rust it's valid because nested block comments are supported.
*/
let x = hitung_sesuatu();
proses(x);
*/
println!("Only this line is active");
}
Documentation Comments (///)
#
Documentation comments use /// and sit directly above the item they document. Their content supports full Markdown — headings, bold, italic, code blocks, lists, and links all work.
/// Calculates the area of a rectangle.
///
/// This function takes a width and height in pixels and
/// returns the area in square pixels.
///
/// # Arguments
///
/// * `lebar` - The rectangle's width, must be greater than 0
/// * `tinggi` - The rectangle's height, must be greater than 0
///
/// # Returns
///
/// The rectangle's area as a `u32`.
///
/// # Panics
///
/// This function does not panic. For invalid input, use [`luas_aman`].
///
/// # Examples
///
/// ```
/// let hasil = luas(10, 5);
/// assert_eq!(hasil, 50);
/// ```
fn luas(lebar: u32, tinggi: u32) -> u32 {
lebar * tinggi
}
Standard Sections in Doc Comments #
The Rust ecosystem has a set of section conventions agreed upon by the community. Following them keeps your documentation consistent with popular crates like std, serde, and tokio.
| Section | Markdown heading | Contents |
|---|---|---|
| Description | (no heading) | First paragraph — a one-sentence summary |
| Details | (no heading) | Additional paragraphs — deeper explanation |
# Arguments | # Arguments | List of parameters with types and constraints |
# Returns | # Returns | Explanation of the return value |
# Errors | # Errors | Conditions that cause Err(...) to be returned |
# Panics | # Panics | Conditions that cause the function to panic |
# Safety | # Safety | Invariants the caller must uphold (for unsafe fn) |
# Examples | # Examples | Usage examples — always include this |
use std::num::ParseIntError;
/// Converts a string into a positive integer.
///
/// The string must represent a decimal number without any characters other than digits.
/// Leading and trailing whitespace is **not** automatically trimmed.
///
/// # Arguments
///
/// * `input` - The string slice to convert
///
/// # Returns
///
/// `Ok(u32)` if the conversion succeeds, or `Err` if the input is invalid
/// or represents a negative value.
///
/// # Errors
///
/// Returns `Err(ParseIntError)` if:
/// - The string isn't a valid number representation
/// - The string contains non-digit characters
/// - The value exceeds the `u32::MAX` limit
///
/// # Examples
///
/// ```
/// let n = parse_positif("42").unwrap();
/// assert_eq!(n, 42u32);
///
/// assert!(parse_positif("abc").is_err());
/// assert!(parse_positif("-5").is_err());
/// ```
fn parse_positif(input: &str) -> Result<u32, ParseIntError> {
input.trim().parse::<u32>()
}
Doc Tests — Comments That Can Be Tested #
This is the feature that makes Rust documentation comments fundamentally different from javadoc or ordinary Python docstrings. Every code block inside a /// comment wrapped in triple backticks is run as a test when you run cargo test.
/// Reverses the given string.
///
/// # Examples
///
/// ```
/// let hasil = balik_string("halo");
/// assert_eq!(hasil, "olah");
///
/// // An empty string stays empty after reversal
/// assert_eq!(balik_string(""), "");
///
/// // A single-character string is unchanged
/// assert_eq!(balik_string("x"), "x");
/// ```
fn balik_string(s: &str) -> String {
s.chars().rev().collect()
}
Run it with:
cargo test --doc
Output:
running 1 test
test src/lib.rs - balik_string (line 5) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
Controlling Doc Test Behavior #
You can control how doc tests run with annotations on the opening line of the code block:
/// Examples with various doc test annotations:
///
/// Code that isn't shown in the documentation but is still run:
/// ```
/// # use std::collections::HashMap; // lines with # don't appear in the HTML
/// # let mut map = HashMap::new();
/// map.insert("kunci", "nilai");
/// assert_eq!(map.get("kunci"), Some(&"nilai"));
/// ```
///
/// Code that's shown but not run (illustration only):
/// ```no_run
/// // This isn't run — usually for code that needs network or file I/O
/// let koneksi = buka_koneksi("localhost:8080");
/// ```
///
/// Code that's expected not to compile:
/// ```compile_fail
/// let x: i32 = "ini bukan angka"; // should be a compile error
/// ```
///
/// Code that's ignored entirely:
/// ```ignore
/// // This code is neither compiled nor run
/// kode_yang_belum_selesai();
/// ```
fn contoh_anotasi() {}
| Annotation | Compiled | Run | Appears in HTML |
|---|---|---|---|
| (no annotation) | Yes | Yes | Yes |
no_run | Yes | No | Yes |
compile_fail | Must fail | No | Yes |
ignore | No | No | Yes |
Lines starting with # | Yes | Yes | No |
Module-Level Documentation Comments (//!)
#
Unlike /// which documents the item below it, //! documents the item that contains it — that is, the module or crate where the comment lives. Place it on the first line of a lib.rs, main.rs, or module file.
//! # Geometry Calculator
//!
//! This crate provides functions for calculating the properties
//! of various two-dimensional and three-dimensional geometric shapes.
//!
//! ## Quick Start
//!
//! ```
//! use geometri::persegi_panjang;
//!
//! let luas = persegi_panjang::luas(10.0, 5.0);
//! assert_eq!(luas, 50.0);
//! ```
//!
//! ## Available Features
//!
//! - `persegi_panjang` — area, perimeter, diagonal
//! - `lingkaran` — area, circumference, arc
//! - `segitiga` — area, perimeter, height
//!
//! ## Version Notes
//!
//! As of version 2.0, all functions use `f64` as the default type.
// Module code below...
pub mod persegi_panjang {
/// Calculates the area of a rectangle.
pub fn luas(lebar: f64, tinggi: f64) -> f64 {
lebar * tinggi
}
}
Module-Level Comments for Nested Modules #
// src/jaringan/mod.rs
//! Networking module — TCP/UDP connection handling and HTTP client.
//!
//! All network operations are asynchronous using `tokio`.
//! Make sure the tokio runtime is active before using the functions in this module.
pub mod tcp;
pub mod http;
Documenting Structs and Fields #
Documentation comments can be applied not only to functions, but also to structs, fields, enum variants, traits, and constants.
/// Represents a user in the system.
///
/// This struct stores basic user information and is used
/// across all application layers from the database to presentation.
pub struct Pengguna {
/// The user's full name — may contain spaces and Unicode characters.
pub nama: String,
/// An email address whose format has been validated.
///
/// The email may not have been verified as owned yet —
/// check the `email_terverifikasi` field for verification status.
pub email: String,
/// The number of loyalty points the user has.
///
/// This value increases every time the user makes a transaction.
/// It can never be negative.
pub poin: u32,
// Private field — no doc comment needed since it isn't exposed publicly
aktif: bool,
}
impl Pengguna {
/// Creates a new user with zero starting points.
///
/// # Arguments
///
/// * `nama` - The user's full name
/// * `email` - A validated email address
///
/// # Examples
///
/// ```
/// # use crate::Pengguna;
/// let user = Pengguna::baru("Budi Santoso", "[email protected]");
/// assert_eq!(user.poin, 0);
/// ```
pub fn baru(nama: &str, email: &str) -> Self {
Pengguna {
nama: nama.to_string(),
email: email.to_string(),
poin: 0,
aktif: true,
}
}
}
Documenting Enums #
/// The processing status of an order.
///
/// This enum represents the entire lifecycle of an order
/// from the moment it's created until it's completed or cancelled.
#[derive(Debug, PartialEq)]
pub enum StatusPesanan {
/// The order was just created, no action taken yet.
Baru,
/// The order is being verified by the system.
///
/// At this stage payment hasn't been confirmed yet.
Diverifikasi,
/// The order is being processed.
///
/// The `oleh` field stores the ID of the employee processing it.
Diproses { oleh: String },
/// The order has been shipped to the destination address.
///
/// # Fields
///
/// * `nomor_resi` - The courier's tracking number
/// * `estimasi_tiba` - Estimated arrival date in YYYY-MM-DD format
Dikirim {
nomor_resi: String,
estimasi_tiba: String,
},
/// The order is complete and has been received by the customer.
Selesai,
/// The order was cancelled, along with the reason.
Dibatalkan(String),
}
Generating Documentation with rustdoc #
rustdoc is a built-in tool that turns /// and //! comments into an HTML documentation site. You don’t need to install it separately — it’s already part of the Rust toolchain.
# Generate documentation for the current crate
cargo doc
# Generate and open it in the browser right away
cargo doc --open
# Include dependencies in the documentation
cargo doc --no-deps # only your own crate (faster)
# Generate with certain features enabled
cargo doc --features "fitur-a fitur-b"
The generated documentation lands in target/doc/nama_crate/index.html. For library crates, this is the page users will see on docs.rs after you publish the crate to crates.io.
sequenceDiagram
participant Dev as Developer
participant Cargo as cargo doc
participant Rustdoc as rustdoc
participant HTML as HTML Documentation
Dev->>Cargo: cargo doc --open
Cargo->>Rustdoc: Parsing source files
Rustdoc->>Rustdoc: Extract /// and //! comments
Rustdoc->>Rustdoc: Render Markdown → HTML
Rustdoc->>Rustdoc: Run doc tests
Rustdoc->>HTML: target/doc/nama_crate/
HTML->>Dev: Open in browserWhen You Don’t Need to Write Comments #
A bad comment is more harmful than no comment at all — it creates the illusion that the code is documented when the explanation is actually wrong or misleading. Rust encourages self-documenting code: descriptive names, expressive types, and clear structure.
// ANTI-PATTERN: a comment that merely repeats what the code already says
// Initialize counter with a value of zero
let mut counter = 0;
// Add one to the counter
counter += 1;
// CORRECT: no comment needed — the code is clear enough on its own
let mut counter = 0;
counter += 1;
// ANTI-PATTERN: an ambiguous variable name forces a comment to explain it
// d = number of days in the current month
let d = 30;
// CORRECT: a descriptive name makes the comment unnecessary
let hari_dalam_bulan = 30;
// ANTI-PATTERN: a comment explaining "what" — already obvious from the code
// Loop through every element of the array
for elemen in &data {
proses(elemen);
}
// CORRECT: a comment explaining "why" — information that isn't visible in the code
// Processing happens sequentially (not in parallel) because each element
// depends on the result of processing the previous element
for elemen in &data {
proses(elemen);
}
// ANTI-PATTERN: a comment that's no longer accurate because the code changed but the comment didn't
// This function always returns a positive value
fn hitung(x: i32) -> i32 {
x * x - 10 // Can now be negative, but the comment wasn't updated
}
// CORRECT: delete the inaccurate comment, or update it with correct information
/// Calculates x squared minus 10. Can return a negative value
/// if the absolute value of `x` is less than ~3.16.
fn hitung(x: i32) -> i32 {
x * x - 10
}
A Quick Guide: Write Comments for “Why”, Not “What” #
flowchart TD
Q{Does this code\nneed a comment?}
Q --> A{Is the code\nclear on its own?}
A -- Yes --> B{Is there a\nnon-obvious reason behind it?}
B -- No --> C[No comment needed\nThe code is self-documenting]
B -- Yes --> D["Write a 'why' comment\nnot a 'what' one"]
A -- No --> E{Can it be fixed\nwith a better name?}
E -- Yes --> F[Rename the variable/function\nRemove the comment]
E -- No --> G[Write an explanatory comment\nConsider extracting into a function]Summary #
- Four types of comments in Rust:
//(line),/* */(block),///(item doc comment),//!(module/crate doc comment).///is more than a note — it’s processed byrustdocinto HTML documentation and run as a test bycargo test --doc.- Doc tests keep documentation accurate — code examples in
///comments are compiled and run, so they can’t drift out of sync with the implementation.- Standard sections like
# Arguments,# Returns,# Errors,# Panics,# Exampleskeep your documentation consistent with the Rust ecosystem.//!is for crates and modules — place it on the first line oflib.rsormod.rsto document the whole unit.- Rust block comments support nesting —
/* /* */ */is valid, unlike in C or Java.- Write comments for “why”, not “what” — good code explains what it does on its own; comments explain the reasoning behind decisions that aren’t visible in the code.
- A wrong comment is worse than no comment — always update comments when you change code, or delete them if they’re no longer relevant.