本章會討論 Cargo 其他一些更為高級的功能,我們將展示如何:
- 使用發布配置來自定義構建
- 將庫發布到?crates.io
- 使用工作空間來組織更大的項目
- 從?crates.io?安裝二進制文件
- 使用自定義的命令來擴展 Cargo
Cargo 的功能不止本章所介紹的,關于其全部功能的詳盡解釋,請查看?文檔
14.1?采用發布配置自定義構建
在 Rust 中?發布配置(release profiles)是預定義的、可定制的帶有不同選項的配置,他們允許程序員更靈活地控制代碼編譯的多種選項。每一個配置都彼此相互獨立。
Cargo 有兩個主要的配置:運行?cargo build
?時采用的?dev
?配置和運行?cargo build --release
?的?release
?配置。dev
?配置被定義為開發時的好的默認配置,release
?配置則有著良好的發布構建的默認配置。
這些配置名稱可能很眼熟,因為它們出現在構建的輸出中:
$ cargo buildFinished dev [unoptimized + debuginfo] target(s) in 0.0 secs
$ cargo build --releaseFinished release [optimized] target(s) in 0.0 secs
構建輸出中的?dev
?和?release
?表明編譯器在使用不同的配置。
當項目的?Cargo.toml?文件中沒有任何?[profile.*]
?部分的時候,Cargo 會對每一個配置都采用默認設置。通過增加任何希望定制的配置對應的?[profile.*]
?部分,我們可以選擇覆蓋任意默認設置的子集。例如,如下是?dev
?和?release
?配置的?opt-level
?設置的默認值:
文件名: Cargo.toml
[profile.dev]
opt-level = 0[profile.release]
opt-level = 3
opt-level
?設置控制 Rust 會對代碼進行何種程度的優化。這個配置的值從 0 到 3。越高的優化級別需要更多的時間編譯,所以如果你在進行開發并經常編譯,可能會希望在犧牲一些代碼性能的情況下編譯得快一些。這就是為什么?dev
?的?opt-level
?默認為?0
。當你準備發布時,花費更多時間在編譯上則更好。只需要在發布模式編譯一次,而編譯出來的程序則會運行很多次,所以發布模式用更長的編譯時間換取運行更快的代碼。這正是為什么?release
?配置的?opt-level
?默認為?3
。
對于每個配置的設置和其默認值的完整列表,請查看?Cargo 的文檔。
14.2?將crate 發布到Crates.io
我們曾經在項目中使用?crates.io?上的包作為依賴,不過你也可以通過發布自己的包來向它人分享代碼。crates.io?用來分發包的源代碼,所以它主要托管開源代碼。
Rust 和 Cargo 有一些幫助它人更方便找到和使用你發布的包的功能。我們將介紹一些這樣的功能,接著講到如何發布一個包。
編寫有用的文檔注釋
準確的包文檔有助于其他用戶理解如何以及何時使用他們,所以花一些時間編寫文檔是值得的。第三章中我們討論了如何使用兩斜杠?//
?注釋 Rust 代碼。Rust 也有特定的用于文檔的注釋類型,通常被稱為?文檔注釋(documentation comments),他們會生成 HTML 文檔。這些 HTML 展示公有 API 文檔注釋的內容,他們意在讓對庫感興趣的程序員理解如何?使用?這個 crate,而不是它是如何被?實現?的。
文檔注釋使用三斜杠?///
?而不是兩斜桿以支持 Markdown 注解來格式化文本。文檔注釋就位于需要文檔的項的之前
/// 將給定的數字加一
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {x + 1
}
這里,我們提供了一個?add_one
?函數工作的描述,接著開始了一個標題為?Examples
?的部分,和展示如何使用?add_one
?函數的代碼。可以運行?cargo doc
?來生成這個文檔注釋的 HTML 文檔。這個命令運行由 Rust 分發的工具?rustdoc
?并將生成的 HTML 文檔放入?target/doc?目錄。
?為了方便起見,運行?cargo doc --open
?會構建當前 crate 文檔(同時還有所有 crate 依賴的文檔)的 HTML 并在瀏覽器中打開。導航到?add_one
?函數將會發現文檔注釋的文本是如何渲染的,
輸入命令后,瀏覽器自動打開。
常用(文檔注釋)部分
其他一些 crate 作者經常在文檔注釋中使用的部分有:
- Panics:這個函數可能會?
panic!
?的場景。并不希望程序崩潰的函數調用者應該確保他們不會在這些情況下調用此函數。 - Errors:如果這個函數返回?
Result
,此部分描述可能會出現何種錯誤以及什么情況會造成這些錯誤,這有助于調用者編寫代碼來采用不同的方式處理不同的錯誤。 - Safety:如果這個函數使用?
unsafe
?代碼(這會在第十九章討論),這一部分應該會涉及到期望函數調用者支持的確保?unsafe
?塊中代碼正常工作的不變條件(invariants)。
文檔注釋作為測試
在文檔注釋中增加示例代碼塊是一個清楚的表明如何使用庫的方法,這么做還有一個額外的好處:cargo test
?也會像測試那樣運行文檔中的示例代碼!沒有什么比有例子的文檔更好的了!也沒有什么比不能正常工作的例子更糟的了,因為代碼在編寫文檔時已經改變。
注釋包含項的結構
還有另一種風格的文檔注釋,//!
,這為包含注釋的項,而不是注釋之后的項增加文檔。這通常用于 crate 根文件(通常是?src/lib.rs)或模塊的根文件為 crate 或模塊整體提供文檔。
作為一個例子,如果我們希望增加描述包含?add_one
?函數的?my_crate
?crate 目的的文檔,可以在?src/lib.rs?開頭增加以?//!
?開頭的注釋
//! # My Crate
//!
//! `my_crate` 是一個使得特定計算更方便的
//! 工具集合/// 將給定的數字加一。
// --snip--
注意?//!
?的最后一行之后沒有任何代碼。因為他們以?//!
?開頭而不是?///
,這是屬于包含此注釋的項而不是注釋之后項的文檔。在這個情況中,包含這個注釋的項是?src/lib.rs?文件,也就是 crate 根文件。這些注釋描述了整個 crate。
使用pub use 導出合適的公有API
公有 API 的結構是你發布 crate 時主要需要考慮的。crate 用戶沒有你那么熟悉其結構,并且如果模塊層級過大他們可能會難以找到所需的部分。
好消息是,即使文件結構對于用戶來說?不是?很方便,你也無需重新安排內部組織:你可以選擇使用?pub use
?重導出(re-export)項來使公有結構不同于私有結構。重導出獲取位于一個位置的公有項并將其公開到另一個位置,好像它就定義在這個新位置一樣。
例如,假設我們創建了一個描述美術信息的庫?art
。這個庫中包含了一個有兩個枚舉?PrimaryColor
?和?SecondaryColor
?的模塊?kinds
,以及一個包含函數?mix
?的模塊?utils(lib.rs)
//! # Art
//!
//! 一個描述美術信息的庫。pub mod kinds {/// 采用 RGB 色彩模式的主要顏色。pub enum PrimaryColor {Red,Yellow,Blue,}/// 采用 RGB 色彩模式的次要顏色。pub enum SecondaryColor {Orange,Green,Purple,}
}pub mod utils {use crate::kinds::*;/// 等量的混合兩個主要顏色/// 來創建一個次要顏色。pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {// --snip--SecondaryColor::Orange}
}
fn main() {}
cargo doc
?所生成的 crate 文檔
?注意?PrimaryColor
?和?SecondaryColor
?類型、以及?mix
?函數都沒有在首頁中列出。我們必須點擊?kinds
?或?utils
?才能看到他們。
另一個依賴這個庫的 crate 需要?use
?語句來導入?art
?中的項,這包含指定其當前定義的模塊結構。示例展示了一個使用?art
?crate 中?PrimaryColor
?和?mix
?項的 crate 的例子:(main.rs)
use art::kinds::PrimaryColor;
use art::utils::mix;fn main() {let red = PrimaryColor::Red;let yellow = PrimaryColor::Yellow;mix(red, yellow);
}
示例中使用?art
?crate 代碼的作者不得不搞清楚?PrimaryColor
?位于?kinds
?模塊而?mix
?位于?utils
?模塊。art
?crate 的模塊結構相比使用它的開發者來說對編寫它的開發者更有意義。其內部的?kinds
?模塊和?utils
?模塊的組織結構并沒有對嘗試理解如何使用它的人提供任何有價值的信息。art
?crate 的模塊結構因不得不搞清楚所需的內容在何處和必須在?use
?語句中指定模塊名稱而顯得混亂和不便。
為了從公有 API 中去掉 crate 的內部組織,我們可以增加?pub use
?語句來重導出項到頂層結構(lib.rs)
//! # Art
//!
//! 一個描述美術信息的庫。pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;pub mod kinds {// --snip--
}pub mod utils {// --snip--
}
創建Crates.io賬號
在你可以發布任何 crate 之前,需要在?crates.io?上注冊賬號并獲取一個 API token。為此,訪問位于?crates.io?的首頁并使用 GitHub 賬號登陸。(目前 GitHub 賬號是必須的,不過將來該網站可能會支持其他創建賬號的方法)一旦登陸之后,查看位于?https://crates.io/me/?的賬戶設置頁面并獲取 API token。
發布新crate 之前
發布到Crates.io
使用cargo yank 從 Crates.io 撤回版本
14.3?Cargo工作空間
隨著項目開發的深入,庫 crate 持續增大,而你希望將其進一步拆分成多個庫 crate。對于這種情況,Cargo 提供了一個叫?工作空間(workspaces)的功能,它可以幫助我們管理多個相關的協同開發的包。
創建工作空間
工作空間?是一系列共享同樣的?Cargo.lock?和輸出目錄的包。讓我們使用工作空間創建一個項目 —— 這里采用常見的代碼以便可以關注工作空間的結構。有多種組織工作空間的方式;我們將展示一個常用方法。我們的工作空間有一個二進制項目和兩個庫。二進制項目會提供主要功能,并會依賴另兩個庫。一個庫會提供?add_one
?方法而第二個會提供?add_two
?方法。這三個 crate 將會是相同工作空間的一部分。讓我們以新建工作空間目錄開始:
$ mkdir add
$ cd add
接著在 add* 目錄中,創建?Cargo.toml?文件。這個?Cargo.toml?文件配置了整個工作空間。它不會包含?[package]
?或其他我們在?Cargo.toml?中見過的元信息。相反,它以?[workspace]
?部分作為開始,并通過指定?adder?的路徑來為工作空間增加成員,如下會加入二進制 crate:
[workspace]members = ["adder",
]
接下來,在?add?目錄運行?cargo new
?新建?adder
?二進制 crate:
$ cargo new adderCreated binary (application) `adder` project
到此為止,可以運行?cargo build
?來構建工作空間。add?目錄中的文件應該看起來像這樣:
工作空間在頂級目錄有一個?target?目錄;adder
?并沒有自己的?target?目錄。即使進入?adder?目錄運行?cargo build
,構建結果也位于?add/target?而不是?add/adder/target。工作空間中的 crate 之間相互依賴。如果每個 crate 有其自己的?target?目錄,為了在自己的?target?目錄中生成構建結果,工作空間中的每一個 crate 都不得不相互重新編譯其他 crate。通過共享一個?target?目錄,工作空間可以避免其他 crate 多余的重復構建。
在工作空間中創建第二個crate
接下來,讓我們在工作空間中指定另一個成員 crate。這個 crate 位于?add-one?目錄中,所以修改頂級?Cargo.toml?為也包含?add-one?路徑:
[workspace]members = ["adder","add-one",
]
接著新生成一個叫做?add-one
?的庫:
$ cargo new add-one --libCreated library `add-one` project
現在?add?目錄應該有如下目錄和文件:
在?add-one/src/lib.rs?文件中,增加一個?add_one
?函數:
文件名: add-one/src/lib.rs
pub fn add_one(x: i32) -> i32 {x + 1
}
現在工作空間中有了一個庫 crate,讓?adder
?依賴庫 crate?add-one
。首先需要在?adder/Cargo.toml?文件中增加?add-one
?作為路徑依賴:
文件名: adder/Cargo.toml
[dependencies]add-one = { path = "../add-one" }
cargo并不假定工作空間中的Crates會相互依賴,所以需要明確表明工作空間中 crate 的依賴關系。
接下來,在?adder
?crate 中使用?add-one
?crate 的函數?add_one
。打開?adder/src/main.rs?在頂部增加一行?use
?將新?add-one
?庫 crate 引入作用域。接著修改?main
?函數來調用?add_one
?函數
文件名: adder/src/main.rs
use add_one;fn main() {let num = 10;println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
}
在?add?目錄中運行?cargo build
?來構建工作空間!
為了在頂層?add?目錄運行二進制 crate,需要通過?-p
?參數和包名稱來運行?cargo run
?指定工作空間中我們希望使用的包:
在工作空間中依賴外部crate
還需注意的是工作空間只在根目錄有一個?Cargo.lock,而不是在每一個 crate 目錄都有?Cargo.lock。這確保了所有的 crate 都使用完全相同版本的依賴。如果在?Cargo.toml?和?add-one/Cargo.toml?中都增加?rand
?crate,則 Cargo 會將其都解析為同一版本并記錄到唯一的?Cargo.lock?中。使得工作空間中的所有 crate 都使用相同的依賴意味著其中的 crate 都是相互兼容的。讓我們在?add-one/Cargo.toml?中的?[dependencies]
?部分增加?rand
?crate 以便能夠在?add-one
?crate 中使用?rand
?crate:
文件名: add-one/Cargo.toml
[dependencies]
rand = "0.5.5"
現在就可以在?add-one/src/lib.rs?中增加?use rand;
?了,接著在?add?目錄運行?cargo build
?構建整個工作空間就會引入并編譯?rand
?crate:
現在頂級的?Cargo.lock?包含了?add-one
?的?rand
?依賴的信息。然而,即使?rand
?被用于工作空間的某處,也不能在其他 crate 中使用它,除非也在他們的?Cargo.toml?中加入?rand
。
為工作空間增加測試
作為另一個提升,讓我們為?add_one
?crate 中的?add_one::add_one
?函數增加一個測試:
文件名: add-one/src/lib.rs
pub fn add_one(x: i32) -> i32 {x + 1
}#[cfg(test)]
mod tests {use super::*;#[test]fn it_works() {assert_eq!(3, add_one(2));}
}
在頂級?add?目錄運行?cargo test
:
?輸出的第一部分顯示?add-one
?crate 的?it_works
?測試通過了。下一個部分顯示?adder
?crate 中找到了 0 個測試,最后一部分顯示?add-one
?crate 中有 0 個文檔測試。在像這樣的工作空間結構中運行?cargo test
?會運行工作空間中所有 crate 的測試。
也可以選擇運行工作空間中特定 crate 的測試,通過在根目錄使用?-p
?參數并指定希望測試的 crate 名稱:
?輸出顯示了?cargo test
?只運行了?add-one
?crate 的測試而沒有運行?adder
?crate 的測試。
如果你選擇向?crates.io發布工作空間中的 crate,每一個工作空間中的 crate 需要單獨發布。cargo publish
?命令并沒有?--all
?或者?-p
?參數,所以必須進入每一個 crate 的目錄并運行?cargo publish
?來發布工作空間中的每一個 crate
14.4?使用cargo install 從Crates.io安裝二進制文件
cargo install
?命令用于在本地安裝和使用二進制 crate。它并不打算替換系統中的包;它意在作為一個方便 Rust 開發者們安裝其他人已經在?crates.io?上共享的工具的手段。只有擁有二進制目標文件的包能夠被安裝。二進制目標?文件是在 crate 有?src/main.rs?或者其他指定為二進制文件時所創建的可執行程序,這不同于自身不能執行但適合包含在其他程序中的庫目標文件。通常 crate 的?README?文件中有該 crate 是庫、二進制目標還是兩者都是的信息。
所有來自?cargo install
?的二進制文件都安裝到 Rust 安裝根目錄的?bin?文件夾中。如果你使用?rustup.rs?安裝的 Rust 且沒有自定義任何配置,這將是?$HOME/.cargo/bin
。確保將這個目錄添加到?$PATH
?環境變量中就能夠運行通過?cargo install
?安裝的程序了。
如果想要安裝?ripgrep
,可以運行如下:
$ cargo install ripgrep
Updating registry `https://github.com/rust-lang/crates.io-index`Downloading ripgrep v0.3.2--snip--Compiling ripgrep v0.3.2Finished release [optimized + debuginfo] target(s) in 97.91 secsInstalling ~/.cargo/bin/rg
最后一行輸出展示了安裝的二進制文件的位置和名稱,在這里?ripgrep
?被命名為?rg
。只要你像上面提到的那樣將安裝目錄加入?$PATH
,就可以運行?rg --help
?并開始使用一個更快更 Rust 的工具來搜索文件了!
14.5?Cargo自定義擴展命令
Cargo 的設計使得開發者可以通過新的子命令來對 Cargo 進行擴展,而無需修改 Cargo 本身。如果?$PATH
?中有類似?cargo-something
?的二進制文件,就可以通過?cargo something
?來像 Cargo 子命令一樣運行它。像這樣的自定義命令也可以運行?cargo --list
?來展示出來。能夠通過?cargo install
?向 Cargo 安裝擴展并可以如內建 Cargo 工具那樣運行他們是 Cargo 設計上的一個非常方便的優點!
參考:更多關于 Cargo 和 Crates.io 的內容 - Rust 程序設計語言 簡體中文版 (bootcss.com)