手把手带你写 Rust 文档
在 Rust 中,文档不是代码的“附属品”,而是程序开发的一部分。Rust 为文档提供了原生的工具链支持,达到文档即代码、文档即测试的效果,这也是为什么 Rust 生态的库普遍有极高的文档质量。这篇文章会手把手带你写 Rust 文档,从基础语法、高级技巧,到最佳实践,写出符合 Rust 社区规范、可维护、高可用性的技术文档。
rustdoc 入门
Rust 文档的核心工具是 rustdoc,它是 Rust 官方内置的文档生成器,和 Cargo 深度集成,可以通过下面的命令使用:
rustdoc --version
# cargo doc 是调度 rustdoc 的便捷封装
cargo doc --version
命令速查
在项目根目录执行以下命令,就能完成文档的生成、预览和测试:
| 命令 | 作用 |
|---|---|
cargo doc | 生成当前项目及所有依赖的文档 |
cargo doc --open | 生成文档并自动在浏览器打开预览 |
cargo doc --no-deps | 仅生成当前项目的文档,跳过依赖,大幅提升生成速度 |
cargo test --doc | 文档测试,会运行文档中的示例代码 |
cargo doc --offline | 离线生成文档,无需网络连接 |
文档注释语法
Rust 严格区分了普通注释和文档注释:普通注释是给代码维护者看的,不会被 rustdoc 解析,而文档注释是给用户看的,会被渲染到文档页面中。
四种注释的区别与用法
| 注释语法 | 类型 | 作用范围 | 用途 |
|---|---|---|---|
// 内容 | 普通行注释 | 仅代码内部 | 给维护者写的代码说明、TODO 等 |
/* 内容 */ | 普通块注释 | 仅代码内部 | 多行的代码内部说明 |
/// 内容 | 行文档注释 | 紧随其后的项(函数、结构体、枚举等) | 给公开的 API 项写文档 |
/** 内容 */ | 块文档注释 | 紧随其后的项 | 多行的 API 项文档,用法和 /// 一致 |
//! 内容 | 模块级行文档注释 | 所在的模块 | 写在文件顶部,给当前模块写文档 |
第一行文档注释
我们先给一个简单的加法函数写文档,如下所示:
/// 计算两个整数的和,返回相加后的结果
///
/// 这是一个基础的整数加法函数,支持正负数、零的运算,
/// 不会做溢出检查,溢出行为遵循 Rust 整数溢出规则
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
执行 cargo doc --open,你就能在文档中看到这个函数的完整说明,rustdoc 会自动把注释内容渲染到函数的详情页中。

Markdown 的支持
rustdoc 对 Markdown 的支持还是比较全面的,包括代码块(带语法高亮)、列表、链接、表格等标准的 Markdown 语法。
基础 Markdown 语法
你可以在文档注释中使用所有常用的 Markdown 语法:
- 标题:
# 一级标题、## 二级标题,推荐二级及以下标题用于文档内部分段 - 列表:有序列表
1. xxx、无序列表- xxx - 代码块:用于写可运行的示例
- 强调:
**加粗**、*斜体* - 链接:
[链接文本](链接地址) - 表格:常用于参数对比、特性说明等
- 引用块:
> 内容,常用于提示、警告信息
文档内链接(Intra-doc Links)
这是 Rust 文档的特性之一:你可以在文档中直接链接到当前 crate、标准库、其他 crate 的任意 API 项,用户点击就能跳转到对应页面。
语法比较简单,直接用[项名]即可完成链接,rustdoc 会自动解析路径并生成跳转链接:
/// 数学运算相关的错误类型枚举
#[derive(Debug, PartialEq, Eq)]
pub enum MathError {
/// 除零错误,当除数为 0 时触发
DivideByZero,
/// 整数溢出错误
Overflow,
}
/// 整数除法运算,返回商或错误
///
/// # Errors
/// 当除数 `b` 为 0 时,返回 [MathError::DivideByZero]
pub fn divide(a: i32, b: i32) -> Result<i32, MathError> {
if b == 0 {
return Err(MathError::DivideByZero);
}
Ok(a / b)
}
上面的例子中,[MathError::DivideByZero] 会自动生成跳转到该枚举项的链接,用户点击就能看到对应的定义和说明。
文档即测试
Rust 文档中最惊艳的功能之一就是文档测试(Doctest),这是其他编程语言中基本没有的功能。rustdoc 会把你文档里的 rust 代码块,当成单元测试来编译运行。这也就解决了文档和代码不同步的痛点,只要你改了代码,文档里的示例跑不通,测试就会直接报错,强制你同步更新文档。
第一个文档测试
我们给 add 函数的文档加上可运行的测试:
/// 计算两个整数的和,返回相加后的结果
///
/// # 示例
/// ```rust
/// use doc_example::add;
///
/// // 基础加法
/// assert_eq!(add(2, 3), 5);
/// // 正负数相加
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
执行 cargo test --doc,你会看到如下输出:
running 1 test
test src/lib.rs - add (line 22) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
隐藏辅助代码
很多时候,示例需要一些导入、main 函数等辅助代码,但这些代码会让文档变得冗余。你可以用 # 开头的行,标记这段代码会被编译运行,但不会渲染在文档中:
/// 除法运算示例
///
/// ```rust
/// # use doc_example::{divide, MathError};
/// # fn main() -> Result<(), MathError> {
/// let result = divide(10, 2)?;
/// assert_eq!(result, 5);
/// # Ok(())
/// # }
/// ```
渲染后的文档只会显示核心的两行代码,隐藏的 use、main 函数会在测试时正常运行,完美解决了 Result 类型的错误处理问题。
测试属性标记
你可以在代码块的开头添加属性,控制测试的行为:
| 属性 | 作用 |
|---|---|
rust,should_panic | 标记该示例预期会 panic,测试时 panic 才算通过 |
rust,ignore | 忽略该示例,不编译不运行(常用于展示性代码) |
rust,no_run | 只编译不运行(常用于网络 IO、文件操作等无法自动化测试的场景) |
rust,compile_fail | 预期该代码编译失败,测试时编译不通过才算通过 |
示例如下:
/// 除零场景示例
///
/// ```rust,should_panic
/// use doc_example::divide;
///
/// // 除零会触发 panic
/// divide(10, 0).unwrap();
/// ```
非 Rust 代码块
如果你需要展示其他格式的内容,比如 JSON、文本、Shell 命令,只需要指定对应的语言标识,rustdoc 就不会把它当成测试:
/// 配置文件格式示例
///
/// ```toml
/// [math]
/// precision = 10
/// enable_overflow_check = true
/// ```
渲染 Mermaid 流程图
rustdoc 并不支持原生渲染 Mermaid 流程图,但是我们可以使用 aquamarine 这个库来达成效果,它是 rustdoc 的过程式宏扩展。
步骤一:添加依赖
cargo add aquamarine
步骤二:使用 Mermaid 流程图
#[cfg_attr(doc, aquamarine::aquamarine)]
/// 计算器运算流程
///
/// ```mermaid
/// graph LR
/// A[设置初始值] --> B[加法运算]
/// B --> C{是否除零?}
/// C -->|否| D[除法运算]
/// C -->|是| E[返回除零错误]
/// D --> F[输出最终结果]
/// ```
pub fn example() {}
渲染后的效果如下:

文档属性
Rust 提供了一系列 #[doc] 属性,让开发者可以精细化控制文档的渲染、可见性、搜索等行为,满足复杂场景的需求。
| 属性 | 作用 | 示例 |
|---|---|---|
#[doc = "内容"] | 动态生成文档内容,常用于宏生成的代码 | #[doc = concat!("这是", "动态生成", "的文档")] |
#[doc(hidden)] | 隐藏该项,不渲染在文档中(用于内部实现) | |
#[doc(alias = "别名")] | #[doc(alias = "别名")] 给项添加搜索别名,用户搜索别名也能找到该项 | |
#[doc(inline)] | 内联重导出项的文档,用户无需跳转就能看到 | |
#[doc(no_inline)] | 不内联文档,点击跳转到原文档页面 | |
#[cfg(doc)] | 仅在生成文档时编译该段代码 | |
#![deny(missing_docs)] | 强制所有公开项必须有文档,缺失则编译报错 | 写在 lib.rs 顶部,用于团队规范约束 |
最佳实践
Rust 社区有一套公认的文档规范,如下所示:
- 一句话摘要:文档的第一行必须是一句话,精准概括这个项的核心作用
- 详细描述:补充适用场景、设计思路、边界情况,告诉用户“什么时候用、什么时候不该用”
- 示例优先:先给可运行的核心示例,再讲参数、返回值等细节
- 专项说明:按需添加
# Parameters、# Returns、# Errors、# Panics、# Safety、# Performance等专项章节 - 公开项必写文档:所有
pub的项都必须有文档,用#![deny(missing_docs)]强制约束 - 文档和代码同步:改了代码不改文档,文档测试就是为了解决这个问题,确保每个示例都能跑通
- unsafe 函数必须写 Safety 说明:这是 Rust 社区的硬性规范,所有
unsafe函数必须写清楚调用者需要遵守的安全约束
最后,想要写出高质量的文档,最好的方法就是学习顶级开源库的写法,比如 Rust 标准库文档,它应该是最权威的文档规范范本了,另外,像 serde、tokio、anyhow 等库的文档也值得拿来学习。
总结
Rust 文档的核心,从来都不是语法和工具,而是“站在用户的角度思考”,你写的每一行文档,都是为了让用户能快速理解、正确使用你的代码。