Rust 注释与文档注释详解:从代码说明到 cargo doc

《Rust 编程实战》系列第 10 篇

在上一篇文章中,我们学习了 Rust 的函数与返回值。

随着代码越来越多,仅仅让程序"能够运行"还不够。我们还需要让其他开发者,甚至未来的自己,能够快速理解代码的作用。

这时就需要使用注释。

Rust 中的注释主要分为两类:

  • 普通注释:用于说明代码逻辑

  • 文档注释:用于生成 API 文档

本文将详细介绍:

  • 单行注释

  • 多行注释

  • 文档注释 ///

  • 模块文档注释 //!

  • Markdown 格式文档

  • 文档代码示例

  • cargo doc 的使用

  • 文档测试

  • 注释的最佳实践


什么是注释?

注释是写给开发者看的说明文字。

编译器通常会忽略注释,不会把它们当作程序代码执行。

例如:

arduino 复制代码
fn main() {
    // 输出欢迎信息
    println!("Hello, Rust!");
}

程序运行时,只会执行:

arduino 复制代码
println!("Hello, Rust!");

而不会执行注释内容。


为什么需要注释?

注释主要有以下几个作用:

  • 解释复杂逻辑

  • 说明特殊处理原因

  • 记录函数或模块用途

  • 提醒后续开发者注意事项

  • 为公共 API 生成文档

  • 帮助团队协作和项目维护

需要注意:

好的注释应该解释"为什么这样做",而不是简单重复代码本身。


单行注释

Rust 使用两个斜杠表示单行注释:

arduino 复制代码
//

例如:

csharp 复制代码
fn main() {
    // 定义用户年龄
    let age = 18;

    // 输出年龄
    println!("{}", age);
}

// 开始直到这一行结束,都会被视为注释。


行尾注释

注释也可以写在代码后面:

csharp 复制代码
fn main() {
    let port = 8080; // Web 服务端口

    println!("{}", port);
}

这种方式适合解释简单配置。

不过,如果说明文字较长,更推荐单独写在上一行:

csharp 复制代码
fn main() {
    // Web 服务默认监听端口
    let port = 8080;

    println!("{}", port);
}

这样可读性更好。


连续多行注释

可以连续使用多个 //

arduino 复制代码
fn main() {
    // 这是一个简单的 Rust 程序
    // 用于演示连续多行注释
    // 程序会输出一条消息

    println!("Rust 注释示例");
}

在实际项目中,这是一种非常常见的写法。


块注释

Rust 也支持块注释:

arduino 复制代码
/*
    注释内容
*/

例如:

arduino 复制代码
fn main() {
    /*
        这里可以写多行注释
        适合临时说明一大段内容
    */

    println!("Hello");
}

也可以写成单行:

csharp 复制代码
fn main() {
    /* 用户默认年龄 */
    let age = 18;

    println!("{}", age);
}

块注释可以嵌套

Rust 的块注释支持嵌套。

例如:

rust 复制代码
fn main() {
    /*
        外层注释

        /*
            内层注释
        */

        继续外层注释
    */

    println!("Hello");
}

有些语言不支持块注释嵌套,但 Rust 支持。

这在临时注释一大段代码时比较方便。


临时注释代码

开发时,有时会暂时停用一段代码。

例如:

arduino 复制代码
fn main() {
    println!("程序开始");

    /*
    println!("测试代码 1");
    println!("测试代码 2");
    println!("测试代码 3");
    */

    println!("程序结束");
}

运行结果:

复制代码
程序开始
程序结束

不过,长期项目中不建议保留大量被注释掉的旧代码。

因为 Git 已经能够记录历史版本,没有必要把废弃代码一直留在源码中。


什么是文档注释?

普通注释只在源代码中可见。

文档注释除了能在源码中说明功能,还可以通过 Cargo 自动生成 HTML 文档。

Rust 常用的文档注释有两种:

arduino 复制代码
///

和:

arduino 复制代码
//!

它们的作用不同。


使用 /// 为函数编写文档

/// 用于说明紧跟在它后面的代码项。

例如:

rust 复制代码
/// 计算两个整数的和。
fn add(a: i32, b: i32) -> i32 {
    a + b
}

这里的文档注释属于:

csharp 复制代码
fn add

函数。


为公共函数编写文档

库项目中,公共函数通常会使用 pub

rust 复制代码
/// 计算两个整数的和。
///
/// # 参数
///
/// - `a`:第一个整数
/// - `b`:第二个整数
///
/// # 返回值
///
/// 返回两个整数相加后的结果。
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

这些内容可以被 cargo doc 转换为网页文档。


文档注释支持 Markdown

Rust 文档注释支持 Markdown 格式。

例如:

csharp 复制代码
/// 创建一个用户。
///
/// # 功能
///
/// 该函数会完成以下操作:
///
/// - 检查用户名
/// - 检查邮箱
/// - 创建用户记录
/// - 返回用户编号
///
/// # 注意
///
/// 用户名不能为空。
pub fn create_user() {
}

常见 Markdown 语法包括:

  • 标题

  • 列表

  • 加粗

  • 代码

  • 链接

  • 代码块


常用文档章节

Rust 项目中经常使用以下文档章节:

bash 复制代码
# Examples
# Arguments
# Returns
# Errors
# Panics
# Safety

它们不是强制要求,但已经成为社区常见规范。


Examples 示例

例如:

rust 复制代码
/// 计算两个整数的和。
///
/// # Examples
///
/// ```
/// let result = 10 + 20;
/// assert_eq!(result, 30);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

生成的文档中会显示可读性很好的代码示例。


Arguments 参数说明

例如:

rust 复制代码
/// 根据商品价格和数量计算总价。
///
/// # Arguments
///
/// - `price`:商品单价
/// - `quantity`:购买数量
///
/// # Returns
///
/// 返回商品总价。
pub fn calculate_total(price: f64, quantity: u32) -> f64 {
    price * quantity as f64
}

Errors 错误说明

如果函数返回 Result,建议说明可能出现的错误。

例如:

rust 复制代码
/// 读取配置文件。
///
/// # Errors
///
/// 当文件不存在、权限不足或内容格式错误时,返回错误。
pub fn read_config() -> Result<String, std::io::Error> {
    std::fs::read_to_string("config.toml")
}

这样调用者可以提前了解错误场景。


Panics 崩溃说明

如果函数在某些情况下会 panic!,建议明确说明。

例如:

rust 复制代码
/// 获取数组中的第一个元素。
///
/// # Panics
///
/// 当数组为空时会发生 panic。
pub fn first_item(items: &[i32]) -> i32 {
    items[0]
}

这对公共 API 很重要。

调用者需要知道函数是否可能直接导致程序终止。


Safety 安全说明

如果函数是 unsafe,必须清楚说明调用要求。

例如:

rust 复制代码
/// 从原始指针读取一个整数。
///
/// # Safety
///
/// 调用者必须保证:
///
/// - 指针不为空
/// - 指针指向有效的 `i32`
/// - 指针在读取期间保持有效
pub unsafe fn read_pointer(ptr: *const i32) -> i32 {
    unsafe { *ptr }
}

unsafe 函数的文档尤其重要。


使用 //! 编写模块文档

//! 用于说明当前模块或当前 Crate。

例如,在 src/lib.rs 顶部:

rust 复制代码
//! 一个简单的数学工具库。
//!
//! 这个库提供整数加法、减法和乘法等基础功能。

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

这里的文档属于整个库,而不是某一个函数。


/// 与 //! 的区别

语法 作用
/// 注释后面的函数、结构体、枚举等项目
//! 注释当前模块或整个 Crate

例如:

rust 复制代码
//! 用户模块。
//!
//! 提供用户创建、查询和删除功能。

/// 用户数据结构。
pub struct User {
    pub id: u64,
    pub name: String,
}

其中:

  • //! 说明整个用户模块

  • /// 说明 User 结构体


为结构体编写文档

例如:

rust 复制代码
/// 用户信息。
///
/// 用于保存系统中的基础用户数据。
pub struct User {
    /// 用户唯一编号。
    pub id: u64,

    /// 用户名称。
    pub name: String,

    /// 用户年龄。
    pub age: u8,
}

不仅结构体可以写文档,每个字段也可以单独说明。


为枚举编写文档

例如:

ruby 复制代码
/// 订单状态。
pub enum OrderStatus {
    /// 订单已经创建,但尚未付款。
    Pending,

    /// 订单已经付款。
    Paid,

    /// 订单已经发货。
    Shipped,

    /// 订单已经取消。
    Cancelled,
}

这样生成的 API 文档会非常清晰。


为常量编写文档

例如:

rust 复制代码
/// 应用程序默认端口。
pub const DEFAULT_PORT: u16 = 8080;

为模块编写文档

例如,创建文件:

bash 复制代码
src/user.rs

内容:

rust 复制代码
//! 用户管理模块。
//!
//! 该模块负责:
//!
//! - 创建用户
//! - 查询用户
//! - 删除用户

/// 创建一个用户。
pub fn create_user() {
    println!("创建用户");
}

lib.rs 中引入:

rust 复制代码
pub mod user;

使用 cargo doc 生成文档

Cargo 可以自动读取文档注释并生成 HTML 页面。

执行:

复制代码
cargo doc

生成的文件会放在:

bash 复制代码
target/doc/

目录中。


自动打开文档

可以执行:

arduino 复制代码
cargo doc --open

Cargo 会完成两件事情:

  1. 生成项目文档
  2. 使用默认浏览器打开文档

这也是平时查看项目 API 文档最方便的方式。


不生成依赖文档

默认情况下,cargo doc 可能会为依赖一起生成文档。

只想生成当前项目文档,可以使用:

css 复制代码
cargo doc --no-deps --open

这个命令在实际开发中非常常用。


完整库项目示例

创建一个库项目:

sql 复制代码
cargo new math_utils --lib

打开:

bash 复制代码
src/lib.rs

写入:

rust 复制代码
//! 一个简单的数学工具库。
//!
//! 提供基础的加法和乘法功能。

/// 计算两个整数的和。
///
/// # Arguments
///
/// - `a`:第一个整数
/// - `b`:第二个整数
///
/// # Returns
///
/// 返回两个整数相加后的结果。
///
/// # Examples
///
/// ```
/// use math_utils::add;
///
/// let result = add(10, 20);
///
/// assert_eq!(result, 30);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

/// 计算两个整数的乘积。
///
/// # Examples
///
/// ```
/// use math_utils::multiply;
///
/// assert_eq!(multiply(4, 5), 20);
/// ```
pub fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

然后执行:

css 复制代码
cargo doc --no-deps --open

浏览器中就能看到自动生成的库文档。


文档中的代码可以测试

Rust 的文档代码块不仅用于展示,还可以作为测试运行。

例如:

rust 复制代码
/// 计算两个整数的和。
///
/// # Examples
///
/// ```
/// use math_utils::add;
///
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

执行:

bash 复制代码
cargo test

Cargo 会检查文档示例是否能够正常编译和运行。

这称为:

复制代码
Doctest

也就是文档测试。


为什么文档测试很重要?

很多项目的代码不断更新,但文档示例没有同步更新。

最终可能出现:

  • 文档中的函数名已经不存在

  • 参数数量发生变化

  • 返回值类型发生变化

  • 示例代码无法编译

  • 使用方法已经过时

文档测试可以帮助开发者及时发现这些问题。

这也是 Rust 文档系统非常强大的地方。


隐藏文档示例中的辅助代码

有时文档示例需要初始化代码,但不希望全部展示给读者。

可以在代码行前加:

bash 复制代码
#

例如:

perl 复制代码
/// # Examples
///
/// ```
/// # let enabled = true;
/// if enabled {
///     println!("功能已启用");
/// }
/// ```
pub fn run() {
}

# 的代码仍然会参与测试,但生成文档时通常不会展示。

这适合隐藏:

  • 初始化代码

  • 导入代码

  • 错误处理样板代码

  • 测试辅助内容


忽略某个文档测试

如果代码仅用于展示,暂时不能运行,可以使用:

csharp 复制代码
/// # Examples
///
/// ```ignore
/// connect_to_remote_server();
/// ```
pub fn connect() {
}

使用:

复制代码
ignore

后,Cargo 不会执行这段文档测试。

不过不建议滥用,因为这会失去文档测试的价值。


标记代码不需要运行

还可以使用:

csharp 复制代码
/// ```no_run
/// let server = create_server();
/// server.run();
/// ```
pub fn start_server() {
}

no_run 表示:

  • 代码需要通过编译

  • 但不实际运行

适合:

  • 启动服务器

  • 连接数据库

  • 删除文件

  • 调用远程接口

  • 长时间运行任务


普通注释与文档注释对比

类型 语法 主要用途
单行注释 // 解释代码逻辑
块注释 /* */ 多行说明或临时注释代码
项目文档注释 /// 为函数、结构体、枚举等生成文档
模块文档注释 //! 为模块或 Crate 生成文档

TODO 注释

开发中经常会写:

arduino 复制代码
// TODO: 增加参数校验

表示这里还有功能需要完成。

例如:

rust 复制代码
fn create_user(name: &str) {
    // TODO: 检查用户名是否为空

    println!("创建用户:{}", name);
}

还可以使用:

arduino 复制代码
// FIXME: 当前实现在线程环境下可能有问题

表示这里存在需要修复的问题。

不过,这类注释应该配合任务管理系统使用,避免永久留在代码中。


不推荐的注释方式

重复代码含义

不推荐:

arduino 复制代码
// 把 count 加 1
count += 1;

因为代码本身已经非常清楚。

更有价值的注释应该说明原因:

arduino 复制代码
// 跳过表头,因此从第二行开始统计
count += 1;

注释与代码不一致

例如:

ini 复制代码
// 默认端口为 8080
let port = 3000;

这比没有注释更加危险。

维护代码时必须同步更新注释。


注释掉大量旧代码

不推荐:

arduino 复制代码
/*
fn old_login() {
    // 几十行旧代码
}
*/

旧代码应该交给 Git 保存。

源码中只保留当前有效实现。


给每一行都写注释

不推荐:

ini 复制代码
// 定义 a
let a = 10;

// 定义 b
let b = 20;

// 计算结果
let result = a + b;

// 输出结果
println!("{}", result);

这种注释会让代码显得冗余。

变量名和函数名足够清楚时,不需要逐行解释。


注释最佳实践

优先写清晰的代码

例如:

不推荐:

ini 复制代码
let x = p * q;

然后写:

arduino 复制代码
// 商品单价乘以数量得到总价

更推荐直接改成:

ini 复制代码
let total_price = unit_price * quantity;

清晰命名往往比注释更有效。


解释为什么,而不是解释是什么

不推荐:

less 复制代码
// 等待 3 秒
sleep(Duration::from_secs(3));

更推荐:

less 复制代码
// 第三方接口存在短时间限流,重试前等待 3 秒
sleep(Duration::from_secs(3));

公共 API 尽量编写文档注释

例如:

csharp 复制代码
pub fn calculate_total() {
}

如果这是对外使用的函数,应该补充:

  • 功能

  • 参数

  • 返回值

  • 错误

  • 示例

这样其他开发者不需要阅读函数内部实现,就能正确使用它。


文档示例尽量可运行

文档中的代码最好能够通过:

bash 复制代码
cargo test

这样可以避免文档长期失效。


实战:为订单模块编写文档

下面是一个完整示例:

rust 复制代码
//! 订单金额计算模块。
//!
//! 提供商品小计、折扣金额和最终订单金额计算功能。

/// 计算商品小计。
///
/// # Arguments
///
/// - `price`:商品单价
/// - `quantity`:商品数量
///
/// # Returns
///
/// 返回商品单价与数量相乘后的结果。
///
/// # Examples
///
/// ```
/// let subtotal = 29.9 * 3.0;
/// assert_eq!(subtotal, 89.7);
/// ```
pub fn calculate_subtotal(price: f64, quantity: u32) -> f64 {
    price * quantity as f64
}

/// 根据折扣率计算折后金额。
///
/// # Arguments
///
/// - `subtotal`:商品小计
/// - `discount_rate`:折扣率,范围为 `0.0..=1.0`
///
/// # Returns
///
/// 当折扣率有效时返回折后金额,否则返回原始小计。
///
/// # Examples
///
/// ```
/// let result = 100.0 * 0.8;
/// assert_eq!(result, 80.0);
/// ```
pub fn apply_discount(subtotal: f64, discount_rate: f64) -> f64 {
    if !(0.0..=1.0).contains(&discount_rate) {
        return subtotal;
    }

    subtotal * discount_rate
}

这个模块的文档包含:

  • 模块用途

  • 函数用途

  • 参数说明

  • 返回值说明

  • 可运行示例

这就是一个比较规范的 Rust API 文档。


本章小结

注释是提高代码可读性和可维护性的重要工具,而 Rust 的文档注释还可以直接生成专业的 API 文档。

通过本文,我们学习了:

  • 使用 // 编写单行注释

  • 使用 /* */ 编写块注释

  • 块注释嵌套

  • 使用 /// 为函数和类型编写文档

  • 使用 //! 为模块和 Crate 编写文档

  • 文档中的 Markdown 格式

  • ExamplesErrorsPanicsSafety

  • 使用 cargo doc 生成文档

  • 使用 cargo doc --no-deps --open 打开项目文档

  • 文档测试 Doctest

  • ignoreno_run

  • 注释常见误区和最佳实践

最重要的原则是:

代码负责表达"做什么",注释负责解释"为什么"。

清晰的代码配合准确的文档,才能让一个 Rust 项目真正具备长期维护价值。


下一篇预告

下一篇我们将学习 Rust 控制流之 if 条件判断,包括:

  • ifelse ifelse

  • 比较运算符

  • 逻辑运算符

  • if 表达式

  • 条件分支返回值

  • 常见类型错误

  • 实战:根据用户等级计算折扣

相关推荐
前鼻音太阳熊1 小时前
【MES系统】MES为什么需要SSE?从设备实时监控谈Spring Boot流式推送设计
java·spring boot·后端
卷无止境1 小时前
Guardrails.ai:为大语言模型加一道"安全阀"
后端·python
尚早立志1 小时前
六)Spring Boot 源码研读之prepareContext 准备上下文
java·spring boot·后端·spring
Dovis(誓平步青云)2 小时前
《Linux CPU频率为何忽高忽低:cpufreq、idle状态与性能抖动教程》
linux·运维·服务器·spring boot·后端·生成对抗网络
To_OC9 小时前
手写 AI 编程 Agent 的命令执行工具:我被 child_process 坑出来的实战经验
后端·node.js·agent
花褪残红青杏小12 小时前
Rust图像处理第18节-分形画布缩放 + 拖拽交互:图像缩放 + f32 性能优化
rust·webassembly·图形学
逝水无殇12 小时前
C# 异常处理详解
开发语言·后端·c#
doiito12 小时前
【开源项目】用 Rust 重构中文 TTS!kokoroi-rs 高性能语音合成引擎介绍
ai·rust
铅笔侠_小龙虾14 小时前
Rust 学习目录
开发语言·学习·rust