一、先把基础摆正
教程给了四条结论,值得原样记住:
- 注释是写给人看的,计算机根本不执行它。
//开头的是单行注释,可以独占一行,也可以跟在代码后面。/* ... */包起来的是多行注释。- 不需要给每一行都写注释------好代码应该自己说得清楚,注释只留给「不显然」的地方。
对应的三段代码,编译运行后的结果(实测):
rust
fn main() {
// This is a comment; the computer ignores this line
let x = 5; // You can also put one after code
println!("{}", x);
}
// 输出:5
rust
fn main() {
/*
This is a multi-line comment
It can span several lines
The computer ignores all of it
*/
let x = 10;
println!("{}", x);
}
// 输出:10
教程还提到一个高频用法:临时把某行代码「注释掉」,而不是删掉。
rust
fn main() {
let x = 5;
// println!("{}", x); // Not printing for now, but don't want to delete it
println!("Program finished");
}
// 输出:Program finished
这里有个教程没提、但实跑就会撞上的副作用:
javascript
warning: unused variable: `x`
--> c.rs:2:9
|
2 | let x = 5;
| ^ help: if this is intentional, prefix it with an underscore: `_x`
把唯一使用 x 的那行注释掉之后,x 立刻变成未使用变量,编译器开始告警。注释不是「让代码消失」,它改变了代码本身的语义环境------调试时看到一堆新警告,先想想是不是刚注释掉了什么。
二、编译器视角:注释到底去哪了
「注释不执行」是入门说法,更准确的一句在 The Rust Reference 里:
Non-doc comments are interpreted as a form of whitespace.
也就是说,普通注释在词法分析阶段就被当成空白丢掉了,它连 AST 都进不去,更不会变成任何一条指令。推论很直接:注释写多写少、写英文还是中文,都不影响生成代码和运行性能。
但 Reference 里还有一句转折:
Line doc comments beginning with exactly three slashes (
///) ... are interpreted as a special syntax fordocattributes.
文档注释不是空白,它是属性。 这是 Rust 和普通 C 系语言最大的分野:注释在这里升级成了 API 契约的一部分。
三、文档注释:四种写法,两种位置
| 写法 | 类型 | 等价形式 | 作用对象 |
|---|---|---|---|
/// |
outer 行文档 | #[doc=" ..."] |
紧跟其后的那个 item |
/** ... */ |
outer 块文档 | #[doc=" ..."] |
紧跟其后的那个 item |
//! |
inner 行文档 | #![doc=" ..."] |
所在的模块 / crate |
/*! ... */ |
inner 块文档 | #![doc=" ..."] |
所在的模块 / crate |
/// 用在函数、结构体、枚举前面,//! 用在文件开头(描述整个模块或 crate)。因为它们是属性,所以必须出现在能接收属性的东西前面 ------实测把 /// 孤零零放在文件末尾:
kotlin
error: expected item after doc comment
--> f.rs:3:1
|
3 | /// a doc comment with no item following it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this doc comment doesn't document anything
不是警告,是编译错误。而普通注释放在哪都无所谓。
「恰好三个」是个坑
Reference 特意强调 exactly three slashes。实测下面这段能正常编译并输出 ok:
rust
fn main() {
//// four slashes: ordinary comment
/*** three asterisks: ordinary comment */
println!("ok");
}
//// 后面没有 item,也完全没事
//// 和 /*** 都只是普通注释。很多人写分隔线时顺手打四个斜杠,本意是「更醒目」,结果它并不会出现在生成的文档里。真要写文档,请严格 ///;真要写分隔线,请坚持 //// 或 // -----,别混。
注释里的代码真的会跑
Rust 最实用的一个设计:/// 里的 Markdown 代码块会被 rustdoc 当成 doctest 执行。我在一个临时 cargo 库里写了两个函数,一个断言正确、一个故意写错:
rust
//! 中文注释示例:Rust 源文件默认 UTF-8,注释里写中文完全没问题。
/// 把两个整数相加。
///
/// # Examples
///
/// ```
/// use docdemo::add;
/// assert_eq!(add(1, 2), 3);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// 整数除法(取整)。
///
/// 下面这个 doctest 故意写错断言,用来验证注释里的代码真的会被执行。
///
/// ```
/// use docdemo::div;
/// assert_eq!(div(6, 2), 4);
/// ```
pub fn div(a: i32, b: i32) -> i32 {
a / b
}
cargo test --doc 的结果:
css
running 2 tests
test src/lib.rs - add (line 7) ... ok
test src/lib.rs - div (line 19) ... FAILED
assertion `left == right` failed
left: 3
right: 4
test result: FAILED. 1 passed; 1 failed
注释里的示例代码会随 CI 一起被验证,写错就挂。 这比任何「文档与代码同步」的口号都有效。顺便也验证了两件事:中文注释没问题(源文件默认 UTF-8),以及 //! 放在文件头是合法的 crate 级文档。
这里还有个新手常踩的坑:doctest 是独立编译的代码片段 ,不会自动看见被注释的函数。我最初用单文件 rustdoc --test g.rs 时,注释里直接写 assert_eq!(add(1, 2), 3),报的是:
javascript
error[E0425]: cannot find function `add` in this scope
正确写法是显式 use 库名::函数名;(如上例的 use docdemo::add;)。库项目里 cargo test --doc 会把 crate 作为外部依赖注入;单文件模式下则需要自己把代码写全。
块文档注释怕 */
Reference 的 Note 里专门提醒:块文档注释不解析内部 Markdown,遇到第一个 */ 就结束。实测这句:
rust
/** Docs mentioning the `*/` marker inside a block doc comment */
直接编译失败:
go
error: unknown start of token: `
error: expected one of `!` or `::`, found `inside`
注释在 */ 处提前闭合,后面的文字被当成 Rust 代码解析了。想在文档里写 */ 这种字面量,老实用 /// 行文档,它逐行生效,没有终止符问题。
让编译器逼你写文档
rust
#![warn(missing_docs)]
pub fn public_api() {}
fn main() {}
实测产出两条警告:
r
warning: missing documentation for the crate
warning: missing documentation for a function
把 #![warn(missing_docs)] 换成 #![deny(missing_docs)],公共 API 漏写文档就直接构建失败。库项目里这一行比代码评审管用。
四、块注释可以嵌套,这点 Rust 和 C/Java 不一样
Reference 写得很干脆:
Comments follow the general C++ style of line (
//) and block (/* ... */) comment forms. Nested block comments are supported.
实测:
rust
fn main() {
/* outer /* inner /* deepest */ still comment */ end */
let x = 7;
println!("{}", x);
}
// 输出:7
在 C/C++/Java 里,第一个 */ 就会把整段注释关掉,里面的 /* 只是普通字符,所以「注释掉一大段代码」经常因为代码里原本有块注释而翻车。Rust 支持配对嵌套,可以放心用 /* */ 包住整段代码调试。文档注释的三种块形式之间也能互相嵌套,Reference 的示例里给了完整的组合表。
五、什么时候该写注释
原教程列了三条:逻辑不显然时、怕几天后回头看不懂时、临时停用某行代码时。我补四条工程侧的:
- 写「为什么」,别写「是什么」。
// 把 i 加 1是噪音;// 索引从 1 开始,因为上游协议把 0 留作心跳包才是信息。后者无法靠命名表达。 - 能用命名和类型解决的,别用注释。
let retry_count = 3;不需要// 重试三次;把魔法数字提成常量、把bool换成语义明确的枚举,注释自然就消失了。 - 注释掉的代码不要提交。 临时屏蔽用来调试没问题,但提交前请删掉------版本历史属于 git,不属于注释。而且如第一节所示,它还会顺手制造未使用变量的警告。
- 给工具链留约定。
// TODO:/// FIXME:/// SAFETY:(unsafe 块说明安全性论证)这类固定前缀,IDE、CI、评审都认;配合missing_docs和cargo doc,让注释进入自动化流程而不是靠自觉。
一句话总结原教程那条提醒:注释的密度和代码质量往往成反比。真正稀缺的不是解释,是被解释的那个决策背后的上下文。
六、速查
| 场景 | 用什么 | 注意 |
|---|---|---|
| 普通说明 | //、/* */ |
当空白处理,不影响生成代码 |
| 公共 API 文档 | /// |
恰好三个斜杠;后面必须有 item |
| 模块 / crate 说明 | //! |
放文件头,等价 #![doc=...] |
| 文档里放示例 | /// + ``````````` 代码块 |
会被 cargo test --doc 执行,记得 use |
| 临时屏蔽代码 | // |
别提交;留意连带产生的 warning |
| 大段注释掉代码 | /* */ |
Rust 支持嵌套,比 C/Java 安全 |
文档里出现 */ |
改用 /// |
块文档注释会在第一个 */ 断开 |
小结
教程那一章讲的是「注释是什么」,一句话就能记住:注释给人看,编译器不管。但真写 Rust 时更有用的是后半句------普通注释是空白,文档注释是属性 。前者随便写,后者会被编译、被 rustdoc 收集、被 cargo test --doc 执行,写错斜杠数量、写错断言、写错终止符,都会以编译错误的形式回来找你。