4.4.文档

最后更新于:2022-04-01 00:43:09

文档是任何软件项目中重要的一部分,并且它在Rust中是一级重要的。让我们讨论下Rust提供给我们编写项目文档的的工具。 ## 关于`rustdoc` Rust发行版中包含了一个工具,`rustdoc`,它可以生成文档。`rustdoc`也可以在Cargo中通过`cargo doc`。 文档可以使用两种方法生成:从源代码,或者从单独的Markdown文件。 ### 文档化源代码 文档化Rust项目的主要方法是在源代码中添加注释。为了这个目标你可以这样使用文档注释: ~~~ /// Constructs a new `Rc<T>`. /// /// # Examples /// /// ~~~ /// use std::rc::Rc; /// /// let five = Rc::new(5); /// ``` pub fn new(value: T) -> Rc { // implementation goes here } ~~~ 这段代码产生像[这样](http://doc.rust-lang.org/nightly/std/rc/struct.Rc.html#method.new)的文档。我忽略了函数的实现,而是留下了一个标准的注释。第一个需要注意的地方是这个注释:它使用了`///`,而不是`//`。三斜线表明这是文档注释。 文档注释用Markdown语法编写。 Rust会记录这些注释,并在生成文档时使用它们。这在文档化像数组这样的结构时很重要: ```rust /// The `Option` type. See [the module level documentation](../) for more. enum Option<T> { /// No value None, /// Some value `T` Some(T), } ~~~ 上面的代码可以工作,但这个不行: ~~~ /// The `Option` type. See [the module level documentation](../) for more. enum Option<T> { None, /// No value Some(T), /// Some value `T` } ~~~ 你会得到一个错误: ~~~ hello.rs:4:1: 4:2 error: expected ident, found `}` hello.rs:4 } ^ ~~~ 这个[不幸的错误](https://github.com/rust-lang/rust/issues/22547)是有道理的:文档注释适用于它后面的内容,而在在最后的注释后面没有任何内容。 ### 编写文档注释 不管怎样,让我们来详细了解一下注释的每一部分: ~~~ /// Constructs a new `Rc<T>`. ~~~ 文档注释的第一行应该是他功能的一个简要总结。一句话。只包括基础。高层次。 ~~~ /// /// Other details about constructing `Rc<T>`s, maybe describing complicated /// semantics, maybe additional options, all kinds of stuff. /// ~~~ 我们原始的例子只有一行总结,不过如果有更多东西要写,我们在一个新的段落增加更多解释。 ### 特殊部分 ~~~ /// # Examples ~~~ 下面,是特殊部分。它由一个标头表明,`#`。有三种经常使用的标头。它们不是特殊的语法,只是传统,目前为止。 ~~~ /// # Panics ~~~ 不可恢复的函数滥用(也就是说,程序错误)在Rust中通常用恐慌表明,它会在最后杀死整个当前的线程。如果你的函数有这样有意义的被识别为或者强制为恐慌的约定,记录文档是非常重要的。 ~~~ /// # Failures ~~~ 如果你的函数或方法返回`Result`,那么描述何种情况下它会返回`Err(E)`是件好事。这并不如`Panics`重要,因为失败被编码进了类型系统,不过仍旧是件好事。 ~~~ /// # Safety ~~~ 如果你的函是`unsafe`的,你应该解释调用者应该支持哪种不可变量。 ~~~ /// # Examples /// /// ~~~ /// use std::rc::Rc; /// /// let five = Rc::new(5); /// ``` ~~~ 第三个,`Examples`。包含一个或多个使用你函数的例子,这样你的用户会为此感(ai)谢(shang)你的。这些例子写在代码块注释中,我们稍后会讨论到,并且可以you不止一个部分: ```rust /// # Examples /// /// Simple `&str` patterns: /// /// ~~~ /// let v: Vec = "Mary had a little lamb".split(' ').collect(); /// assert_eq!(v, vec!["Mary", "had", "a", "little", "lamb"]); /// `/// /// More complex patterns with a lambda: /// ///` /// let v: Vec = "abc1def2ghi".split(|c: char| c.is_numeric()).collect(); /// assert_eq!(v, vec!["abc", "def", "ghi"]); /// ``` ~~~ 让我们聊聊这些代码块的细节。 ### 代码块注释 在注释中编写Rust代码,使用三重音符: ```rust /// ~~~ /// println!("Hello, world"); /// ``` ~~~ 如果你想要一些不是Rust的代码,你可以加上一个注释: ```rust /// ```c /// printf("Hello, world\n"); /// ~~~ ~~~ 这回根据你选择的语言高亮代码。如果你只是想展示普通文本,选择`text`。 选择正确的注释是很重要的,因为`rustdoc`用一种有意思的方法是用它:它可以用来实际测试你的代码,这样你的注释就不会过时。如果你写了些C代码不过`rustdoc`会认为它是Rust代码由于你忽略了注释,`rustdoc`会在你生成文档时抱怨。 ## 文档作为测试 让我们看看我的例子文档的样例: ```rust /// ~~~ /// println!("Hello, world"); /// ``` ~~~ 你会注意到你并不需要`fn main()`或者别的什么函数。`rustdoc`会自动一个`main()`包装你的代码,并且在正确的位置。例如: ```rust /// ~~~ /// use std::rc::Rc; /// /// let five = Rc::new(5); /// ``` ~~~ 这回作为测试: ```rust fn main() { use std::rc::Rc; let five = Rc::new(5); } ~~~ 这里是`rustdoc`用来后处理例子的完整的算法: 1. 任何`#![foo]`开头的属性会被完整的作为包装箱属性 2. 一些通用的`allow`属性被插入,包括`unused_variables`,`unused_assignments`,`unused_mut`,`unused_attributes`和`dead_code`。小的例子经常触发这些lint检查 3. 如果例子并未包含`extern crate`,那么`extern crate ;`被插入 4. 最后,如果例子不包含`fn main`,剩下的文本将被包装到`fn main() { your_code }`中 有时,这是不够的。例如,我们已经考虑到了所有`///`开头的代码样例了吗?普通文本: ~~~ /// Some documentation. # fn foo() {} ~~~ 与它的输出看起来有些不同: ~~~ /// Some documentation. ~~~ 是的,你猜对了:你写的以`#`开头的行会在输出中被隐藏,不过会在编译你的代码时被使用。你可以利用这一点。在这个例子中,文档注释需要适用于一些函数,所以我只想向你展示文档注释,我需要在下面增加一些函数定义。同时,这只是用来满足编译器的,所以省略它会使得例子看起来更清楚。你可以使用这个技巧来详细的解释较长的例子,同时保留你文档的可测试行。例如,这些代码: ~~~ let x = 5; let y = 6; println!("{}", x + y); ~~~ 这是表现出来的解释: 首先,我们把`x`设置为`5`: ~~~ let x = 5; ~~~ 接着,我们把`y`设置为`6`: ~~~ let y = 6; ~~~ 最后,我们打印`x`和`y`的和: ~~~ println!("{}", x + y); ~~~ 这是同样的解释的原始文本: ~~~ 首先,我们把`x`设置为`5`: let x = 5; # let y = 6; # println!("{}", x + y); 接着,我们把`y`设置为`6`: # let x = 5; let y = 6; # println!("{}", x + y); 最后,我们打印`x`和`y`的和: # let x = 5; # let y = 6; println!("{}", x + y); ~~~ 通过重复例子的所有部分,你可以确保你的例子仍能编译,同时只显示与你解释相关的部分。 ### 文档化宏 下面是一个宏的文档例子: ~~~ /// Panic with a given message unless an expression evaluates to true. /// /// # Examples /// /// ~~~ /// # #[macro_use] extern crate foo; /// # fn main() { /// panic_unless!(1 + 1 == 2, “Math is broken.”); /// # } /// `/// ///`should_panic /// # #[macro_use] extern crate foo; /// # fn main() { /// panic_unless!(true == false, “I’m broken.”); /// # } /// ``` # [macro_export] macro_rules! panic_unless { ($condition:expr, $($rest:expr),+) => ({ if ! $condition { panic!($($rest),+); } }); } ~~~ 你会注意到3个地方:我们需要添加我们自己的`extern crate`行,这样我们可以添加`#[macro_use]`属性。第二,我们也需要添加我们自己的`main()`。最后,用`#`机智的注释掉这两个代码,这样它们不会出现在输出中。 ### 运行文档测试 要运行测试,那么 ```bash $ rustdoc --test path/to/my/crate/root.rs # or $ cargo test ~~~ 对了,`cargo test`也会测试嵌入的文档。 这还有一些注释有利于帮助`rustdoc`在测试你的代码时正常工作: ~~~ /// ```ignore /// fn foo() { /// ~~~ ~~~ `ignore`指令告诉Rust忽略你的代码。这几乎不会是你想要的,因为这是最不受支持的。相反,考虑注释为`text`如果不是代码的话,或者使用`#`来形成一个只显示你关心部分的例子。 ```rust /// ```should_panic /// assert!(false); /// ~~~ ~~~ `should_panic`告诉`rustdoc`这段代码应该正确编译,但是作为一个测试则不能通过。 ```rust /// ```no_run /// loop { /// println!("Hello, world"); /// } /// ~~~ ~~~ `no_run`属性会编译你的代码,但是不运行它。这对像如“如何开始一个网络服务”这样的例子很重要,你会希望确保它能够编译,不过它可能会无限循环的执行! ### 文档化模块 Rust有另一种文档注释,`//!`。这种注释并不文档化接下来的内容,而是包围它的内容。换句话说: ```rust mod foo { //! This is documentation for the `foo` module. //! //! # Examples // ... } ~~~ 这是你会看到`//!`最常见的用法:作为模块文档。如果你在`foo.rs`中有一个模块,打开它你常常会看到这些: ~~~ //! A module for using `foo`s. //! //! The `foo` module contains a lot of useful functionality blah blah blah ~~~ ### 文档注释风格 查看[RFC 505](https://github.com/rust-lang/rfcs/blob/master/text/0505-api-comment-conventions.md)以了解文档风格和格式的惯例。 ### 其它文档 所有这些行为都能在非Rust代码文件中工作。因为注释是用Markdown编写的,它们通常是`.md`文件。 当你在Markdown文件中写文档时,你并不需要加上注释前缀。例如: ~~~ /// # Examples /// /// ~~~ /// use std::rc::Rc; /// /// let five = Rc::new(5); /// ``` ~~~ 就是 > ~~~ > # Examples > > use std::rc::Rc; > > let five = Rc::new(5); ``` 当在一个Markdown文件中。不过这里有个窍门:Markdown文件需要有一个像这样的标题: ~~~ % The title This is the example documentation. ~~~ `%`行需要放在文件的第一行。 ## `doc`属性 在更底层,文档注释是文档属性的语法糖: ~~~ /// this #[doc="this"] ~~~ 跟下面这个是相同的: ~~~ //! this #![doc="/// this"] ~~~ 写文档时你不会经常看见这些属性,不过当你要改变一些选项,或者写一个宏的时候比较有用。 ## 重导出(Re-exports) `rustdoc`会将公有部分的文档重导出: ~~~ extern crate foo; pub use foo::bar; ~~~ 这回在`foo`包装箱中生成文档,也会在你的包装箱中生成文档。它会在两个地方使用相同的内容。 这种行文可以通过`no_inline`来阻止: ~~~ extern crate foo; #[doc(no_inline)] pub use foo::bar; ~~~ ## 控制HTML 你可以通过`#![doc]`属性控制`rustdoc`生成的THML文档的一些方面: ~~~ #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", html_favicon_url = "http://www.rust-lang.org/favicon.ico", html_root_url = "http://doc.rust-lang.org/")]; ~~~ 这里设置了一些不同的选项,带有一个logo,一个收藏夹,和一个根URL。 ## 通用选项 `rustdoc`也提供了一些其他命令行选项,以便进一步自定义: * `--html-in-header FILE`:在`...`部分的末尾加上`FILE`内容 * `--html-before-content FILE`:在``之后,在渲染内容之前加上`FILE`内容 * `--html-after-content FILE`:在所有渲染内容之后加上`FILE`内容 ## 注解安全 文档注释中的Markdown会被不加处理的放置于最终的网页中。注意HTML文本(XSS?): ~~~ /// <script>alert(document.cookie)</script> ~~~
';