文檔是軟件項目的重要組成部分,而且它在 Rust 中是第一位的。讓我們看看怎么使用 Rust 給出的工具來記錄你的項目。
Rust發(fā)行版包含一個工具 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<T> {
// implementation goes here
}
這段代碼生成如下所示的文檔。在這里只保留了它本來位置的常規(guī)的注釋,而省去了實現(xiàn)。關(guān)于這個注釋的第一件需要注意的事:它使用 ///
,而不是 //
。即三重斜線表示文檔的注釋。
在 markdown 中書寫文檔的注釋。
Rust 跟蹤這些注釋,并在生成文檔時使用它們。當(dāng)記錄一些諸如枚舉時,這是重要的:
/// The `Option` type. See [the module level documentation](../) for more.
enum Option<T> {
/// No value
None,
/// Some value `T`
Some(T),
}
上面的代碼會正常執(zhí)行,但是下面的代碼不會正常執(zhí)行:
/// 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 }
這個 unfortunate error 是正確的:文檔注釋適用于緊隨其后的內(nèi)容,但是不能出現(xiàn)在語句的最后。
不管怎樣,讓我們先詳細(xì)了解每個部分的注釋:
/// Constructs a new `Rc<T>`.
第一行的文檔注釋應(yīng)該是功能的一個簡短的摘要。一個句子,只需要是最基本的,高水平的。
///
/// Other details about constructing `Rc<T>`s, maybe describing complicated
/// semantics, maybe additional options, all kinds of stuff.
///
我們最初的例子只是一行注釋,但如果我們有更多的事情需要說明,我們可以在一個新的段落添加更多的解釋。
/// # Examples
接下來是特殊部分。這些以一個頭 # 表示。有三種常用的頭文件。他們沒有特殊的語法,現(xiàn)在只是有約定俗成的用法,
/// # Panics
在Rust中,一個函數(shù)出現(xiàn)不可恢復(fù)的錯誤(即編程錯誤)通常由 panics 表示,它會至少結(jié)束整個當(dāng)前線程。如果你的函數(shù)有這樣一個重要的約定,它可以被 panics 檢測/執(zhí)行,記錄就變得非常重要。
/// # Failures
如果你的函數(shù)或方法返回一個Result<T, E>,這個結(jié)果描述了現(xiàn)在的狀況,如果返回 Err(E),表示現(xiàn)在狀況還不是很壞。這和 Panics 相比就顯得稍微不那么重要了,因為錯誤已經(jīng)被編碼,但它仍然不是一件特別壞的事。
/// # Safety
如果你的功能是 unsafe 的,你應(yīng)該向調(diào)用者解釋哪些不變量是負(fù)責(zé)維護(hù)的。
/// # Examples
///
/// ```
/// use std::rc::Rc;
///
/// let five = Rc::new(5);
/// ```
第三,Examples。包括一個或多個使用你的函數(shù)或方法的例子,你的用戶會因為這些函數(shù)或方法而愛你。這些例子都包含了代碼塊注釋,我們將討論這部分:
/// # Examples
///
/// Simple `&str` patterns:
///
/// ```
/// let v: Vec<&str> = "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<&str> = "abc1def2ghi".split(|c: char| c.is_numeric()).collect();
/// assert_eq!(v, vec!["abc", "def", "ghi"]);
/// ```
讓我們討論一下這些代碼塊的細(xì)節(jié)。
使用三重斜線對 Rust 代碼進(jìn)行注釋:
/// ```
/// println!("Hello, world");
/// ```
如果你想要某些代碼不是 Rust 代碼,您可以添加一個注解:
/// ```c
/// printf("Hello, world\n");
/// ```
根據(jù)你選擇的任何語言突出顯示這些內(nèi)容。如果你只想顯示純文本,只需要選擇 text 就可以了。
選擇正確的注釋是很重要的,因為 rustdoc 以一個有趣的方式使用注釋:實際上它可以用來測試你的例子,所以,他們離不開日期。如果你有一些C代碼,但 rustdoc 認(rèn)為它是 Rust,因為你沒有使用注釋,當(dāng)試圖生成文檔 rustdoc 會產(chǎn)生異常。
讓我們討論一下文檔樣例示例:
/// ```
/// println!("Hello, world");
/// ```
你會注意到,你不需要一個 fn main() 或任何東西。rustdoc 會在你的代碼中的正確位置自動添加一個 main()函數(shù)。例如:
/// ```
/// use std::rc::Rc;
///
/// let five = Rc::new(5);
/// ```
這是最終的測試:
fn main() {
use std::rc::Rc;
let five = Rc::new(5);
}
這是 rustdoc 使用后處理例子的完整算法:
然而,有時這還是不夠的。例如,所有這些代碼示例中,我們用 ///
來標(biāo)注我們在說什么,原始文本:
/// Some documentation.
# fn foo() {}
輸出看起來有些不同:
/// Some documentation.
是的,這是正確的:你可以以 # 開始添加注釋,輸出時這些注釋將被隱藏,編譯代碼時這些注釋將會被使用。你可以利用這個優(yōu)勢。在這種情況下,文檔注釋需要適用于某種功能,所以如果我想給一個文檔注釋,我就需要添加一個小函數(shù)來定義它。同時,它只是為了滿足編譯器,所以隱藏它會使樣例更加清晰。您可以使用這種方法來解釋更詳細(xì)的例子,同時仍然保留文檔的可測試性。例如,這段代碼:
let x = 5;
let y = 6;
println!("{}", x + y);
下面呈現(xiàn)一個解釋:
首先,我們將 x 設(shè)置為 5:
let x = 5;
接下來,我們將 y 設(shè)置為 6:
let y = 6;
最后,我們打印 x 和 y 的總和:
println!("{}", x + y);
這是在原始文本中的相同的解釋:
First, we set x to five:
let x = 5;
# let y = 6;
# println!("{}", x + y);
Next, we set y to six:
# let x = 5;
let y = 6;
# println!("{}", x + y);
Finally, we print the sum of x and y:
# let x = 5;
# let y = 6;
println!("{}", x + y);
通過重復(fù)例子的所有部分,您可以確保您的例子仍然可以編譯,而只顯示跟你的解釋相關(guān)的那部分。
這里有一個宏的例子:
/// 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),+); } });
}
你需要注意三件事:添加自己的 extern crate 行,這樣我們就可以添加 #[macro_use] 屬性。第二,我們需要添加自己的 main() 函數(shù)。最后,明智地使用 # 注釋掉這兩個東西,確保他們不會顯示在輸出中。
運行測試
$ rustdoc --test path/to/my/crate/root.rs
# or
$ cargo test
cargo test 也可以對嵌套文檔進(jìn)行測試。然而,cargo test只能測試函數(shù)庫而不能測試二進(jìn)制工具。這是由于 rustdoc 的工作方式:鏈接庫進(jìn)行測試,但如果是二進(jìn)制的,就沒有什么可鏈接的。
當(dāng)測試你的代碼時,這還有幾個有用的注釋,可以幫助 rustdoc 做正確的事情:
/// ```ignore
/// fn foo() {
/// ```
ignore 指令告訴 Rust 忽略代碼。這是最通用的。相反,如果它不是代碼,可以考慮使用 text 注釋,或者使用 #s 得到一個工作示例,這個工作示例只顯示你關(guān)心的部分。
/// ```should_panic
/// assert!(false);
/// ```
should_panic 告訴 rustdoc 應(yīng)該正確地編譯代碼,但測試實例實際上并沒有通過。
/// ```no_run
/// loop {
/// println!("Hello, world");
/// }
/// ```
no_run 屬性將編譯代碼,但不運行它。這是很重要的例子,例如 “Here's how to start up a network service,” 這表示您想要確保編譯,但可能以無限循環(huán)的模式運行!
Rust有另外一種文檔評論 //!
。這個評論不記錄下一個項目,而是記錄封閉的項目。換句話說:
mod foo {
//! This is documentation for the `foo` module.
//!
//! # Examples
// ...
}
//!
最常見的用途:用于模塊文檔。如果你在 foo.rs 之內(nèi)有一個模塊,你經(jīng)會經(jīng)常打開它的代碼,并且看到這個:
//! A module for using `foo`s.
//!
//! The `foo` module contains a lot of useful functionality blah blah blah
查看 RFC 505,得到完整規(guī)范的風(fēng)格和格式文檔。
所有這一切行為都工作于 non-Rust 源文件。因為評論都寫在 Markdown 中,他們經(jīng)常都是 .md 文件。
當(dāng)你在 Markdown 文件中編寫文檔時,不需要使用評論給文檔加前綴。例如:
/// # Examples
///
/// ```
/// use std::rc::Rc;
///
/// let five = Rc::new(5);
/// ```
只需要
# Examples
```
use std::rc::Rc;
let five = Rc::new(5);
```
這些代碼存在于 Markdown 文件中。不過有一個問題:Markdown 文件需要一個這樣的標(biāo)題:
% The title
This is the example documentation.
注意:加 % 的行需要是文件的第一行。
在更深的層面,文檔注釋是文檔屬性的裝飾:
/// this
#[doc="this"]
如下是相同的:
//! this
#![doc="http:/// this"]
你不會經(jīng)??吹竭@個屬性用于編寫文檔,但當(dāng)改變一些選項,或者當(dāng)編寫一個宏是,它可能是有用的。
rustdoc 將顯示一個文檔為了在兩個位置得到一個公共再出口:
extern crate foo;
pub use foo::bar;
因為 bar 且在文檔內(nèi)部因為 crate foo 而創(chuàng)建文檔。它將在兩個地方使用相同的文檔。
這種行為可以被 no_inline 禁止:
extern crate foo;
#[doc(no_inline)]
pub use foo::bar;
你可以通過 #!(doc) 的屬性版本控制 rustdoc 產(chǎn)生 HTML 的若干部分:
#![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/")];
這是一個不同選項的集合,包括標(biāo)志,標(biāo)識,和一個根 URL。
rustdoc 還包含一些其他命令行選項,為了進(jìn)一步用戶化:
<head>…</head>
的結(jié)尾部分,包括文件的內(nèi)容。<body>
之后,呈現(xiàn)的內(nèi)容之前(包括搜索欄),包括文件的內(nèi)容。Markdown 中放置的文檔注釋,沒有被處理成最終的網(wǎng)頁。小心文字的 HTML:
/// <script>alert(document.cookie)</script>
更多建議: