文檔

2018-08-12 22:03 更新

文檔

文檔是軟件項目的重要組成部分,而且它在 Rust 中是第一位的。讓我們看看怎么使用 Rust 給出的工具來記錄你的項目。

關(guān)于 rustdoc

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 使用后處理例子的完整算法:

  • 任何主要 #!(foo) 屬性是完好的 crate 屬性。
  • 一些常見的 allow 屬性被插入,包括 unused_variables ,unused_assignments,unused_mut,unused_attributes,dead_code。小例子經(jīng)常會引發(fā)這些麻煩。
  • 如果樣例不包含 extern crate,那么 extern crate 就會被插入。
  • 最后,如果樣例不包含 fn main,文本的其余部分會包裝在fn main() { your_code }。

然而,有時這還是不夠的。例如,所有這些代碼示例中,我們用 /// 來標(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

文檔注釋風(fēng)格

查看 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;

控制 HTML

你可以通過 #!(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)一步用戶化:

  • html-in-header FILE:在 <head>…</head> 的結(jié)尾部分,包括文件的內(nèi)容。
  • html-before-content FILE:在 <body> 之后,呈現(xiàn)的內(nèi)容之前(包括搜索欄),包括文件的內(nèi)容。
  • html-after-content FILE:在所有呈現(xiàn)內(nèi)容之后,包括文件的內(nèi)容。

安全事項

Markdown 中放置的文檔注釋,沒有被處理成最終的網(wǎng)頁。小心文字的 HTML:

/// <script>alert(document.cookie)</script>
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號