Rust 入门 注释和文档之 cargo doc (二十三)
好的代码会说话,好的程序员不写注释,这些都是烂大街的“编程界俚语”,但是,如果你真的遇到一个不写注释的项目或程序员,。。。
在之前的章节,我们学习来包和模块如何使用,在此章节将进一步学习如何书写文档注释,以及如何使用 cargo doc 生成项目的文档。 最后将以一个包,模块和文档的综合例子,来将这些知识融合贯通
注释的种类
在Rust中,注释分为三类
代码注释,用于说明某以uaidaima的躬耕你,读者往往是同一个项目的协作开发者
文档注释,支持Markdown ,对项目描述,公共API 等用户关心的功能进行介绍,同时还能提供示例代码,目标读者往往是想要了解项目的人
包和模块注释,严格来说这也是文档注释中的一种,它主要用于说明当前包和模块的共轭能,方便用户迅速了解一个项目
通过这些注释,实现来Rust 极其优秀的文档化支持,甚至你还能在文档注释中写测试用例,省去来单独写测试用例的环节。
代码注释
显然之前的刮目相看是打了引号的, 想要去掉引号,该写注释的时候,就老老实实的,不过写时需要循序八字原则: 围绕目标,言简意赅,
代码注释方式有两种
行注释 //
fn main() {// 我是 Sun...// face let name = "sunface";let age = 18; // 今年好像是 18岁
}
如上所示,行注释可以放在某一行代码的上方,也可以放在当前代码行的后方。如果超出一行的长度,需要在新行的开头也加上 //
当注释行数较多时,还可以使用块注释
块注释/*....... */
fn main() {/* xxxx*/let name = "sunface";let age = "???"; // 今年其实。。。挺大了}
如上所示,只需要将注释内容使用 /* */ 进行包裹即可
文档注释
当查看一个 crate.io 上的包时,往往需要通过它提供的文档来浏览相关的功能特性,使用方式,这种文档就是通过文档注释实现的。
Rust 提供了 cargo doc 的命令,可以用于把这些文档注释转换成 HTML 网页文件,最终展示给用户浏览, 这样 用户就知道这个包做什么的 以及该如何使用。
文档行注释///
开门见山
/// `add_one` 将指定值加1
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {x + 1
}
以上代码有几点需要注意
文档注释需要位于 lib 类型的包中, 例如 src/lib.rs 中
文档注释可以使用markdown 语法,例如 # Examples 的标题,以及代码块高亮
被注释的对象需要使用 pub 对外可见,记住,文档注释是给用看的, 内部实现细节不应该被暴露出去
文档注释中的例子,为什么看上去显示能运行的颜值么? 竟然还是有 assert_eq 这中常用于测试目的的宏。
文档块注释 /** ... */
与代码注释一样,文档也有块注释,当注释内容多是,使用块注释可以减少 /// 的使用:
/** `add_two` 将指定值加2```
let arg = 5;
let answer = my_crate::add_two(arg);assert_eq!(7, answer);
```
*/
pub fn add_two(x: i32) -> i32 {x + 2
}
查看文档 cargo doc
锦衣不夜行,我们写了这么漂亮的文档注释,当然要看看网页中是什么效果。
很简单,运行 cargo doc 可以直接生成 HTML 文档,放入taget/doc 目录下
当然,为来方便,我们使用 cargo doc --open 命令,可以在生成文档后,自动在浏览器中打开网页,最终效果如图所示
常用文档标题
之前我们见到来在文档注释中该如何使用 markdown ,其中包括 # Examples 标题,还有一些常用的,你可以在项目中酌情是哦哪个
Panics: 函数可能会出现的异常情况,这样调用函数的人就可以提前规避
Errors 描述可能出现的错误及什么情况会导致错误,有助于调用者针对不同的错误采取不同的处理方式
Safety 如果函数使用unsafe 代码, 那么调用者就需要注意一些使用条件,以确保unsafe 代码块的正常工作。
话说回来, 这些标题更多的是一种惯例, 如果你非要用中文标题也没问题,但是最好在团队中保持同样的风格
包和模块级别的注释
除了函数,结构体等Rust项的注释,你还可以给包和模块添加注释,需要注意的是, 这些注释要添加到包、模块的最上方!
与之前的任何注释一样,包级别的注释也分为两种 : 行注释 //! 和 块注释 /*! ... */.
现在,为我们的包增加注释,在 src/lib.rs 包根的最三方,添加:
/*! lib包是world_hello二进制包的依赖包,里面包含了compute等有用模块 */pub mod compute;
然后在为该包根的子模块 src/compute.rs 添加注释
//! 计算一些你口算算不出来的复杂算术题
/// `add_one`将指定值加1
///
运行 cargo doc --open 看效果
包模块注释,可以让用户从整体的角度理解包的用户,对于用户来说非常友好的,就和一篇文章的开头一样,总是要对文章的内容进行大致的介绍。让用户在看的时候心中有数。
至此,关于如何注释的内容,就结束了,还有其它的用法, 一起来看看。
文档测试(Doc Test)
相信读者之前都写过单元测试用例,其中一个很蛋疼的问题就是,随着代码的进化,单元测试用例经常会失败,过段时间后, 你发项需要连续修改不少处代码,才能让测试重新工作起来,然而,在Rust中, 大可不必。
在之前的 add_one 中,我们写的示例代码非常像是一个单元测试的用例,这是偶然吗? 并不是,因为Rust 允许我们在文档注释中写单元测试用例!
/// `add_one` 将指定值加1
///
/// # Examples11
///
/// ```
/// let arg = 5;
/// let answer = world_hello::compute::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {x + 1
}
以上的注释不仅仅是文档,还可以作为单元测试的用例运行,使用cargo test 运行测试:
Doc-tests world_hello
running 2 tests
test src/compute.rs - compute::add_one (line 8) ... ok
test src/compute.rs - compute::add_two (line 22) ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.00s
可以看到,文档中的测试用例被完美运行,而且输出中也明确提示了 Doc-tests world_hello ,意味着这些测试的名字交错 Doc test 文档测试。
需要注意的是,你可能需要使用类如
world_hello::compute::add_one(arg)
的完整路径来调用函数,因为测试是在另外一个独立的线程中运行的
造成 panic 的文档测试
文档测试中的用例还可以造成 panic :
/// # Panics
///
/// The function panics if the second argument is zero.
///
/// ```rust
/// // panics on division by zero
/// world_hello::compute::div(10, 0);
/// ```
pub fn div(a: i32, b: i32) -> i32 {if b == 0 {panic!("Divide-by-zero error");}a / b
}
以上测试运行后会 panic :
---- src/compute.rs - compute::div (line 38) stdout ----
Test executable failed (exit code 101).stderr:
thread 'main' panicked at 'Divide-by-zero error', src/compute.rs:44:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
如果想要通过这种测试,可以添加 should_panic
/// # Panics
///
/// The function panics if the second argument is zero.
///
/// ```rust,should_panic
/// // panics on division by zero
/// world_hello::compute::div(10, 0);
/// ```
通过should_panic 告诉Rust 我们这个用例会导致 panic ,这样测试用例就能顺利通过。
保留测试,隐藏文档
在某些时候,我们希望保留文档测试的功能, 但是又要将某些测试用例的内容从文档中隐藏起来
/// ```
/// # // 使用#开头的行会在文档中被隐藏起来,但是依然会在文档测试中运行
/// # fn try_main() -> Result<(), String> {
/// let res = world_hello::compute::try_div(10, 0)?;
/// # Ok(()) // returning from try_main
/// # }
/// # fn main() {
/// # try_main().unwrap();
/// #
/// # }
/// ```
pub fn try_div(a: i32, b: i32) -> Result<i32, String> {if b == 0 {Err(String::from("Divide-by-zero"))} else {Ok(a / b)}
}
以上文档注释中,我们使用 # 将不想让用户看到的内容隐藏起来,但是又不影响测试用例的运行,最终用户将只能看到那行没有隐藏的 let res = wold_hello::compute::try_div(10,0)?; :
文档注释中的代码跳转
Rust在文档注释中还提供来一个非常强大的功能,那就是可以实现对外部项的链接
跳转到标准库
/// 'add_one' 返回一个 ['Option'] 类型
pub fn add_one(x: i32) -> Option<i32> {Some(x + 1)
}
此处的[option] 就是一个链接, 指向来标准库中的 Option 枚举类型, 有两种方式可以进行跳转
在 IDE 中,使用 Command + 鼠标左键(macOS), CTRL + 鼠标左键(Windows)
在文档中直接点链接
再比如,还可以使用路径的方式跳转:
use std::sync::mpsc::Receiver;/// ['Receiver<T>'] ['std::future']
///
/// ['std::future::Future'] ['Self::recv()']
pub struct AsyncReceiver<T> {sender: Receiver<T>,
}impl<T> AsyncReceiver<T> {pub async fn recv() -> T {unimplemented!()}
}
使用完整路径跳转到指定项
除了跳转到标准库,你还可以通过指定具体的路径跳转到自己代码或者其它库的指定项,例如在lib.rs 中添加以下代码:
pub mod a {/// 'add_one' 返回一个 ['Option']类型/// 跳转到 ['crate::MySpecialFormatter']pub fn add_one(x: i32) -> Option<i32> {Some(x + 1)}
}pub struct MySpecialFormatter;
使用 crate::MySpecialFormatter 这种路径就可以实现跳转到 lib.rs 中定义的结构体上。
同名项的跳转
如果遇到同名项,可以使用标示类型的方式进行跳转
/// 跳转到结构体 ['Foo'](struct@Foo)
pub struct Bar;/// 跳转到同名函数 ['Foo'](fn@Foo)
pub struct Foo {}/// 跳转到同名宏 ['foo!']
pub fn Foo() {}#[macro_export]
macro_rules! foo {() => {}
}
文档搜索别名
Rust 文档至此搜索功能, 我们可以为自己的类型定义几个别名,以实现更好的搜索展现,当别名命中时,搜索结果会被放在第一位:
#[doc(alias = "x")]
#[doc(alias = "big")]
pub struct BigX;#[doc(alias("y","big"))]
pub struct BigY;
一个综合例子
这个例子我们将重点应用几个知识点:
文档注释
一个项目可以包含两个包: 二进制可执行包和 lib 包 (库包) ,它们的包根分别是 src/main.rs 和 src/lib.rs
在二进制包中引用lib 包
使用 pub use 再导出 API ,并观察文档
首先,使用 cargo new art 创建一个 Package art;
Created binary (application) `art` package
系统提示我们创建了一个二进制 Package , 根据之前章节学过的内容,可以知道该 Package 包含一个同名的二进制包: 包名为art 包根为 src/main.rs , 该包可以编译成二进制然后运行。
现在,在src 目录下创建一个 lib.rs 文件, 根据之前学习的知识,创建该文件等于又创建了一个库类型的包, 包名也是 art ,包根为 src/lib.rs ,该包是库类型的,因此往往作为依赖库被引入
将以下内容添加到 src/lib.rs 中
//! # Art
//!
//! 未来的艺术建模库,现在的调色库pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;pub mod kinds {//! 定义颜色的类型/// 主色pub enum PrimaryColor {Red,Yellow,Blue,}/// 副色#[derive(Debug,PartialEq)]pub enum SecondaryColor {Orange,Green,Purple,}
}pub mod utils {//! 实用工具,目前只实现了调色板use crate::kinds::*;/// 将两种主色调成副色/// '''rust/// use art::utils::mix;/// use art::kinds::{PrimaryColor,SecondaryColor);/// assert!(matches!(mix(PriamaryColor::Yellow,PrimaryColor::Blue),SecondaryColor::Green));/// '''pub fn mix(c1:PrimaryColor,c2:PrimaryColor) -> SecondaryColor{SecondaryColor::Green}
}
在库包的包根 src/lib.rs 下, 我们又定义了几个子模块,同时将子模块中的三个项通过 pub use 进行了在导出
接着,将下面内容添加到 src/main.rs 中
use art::kinds::PrimaryColor;
use art::utils::mix;fn main() {let blue = PrimaryColor::Blue;let yellow =PrimaryColor::Yellow;println!("{:?}",mix(blue,yellow));
}
在二进制可执行包的包根 src/main.rs 下, 我们引入了库包 art 中的模块项,同时使用main 函数作为程序的入口,该二进制包可以使用 cargo run 运行
Green
至此,库包完美提供了用于调色的api ,二进制包引入这些API 完美的实现了料色并打印输出。
总结
在Rust 中, 注释分为主要三种类型: 代码注释,文档注释,包和模块注释,每个注释类型都拥有两种形式,行注释和块注释,熟练掌握包模块和注释的知识,非常有主语我们创建工程性更强的项目。