# 文檔
> [documentation.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/documentation.md)
commit 6ba952020fbc91bad64be1ea0650bfba52e6aab4
文檔是任何軟件項目中重要的一部分,并且它在Rust中是一級重要的。讓我們討論下Rust提供給我們編寫項目文檔的的工具。
### 關于`rustdoc`
Rust發行版中包含了一個工具,`rustdoc`,它可以生成文檔。`rustdoc`也可以在Cargo中通過`cargo doc`使用。
文檔可以使用兩種方法生成:從源代碼,或者從單獨的Markdown文件。
### 文檔化源代碼
文檔化Rust項目的主要方法是在源代碼中添加注釋。為了這個目標你可以這樣使用文檔注釋:
~~~
/// Constructs a new `Rc`.
///
/// # 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會記錄這些注釋,并在生成文檔時使用它們。這在文檔化像枚舉這樣的結構時很重要:
~~~
/// 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>`.
# fn foo() {}
~~~
文檔注釋的第一行應該是它功能的一個簡要總結。一句話。只包括基礎。高層次。
~~~
///
/// Other details about constructing `Rc<T>`s, maybe describing complicated
/// semantics, maybe additional options, all kinds of stuff.
///
# fn foo() {}
~~~
我們原始的例子只有一行總結,不過如果有更多東西要寫,我們在一個新的段落增加更多解釋。
### 特殊部分
下面,是特殊部分。它由一個標頭指示,`#`。有四種經常使用的標頭。它們不是特殊的語法,只是傳統,目前為止。
~~~
/// # Panics
# fn foo() {}
~~~
不可恢復的函數濫用(比如,程序錯誤)在Rust中通常用恐慌(panics)指示,它至少會殺死整個當前的線程。如果你的函數有被識別為或者強制為恐慌這樣的不平凡契約,記錄文檔是非常重要的。
~~~
/// # Failures
# fn foo() {}
~~~
如果你的函數或方法返回`Result<T, E>`,那么描述何種情況下它會返回`Err(E)`是件好事。這并不如`Panics`重要,因為失敗(failure)被編碼進了類型系統,不過仍舊是件好事。
~~~
/// # Safety
# fn foo() {}
~~~
如果你的函是`unsafe`的,你應該解釋調用者應該支持哪種不可變量。
~~~
/// # Examples
///
/// ```
/// use std::rc::Rc;
///
/// let five = Rc::new(5);
/// ```
# fn foo() {}
~~~
第四個,`Examples`。包含一個或多個使用你函數的例子,這樣你的用戶會為此感(ài)謝(shàng)你的。這些例子寫在代碼塊注釋中,我們稍后會討論到,并且可以有不止一個部分:
~~~
/// # 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"]);
/// ```
# fn foo() {}
~~~
讓我們聊聊這些代碼塊的細節。
### 代碼塊注釋
在注釋中編寫Rust代碼,使用三個重音號:
~~~
/// ```
/// println!("Hello, world");
/// ```
# fn foo() {}
~~~
如果你想要一些不是Rust的代碼,你可以加上一個注解:
~~~
/// ```c
/// printf("Hello, world\n");
/// ```
# fn foo() {}
~~~
這會根據你選擇的語言高亮代碼。如果你只是想展示普通文本,選擇`text`。
選擇正確的注釋是很重要的,因為`rustdoc`用一種有意思的方法使用它:它可以用來實際測試你的代碼,這樣你的注解就不會過時。如果你寫了些C代碼不過`rustdoc`會認為它是Rust代碼由于你忽略了注解,`rustdoc`會在你生成文檔時提示。
### 文檔作為測試
讓我們看看我的例子文檔的樣例:
~~~
/// ```
/// println!("Hello, world");
/// ```
# fn foo() {}
~~~
你會注意到你并不需要`fn main()`或者別的什么函數。`rustdoc`會自動一個`main()`包裝你的代碼,使用試探法試圖把它放到正確的位置。例如:
~~~
/// ```
/// use std::rc::Rc;
///
/// let five = Rc::new(5);
/// ```
# fn foo() {}
~~~
這會作為測試:
~~~
fn main() {
use std::rc::Rc;
let five = Rc::new(5);
}
~~~
這里是`rustdoc`用來后處理例子的完整的算法:
1. 任何`#![foo]`開頭的屬性會被完整的作為包裝箱屬性
1. 一些通用的`allow`屬性被插入,包括`unused_variables`、`unused_assignments`、`unused_mut`、`unused_attributes`和`dead_code`。小的例子經常觸發這些lint檢查
1. 如果例子并未包含`extern crate`,那么`extern crate <mycrate>;`被插入(注意缺失了`#[macro_use]`)
1. 最后,如果例子不包含`fn main`,剩下的文本將被包裝到`fn main() { your_code }`中
有時,這是不夠的。例如,我們已經考慮到了所有`///`開頭的代碼樣例了嗎?普通文本:
~~~
/// Some documentation.
# fn foo() {}
~~~
與它的輸出看起來有些不同:
~~~
/// Some documentation.
# fn foo() {}
~~~
是的,你猜對了:你寫的以`#`開頭的行會在輸出中被隱藏,不過會在編譯你的代碼時被使用。你可以利用這一點。在這個例子中,文檔注釋需要適用于一些函數,所以我只想向你展示文檔注釋,我需要在下面增加一些函數定義。同時,這只是用來滿足編譯器的,所以省略它會使得例子看起來更清楚。你可以使用這個技巧來詳細的解釋較長的例子,同時保留你文檔的可測試行。
例如,想象一下我們想要為如下代碼寫文檔:
~~~
let x = 5;
let y = 6;
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);
~~~
為了讓每個代碼塊可以執行,我們想要每個代碼塊都有整個程序,不過我們并不想讀者每回都看到所有的行。這是我們的源代碼:
~~~
首先,我們把`x`設置為`5`:
```text
let x = 5;
# let y = 6;
# println!("{}", x + y);
```
接著,我們把`y`設置為`6`:
```text
# let x = 5;
let y = 6;
# println!("{}", x + y);
```
最后,我們打印`x`和`y`的和:
```text
# 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),+); } });
}
# fn main() {}
~~~
你會注意到3個地方:我們需要添加我們自己的`extern crate`行,這樣我們可以添加`#[macro_use]`屬性。第二,我們也需要添加我們自己的`main()`(為了上面討論過的原因)。最后,用`#`機智的注釋掉這兩個代碼,這樣它們不會出現在輸出中。
另一個`#`好用的情況是當你想要忽略錯誤處理的時候。例如你想要如下情況。
~~~
/// use std::io;
/// let mut input = String::new();
/// try!(io::stdin().read_line(&mut input));
~~~
問題是`try!`返回一個`Result<T, E>`而測試函數并不返回任何值所以這會產生一個類型不匹配錯誤。
~~~
/// A doc test using try!
///
/// ```
/// use std::io;
/// # fn foo() -> io::Result<()> {
/// let mut input = String::new();
/// try!(io::stdin().read_line(&mut input));
/// # Ok(())
/// # }
/// ```
# fn foo() {}
~~~
你可以將代碼放進函數里來解決這個問題。在運行文檔測試時它捕獲并返回`Result<T, E>`。這種模式不時出現在標準庫中。
### 運行文檔測試
要運行測試,要么
~~~
$ rustdoc --test path/to/my/crate/root.rs
# or(或者)
$ cargo test
~~~
對了,`cargo test`也會測試內嵌的文檔。**然而,`cargo test`將不會測試二進制 crate,只測試庫 crate**。這是由于`rustdoc`的運行機制:它鏈接要測試的庫,不過對于一個二進制文件,木有什么好鏈接的。
這還有一些注釋有利于幫助`rustdoc`在測試你的代碼時正常工作:
~~~
/// ```ignore
/// fn foo() {
/// ```
# fn foo() {}
~~~
`ignore`指令告訴Rust忽略你的代碼。這幾乎不會是你想要的,因為這是最不受支持的。相反,如果不是代碼的話考慮注釋為`text`,或者使用`#`來形成一個可運行但只顯示你關心部分的例子。
~~~
/// ```should_panic
/// assert!(false);
/// ```
# fn foo() {}
~~~
`should_panic`告訴`rustdoc`這段代碼應該正確編譯,但是作為一個測試則不能通過。
~~~
/// ```no_run
/// loop {
/// println!("Hello, world");
/// }
/// ```
# fn foo() {}
~~~
`no_run`屬性會編譯你的代碼,但是不運行它。這對像如“如何開始一個網絡服務”這樣的例子很重要,你會希望確保它能夠編譯,不過它可能會無限循環的執行!
### 文檔化模塊
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);
/// ```
# fn foo() {}
~~~
在一個Markdown文件中,就是:
~~~
# Examples
```
use std::rc::Rc;
let five = Rc::new(5);
```
~~~
不過文檔寫在 Markdown 文件中要加一點:Markdown文件需要有一個像這樣的標題:
~~~
% The title
This is the example documentation.
~~~
`%`行需要放在文件的第一行。
### `doc`屬性
在更底層,文檔注釋是文檔屬性的語法糖:
~~~
/// this
# fn foo() {}
#[doc="this"]
# fn bar() {}
~~~
跟下面這個是相同的:
~~~
//! this
#![doc="this"]
~~~
寫文檔時你不會經常看見這些屬性,不過當你要改變一些選項,或者寫一個宏的時候比較有用。
### 重導出(Re-exports)
`rustdoc`對公有重導出部分會在兩個地方都顯式文檔:
~~~
extern crate foo;
pub use foo::bar;
~~~
這既會為`bar`在`foo`包裝箱中生成文檔,也會在你的包裝箱中生成文檔。它會在兩個地方使用相同的內容。
這種行文可以通過`no_inline`來阻止:
~~~
extern crate foo;
#[doc(no_inline)]
pub use foo::bar;
~~~
### 缺失文檔
有時你想要確保你項目中每一個公開的項都有文檔,特別是當你編寫一個庫的時候。Rust 允許你為此產生警告或錯誤,當一個項缺少文檔時。為了生成警告,你需要使用`warn`:
~~~
#![warn(missing_docs)]
~~~
而為了生成錯誤你需要使用`deny`:
~~~
#![deny(missing_docs)]
~~~
有時你想要顯式的禁用警告/錯誤來讓一些項沒有文檔。這可以使用`allow`來搞定:
~~~
#[allow(missing_docs)]
struct Undocumented;
~~~
你可能甚至希望在文檔中完全隱藏某些項:
~~~
#[doc(hidden)]
struct Hidden;
~~~
### 控制HTML
你可以通過`#![doc]`屬性控制`rustdoc`生成的THML文檔的一些方面:
~~~
#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
html_root_url = "https://doc.rust-lang.org/")]
~~~
這里設置了一些不同的選項,帶有一個logo,一個網站圖標,和一個根URL。
### 配置文檔測試
你也可以通過`#![doc(test(..))]`屬性來配置`rustdoc`測試你文檔示例的方式。
~~~
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
~~~
這允許示例中存在未使用的變量,但其他 lint 警告拋出仍會使測試失敗。
### 生成選項
`rustdoc`也提供了一些其他命令行選項,以便進一步定制:
- `--html-in-header FILE`:在`<head>...</head>`部分的末尾加上`FILE`內容
- `--html-before-content FILE`:在`<body>`之后,在渲染內容之前加上`FILE`內容
- `--html-after-content FILE`:在所有渲染內容之后加上`FILE`內容
### 注解安全
文檔注釋中的Markdown會被不加處理的放置于最終的網頁中。注意HTML文本(XSS?):
~~~
/// <script>alert(document.cookie)</script>
# fn foo() {}
~~~
- 前言
- 貢獻者
- 1.介紹
- 2.準備
- 3.學習 Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的 Rust
- 4.語法和語義
- 4.1.變量綁定
- 4.2.函數
- 4.3.原生類型
- 4.4.注釋
- 4.5.If語句
- 4.6.循環
- 4.7.所有權
- 4.8.引用和借用
- 4.9.生命周期
- 4.10.可變性
- 4.11.結構體
- 4.12.枚舉
- 4.13.匹配
- 4.14.模式
- 4.15.方法語法
- 4.16.Vectors
- 4.17.字符串
- 4.18.泛型
- 4.19.Traits
- 4.20.Drop
- 4.21.if let
- 4.22.trait 對象
- 4.23.閉包
- 4.24.通用函數調用語法
- 4.25.crate 和模塊
- 4.26.const和static
- 4.27.屬性
- 4.28.type別名
- 4.29.類型轉換
- 4.30.關聯類型
- 4.31.不定長類型
- 4.32.運算符和重載
- 4.33.Deref強制多態
- 4.34.宏
- 4.35.裸指針
- 4.36.不安全代碼
- 5.高效 Rust
- 5.1.棧和堆
- 5.2.測試
- 5.3.條件編譯
- 5.4.文檔
- 5.5.迭代器
- 5.6.并發
- 5.7.錯誤處理
- 5.8.選擇你的保證
- 5.9.外部函數接口
- 5.10.Borrow 和 AsRef
- 5.11.發布途徑
- 5.12.不使用標準庫
- 6.Rust 開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接進階
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 6.11.自定義內存分配器
- 7.詞匯表
- 8.語法索引
- 9.參考文獻
- 附錄:名詞中英文對照