引言
这个系列是对 RustChinaConf 2022 线上大会议题的回顾,后面等官方 RustConf 2022 的视频出来也会有相关回顾文章。
本次大会分为一个主会场和七个分会场,涉及二十多个议题,不要着急,让我们一篇一篇回顾。
本系列回顾文章不是演讲全文的文字稿,而是重点摘要。
主题简介
Guillaume Gomez,华为工程师,rustdoc团队负责人。我的最终目标是使文档的编写尽可能简单,以便所有项目都有很好的文档
Rustdoc是官方的Rust工具,可以为你的crates生成文档。但它所做的远不止这些:它允许你测试你的代码示例,在文档和项目的源代码之间切换,将markdown文件转换为HTML,自动生成项目的链接,显示文档的覆盖范围,它有自己的行数,它允许,等等。
但这还不是全部! 很多更美好的功能即将到来。例如,其中一个功能允许你从源代码页面直接跳到一个项目的定义或其文档页面。
本讲座[1]将介绍这些功能,让你了解rustdoc允许你做什么。
演讲摘要
什么是 rustdoc
rustdoc 是和 Rust 编译器 rustc
一起提供的工具,用于生成代码的文档。通过 cargo doc
命令开发者就可以用其生成文档。
# 创建项目
> cargo new --lib basic
# 生成文档
> cargo doc
# 使用 --open 参数直接从浏览器打开文档
> cargo doc --open
rustdoc 生成的文档页面功能非常丰富:
- 有搜索框,可以搜索你的crate定义的类型、trait和函数等
- 可以通过
[src]
链接来查看源吗 - 文档示例中的代码可以通过
Run
按钮一键打开playground
- 其他
很多功能 Guillaume 在演讲视频中也做了分享,可以自行摸索。
如何用 rustdoc 创建文档
在 Rust 里可以通过基本的文档注释 ///
和 //!
来创建文档, Guillaume 在演讲视频中也做了演示,因为这是最基本的语法,本文里就不做过多介绍。
“注:虽然 Rust 文档支持 Markdown,Guillaume 在演讲中说也可以支持图片,但实际上 rustdoc 中支持图片并没有普通 Markdown 文件那么方便。参加 issues 32104[2] 。有第三方库可以方便地让文档支持图片,embed-doc-image[3]。对于
svg
,可以直接用#![doc=include_str!("shared-bus.svg")]
。
使用 rustdoc 进行文档测试
文档注释中的代码示例,可以通过 cargo test
来运行,这是 rustdoc 提供的文档测试功能。利用文档测试可以做很多事情,比如:
- 在编写宏的时候,可以将文档测试中的代码设置为测试时编译失败
- 在一些场景中忽略某些测试代码
- 其他
相关设置:
代码语言:javascript复制// 使用 ignore 可以让测试忽略代码
/// ```ignore
/// fn foo() {
/// ```
// should_panic 告诉 rustdoc 代码应该正确编译,但在执行过程中会出现恐慌
// 如果代码没有恐慌,测试将失败。
/// ```should_panic
/// assert!(false);
/// ```
// no_run属性将编译代码但不运行它
// 在一些希望编译但没有运行环境的场景下可以使用它
// 也可以演示一些导致未定义行为的代码片段
/// ```no_run
/// loop {
/// println!("Hello, world");
/// }
/// ```
// compile_fail告诉rustdoc编译应该失败
// 如果它编译,那么测试将失败
/// ```compile_fail
/// let x = 5;
/// x = 2; // shouldn't compile!
/// ```
// edition2015,edition2018 和 edition2021 用于告诉rustdoc,
// 代码示例应该使用相应版本的 Rust 编译
/// Only runs on the 2018 edition.
///
/// ```edition2018
/// let result: Result<i32, ParseIntError> = try {
/// "1".parse::<i32>()?
/// "2".parse::<i32>()?
/// "3".parse::<i32>()?
/// };
/// ```
文档内链接
Rustdoc 能够使用项目的路径作为链接直接链接到其他 rustdoc 页面。这被称为“文档内链接”。其语法与 Markdown 添加链接语法略有不同。
代码语言:javascript复制// 下面这几种方式都可以链接到 Bar
/// This struct is not [Bar]
pub struct Foo1;
/// This struct is also not [bar](Bar "bar")
pub struct Foo2;
/// This struct is also not [bar][b]
///
/// [b]: Bar
pub struct Foo3;
/// This struct is also not [`Bar`]
pub struct Foo4;
/// This struct *is* [`Bar`]!
pub struct Bar;
use std::sync::mpsc::Receiver;
// rustdoc 支持链接到 标准库文档中的类型
/// This is a version of [`Receiver<T>`] with support for [`std::future`].
///
/// You can obtain a [`std::future::Future`] by calling [`Self::recv()`].
pub struct AsyncReceiver<T> {
sender: Receiver<T>
}
impl<T> AsyncReceiver<T> {
pub async fn recv() -> T {
unimplemented!()
}
}
/// This is a special implementation of [positional parameters].
///
/// [positional parameters]: std::fmt#formatting-parameters
struct MySpecialFormatter;
// 可以使用 `@` 来消除同名歧义
// 指定是 结构体 Foo
/// See also: [`Foo`](struct@Foo "`Foo`")
/// This struct *is* [`Bar`]!
pub struct Bar;
// 指定是函数 Foo
/// This is different from [`Foo`](fn@Foo "`Foo`")
pub struct Foo {}
pub fn Foo() {}
链接一般以语言项(item)定义的模块为解析范围,哪怕 item 被重导出也一样。如果来自另一个 crate 的链接无法被解析则会发出警告。
“注意:由于
macro_rules!
宏的文档内链接 将相对于 crate root[4]进行解析,而不是定义它的模块。
rustdoc lint
rustdoc
提供 lints 来帮助开发者编写和测试文档。比如:
#![allow(rustdoc::broken_intra_doc_links)] // allows the lint, no diagnostics will be reported
#![warn(rustdoc::broken_intra_doc_links)] // warn if there are broken intra-doc links
#![deny(rustdoc::broken_intra_doc_links)] // error if there are broken intra-doc links
这些lint 仅在执行 rustdoc 时可用。
rustdoc lints 可以作以下分类:
- 默认产生警告的lints
- BROKEN_INTRA_DOC_LINKS
- PRIVATE_INTRA_DOC_LINKS
- INVALID_CODEBLOCK_ATTRIBUTES
- BARE_URLS
- INVALID_RUST_CODEBLOCK
- 默认允许的 lints
- MISSING_CRATE_LEVEL_DOCS
- MISSING_DOC_CODE_EXAMPLES
- PRIVATE_DOC_TESTS
- INVALID_HTML_TAGS
详细解释可以参阅 rustdoc book。
文档注释工作机制
代码中的文档注释 ///
在编译时会被 #[doc]
属性替换。比如:
/// This struct is not [Bar]
pub struct Foo1;
被替换为:
代码语言:javascript复制#[doc = " This struct is not [Bar]" ]
pub struct Foo1;
所以,///
是 #[doc]
属性宏的语法糖。
知道这个知识点有什么用呢?利用 #[doc]
允许我们可以更好地编写文档。
可以把 README 复用为文档注释
代码语言:javascript复制#[doc = include_str!("../../README.md")]
可以设置别名
代码语言:javascript复制#[doc(alias = "TheAlias")]
pub struct SomeType;
在文档中可以按设置的别名进行搜索,此功能在 FFi 时非常有用:
代码语言:javascript复制pub struct Obj {
inner: *mut ffi::Obj,
}
impl Obj {
#[doc(alias = "lib_name_do_something")]
pub fn do_something(&mut self) -> i32 {
unsafe { ffi::lib_name_do_something(self.inner) }
}
}
这样就可以在文档中按被包装的 C 方法原函数名(lib_name_do_something)去搜索到 Obj::do_something
。
关于 rustdoc 还有很多功能可以去 rustdoc book 中查阅。
延伸阅读
- rustdoc book[5]
- Nightly 下 rustdoc 功能[6]
参考资料
[1]
本讲座: https://www.bilibili.com/video/BV1eg411C77X
[2]
32104: https://github.com/rust-lang/rust/issues/32104
[3]
embed-doc-image: https://github.com/Andlon/embed-doc-image
[4]
相对于 crate root: https://github.com/rust-lang/rust/issues/72243
[5]
rustdoc book: https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html
[6]
Nightly 下 rustdoc 功能: https://doc.rust-lang.org/rustdoc/unstable-features.html