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