乐于分享
好东西不私藏

手把手带你写 Rust 文档

手把手带你写 Rust 文档

在 Rust 中,文档不是代码的“附属品”,而是程序开发的一部分。Rust 为文档提供了原生的工具链支持,达到文档即代码、文档即测试的效果,这也是为什么 Rust 生态的库普遍有极高的文档质量。这篇文章会手把手带你写 Rust 文档,从基础语法、高级技巧,到最佳实践,写出符合 Rust 社区规范、可维护、高可用性的技术文档。

rustdoc 入门

Rust 文档的核心工具是 rustdoc,它是 Rust 官方内置的文档生成器,和 Cargo 深度集成,可以通过下面的命令使用:

rustdoc --versioncargo 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 会自动把注释内容渲染到函数的详情页中。

add函数注释文档

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 testtest src/lib.rs - add (line 22) ... oktest 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(())/// # }/// ```

渲染后的文档只会显示核心的两行代码,隐藏的 usemain 函数会在测试时正常运行,完美解决了 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[1] 这个库来达成效果,它是 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 标准库文档[2],它应该是最权威的文档规范范本了,另外,像 serde、tokio、anyhow 等库的文档也值得拿来学习。

总结

Rust 文档的核心,从来都不是语法和工具,而是“站在用户的角度思考”,你写的每一行文档,都是为了让用户能快速理解、正确使用你的代码。

引用链接

[1] aquamarine: https://docs.rs/aquamarine/latest/aquamarine/[2] Rust 标准库文档: https://doc.rust-lang.org/std/