Rust 包管理器 Cargo 入門

2020-03-09 10:49:00

了解 Rust 的軟體包管理器和構建工具。

Rust 是一種現代程式語言,可提供高效能、可靠性和生產力。幾年來,它一直被 StackOverflow 調查評為最受歡迎的語言

除了是一種出色的程式語言之外,Rust 還具有一個稱為 Cargo 的構建系統和軟體包管理器。Cargo 處理許多工,例如構建程式碼、下載庫或依賴項等等。這兩者綑綁在一起,因此在安裝 Rust 時會得到 Cargo。

安裝 Rust 和 Cargo

在開始之前,你需要安裝 Rust 和 Cargo。Rust 專案提供了一個可下載的指令碼來處理安裝。要獲取該指令碼,請開啟瀏覽器以存取 https://sh.rustup.rs 並儲存該檔案。閱讀該指令碼以確保你對它的具體行為有所了解,然後再執行它:

$ sh ./rustup.rs

你也可以參考這個安裝 Rust 的網頁以獲取更多資訊。

安裝 Rust 和 Cargo 之後,你必須獲取source env 檔案中的設定:

$ source $HOME/.cargo/env

更好的辦法是,將所需目錄新增到 PATH 環境變數中:

export PATH=$PATH:~/.cargo/bin

如果你更喜歡使用軟體包管理器(例如 Linux 上的 DNF 或 Apt),請在發行版本的儲存庫中查詢 Rust 和 Cargo 軟體包,並進行相應的安裝。 例如:

$ dnf install rust cargo

安裝並設定它們後,請驗證你擁有的 Rust 和 Cargo 版本:

$ rustc --versionrustc 1.41.0 (5e1a79984 2020-01-27)$ cargo --versioncargo 1.41.0 (626f0f40e 2019-12-03)

手動構建和執行 Rust

從在螢幕上列印“Hello, world!”的簡單程式開始。開啟你喜歡的文字編輯器,然後鍵入以下程式:

$ cat hello.rsfn main() {    println!("Hello, world!");}

以擴充套件名 .rs 儲存檔案,以將其標識為 Rust 原始碼檔案。

使用 Rust 編譯器 rustc 編譯程式:

$ rustc hello.rs

編譯後,你將擁有一個與源程式同名的二進位制檔案:

$ ls -ltotal 2592-rwxr-xr-x. 1 user group 2647944 Feb 13 14:14 hello-rw-r--r--. 1 user group      45 Feb 13 14:14 hello.rs$

執行程式以驗證其是否按預期執行:

$ ./helloHello, world!

這些步驟對於較小的程式或任何你想快速測試的東西就足夠了。但是,在進行涉及到多人的大型程式時,Cargo 是前進的最佳之路。

使用 Cargo 建立新包

Cargo 是 Rust 的構建系統和包管理器。它可以幫助開發人員下載和管理依賴項,並幫助建立 Rust 包。在 Rust 社群中,Rust 中的“包”通常被稱為“crate”(板條箱),但是在本文中,這兩個詞是可以互換的。請參閱 Rust 社群提供的 Cargo FAQ 來區分。

如果你需要有關 Cargo 命令列實用程式的任何幫助,請使用 --help-h 命令列引數:

$ cargo –help

要建立一個新的包,請使用關鍵字 new,跟上包名稱。在這個例子中,使用 hello_opensource 作為新的包名稱。執行該命令後,你將看到一條訊息,確認 Cargo 已建立具有給定名稱的二進位制包:

$ cargo new hello_opensource     Created binary (application) `hello_opensource` package

執行 tree 命令以檢視目錄結構,它會報告已建立了一些檔案和目錄。首先,它建立一個帶有包名稱的目錄,並且在該目錄內有一個存放你的原始碼檔案的 src 目錄:

$ tree ..└── hello_opensource    ├── Cargo.toml    └── src        └── main.rs2 directories, 2 files

Cargo 不僅可以建立包,它也建立了一個簡單的 “Hello, world” 程式。開啟 main.rs 檔案看看:

$ cat hello_opensource/src/main.rsfn main() {    println!("Hello, world!");}

下一個要處理的檔案是 Cargo.toml,這是你的包的組態檔。它包含有關包的資訊,例如其名稱、版本、作者資訊和 Rust 版本資訊。

程式通常依賴於外部庫或依賴項來執行,這使你可以編寫應用程式來執行不知道如何編碼或不想花時間編碼的任務。你所有的依賴項都將在此檔案中列出。此時,你的新程式還沒有任何依賴關係。開啟 Cargo.toml 檔案並檢視其內容:

$ cat hello_opensource/Cargo.toml[package]name = "hello_opensource"version = "0.1.0"authors = ["user <[email protected]>"]edition = "2018"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]

使用 Cargo 構建程式

到目前為止,一切都很順利。現在你已經有了一個包,可構建一個二進位制檔案(也稱為可執行檔案)。在此之前,進入包目錄:

$ cd hello_opensource/

你可以使用 Cargo 的 build 命令來構建包。注意訊息說它正在“編譯”你的程式:

$ cargo build   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished dev [unoptimized + debuginfo] target(s) in 0.38s

執行 build 命令後,檢查專案目錄發生了什麼:

$ tree ..├── Cargo.lock├── Cargo.toml├── src│   └── main.rs└── target    └── debug        ├── build        ├── deps        │   ├── hello_opensource-147b8a0f466515dd        │   └── hello_opensource-147b8a0f466515dd.d        ├── examples        ├── hello_opensource        ├── hello_opensource.d        └── incremental            └── hello_opensource-3pouh4i8ttpvz                ├── s-fkmhjmt8tj-x962ep-1hivstog8wvf                │   ├── 1r37g6m45p8rx66m.o                │   ├── 2469ykny0eqo592v.o                │   ├── 2g5i2x8ie8zed30i.o                │   ├── 2yrvd7azhgjog6zy.o                │   ├── 3g9rrdr4hyk76jtd.o                │   ├── dep-graph.bin                │   ├── query-cache.bin                │   ├── work-products.bin                │   └── wqif2s56aj0qtct.o                └── s-fkmhjmt8tj-x962ep.lock9 directories, 17 files

哇!編譯過程產生了許多中間檔案。另外,你的二進位制檔案將以與軟體包相同的名稱儲存在 ./target/debug 目錄中。

使用 Cargo 執行你的應用程式

現在你的二進位制檔案已經構建好了,使用 Cargo 的 run 命令執行它。如預期的那樣,它將在螢幕上列印 Hello, world!

$ cargo run    Finished dev [unoptimized + debuginfo] target(s) in 0.01s     Running `target/debug/hello_opensource`Hello, world!

或者,你可以直接執行二進位制檔案,該檔案位於:

$ ls -l ./target/debug/hello_opensource-rwxr-xr-x. 2 root root 2655552 Feb 13 14:19 ./target/debug/hello_opensource

如預期的那樣,它產生相同的結果:

$ ./target/debug/hello_opensourceHello, world!

假設你需要重建包,並丟棄早期編譯過程建立的所有二進位制檔案和中間檔案。Cargo 提供了一個方便的clean 選項來刪除所有中間檔案,但原始碼和其他必需檔案除外:

$ cargo clean$ tree ..├── Cargo.lock├── Cargo.toml└── src    └── main.rs1 directory, 3 files

對程式進行一些更改,然後再次執行以檢視其工作方式。例如,下面這個較小的更改將 Opensource 新增到 Hello, world! 字串中:

$ cat src/main.rsfn main() {    println!("Hello, Opensource world!");}

現在,構建該程式並再次執行它。這次,你會在螢幕上看到 Hello, Opensource world!

$ cargo build   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished dev [unoptimized + debuginfo] target(s) in 0.39s$ cargo run    Finished dev [unoptimized + debuginfo] target(s) in 0.01s     Running `target/debug/hello_opensource`Hello, Opensource world!

使用 Cargo 新增依賴項

Cargo 允許你新增程式需要執行的依賴項。使用 Cargo 新增依賴項非常容易。每個 Rust 包都包含一個 Cargo.toml 檔案,其中包含一個依賴關係列表(預設為空)。用你喜歡的文字編輯器開啟該檔案,找到 [dependencies] 部分,然後新增要包含在包中的庫。例如,將 rand 庫新增為依賴項:

$ cat Cargo.toml[package]name = "hello_opensource"version = "0.1.0"authors = ["test user <[email protected]>"]edition = "2018"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]rand = "0.3.14"

試試構建你的包,看看會發生什麼。

$ cargo build    Updating crates.io index   Compiling libc v0.2.66   Compiling rand v0.4.6   Compiling rand v0.3.23   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished dev [unoptimized + debuginfo] target(s) in 4.48s

現在,Cargo 會聯絡 Crates.io(這是 Rust 用於儲存 crate(或包)的中央倉庫),並下載和編譯 rand。但是,等等 —— libc 包是怎麼回事?你沒有要安裝 libc 啊。是的,rand 包依賴於 libc 包;因此,Cargo 也會下載並編譯 libc

庫的新版本會不斷湧現,而 Cargo 提供了一種使用 update 命令更新其所有依賴關係的簡便方法:

cargo update

你還可以選擇使用 -p 標誌跟上包名稱來更新特定的庫:

cargo update -p rand

使用單個命令進行編譯和執行

到目前為止,每當對程式進行更改時,都先使用了 build 之後是 run。有一個更簡單的方法:你可以直接使用 run 命令,該命令會在內部進行編譯並執行該程式。要檢視其工作原理,請首先清理你的軟體包目錄:

$ cargo clean$ tree ..├── Cargo.lock├── Cargo.toml└── src    └── main.rs1 directory, 3 files

現在執行 run。輸出資訊表明它已進行編譯,然後執行了該程式,這意味著你不需要每次都顯式地執行 build

$ cargo run   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished dev [unoptimized + debuginfo] target(s) in 0.41s     Running `target/debug/hello_opensource`Hello, world!

在開發過程中檢查程式碼

在開發程式時,你經常會經歷多次迭代。你需要確保你的程式沒有編碼錯誤並且可以正常編譯。你不需要負擔在每次編譯時生成二進位制檔案的開銷。Cargo 為你提供了一個 check 選項,該選項可以編譯程式碼,但跳過了生成可執行檔案的最後一步。首先在包目錄中執行 cargo clean

$ tree ..├── Cargo.lock├── Cargo.toml└── src    └── main.rs1 directory, 3 files

現在執行 check 命令,檢視對目錄進行了哪些更改:

$ cargo check    Checking hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished dev [unoptimized + debuginfo] target(s) in 0.18s

該輸出顯示,即使在編譯過程中建立了中間檔案,但沒有建立最終的二進位制檔案或可執行檔案。這樣可以節省一些時間,如果該包包含了數千行程式碼,這非常重要:

$ tree ..├── Cargo.lock├── Cargo.toml├── src│   └── main.rs└── target    └── debug        ├── build        ├── deps        │   ├── hello_opensource-842d9a06b2b6a19b.d        │   └── libhello_opensource-842d9a06b2b6a19b.rmeta        ├── examples        └── incremental            └── hello_opensource-1m3f8arxhgo1u                ├── s-fkmhw18fjk-542o8d-18nukzzq7hpxe                │   ├── dep-graph.bin                │   ├── query-cache.bin                │   └── work-products.bin                └── s-fkmhw18fjk-542o8d.lock9 directories, 9 files

要檢視你是否真的節省了時間,請對 buildcheck 命令進行計時並進行比較。首先,計時 build 命令:

$ time cargo build   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished dev [unoptimized + debuginfo] target(s) in 0.40sreal    0m0.416suser    0m0.251ssys     0m0.199s

在執行 check 命令之前清理目錄:

$ cargo clean

計時 check 命令:

$ time cargo check    Checking hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished dev [unoptimized + debuginfo] target(s) in 0.15sreal    0m0.166suser    0m0.086ssys     0m0.081s

顯然,check 命令要快得多。

建立外部 Rust 包

到目前為止,你所做的這些都可以應用於你從網際網路上獲得的任何 Rust crate。你只需要下載或克隆儲存庫,移至包資料夾,然後執行 build 命令,就可以了:

git clone <github-like-url>cd <package-folder>cargo build

使用 Cargo 構建優化的 Rust 程式

到目前為止,你已經多次執行 build,但是你注意到它的輸出了嗎?不用擔心,再次構建它並密切注意:

$ cargo build   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished dev [unoptimized + debuginfo] target(s) in 0.36s

看到了每次編譯後的 [unoptimized + debuginfo] 文字了嗎?這意味著 Cargo 生成的二進位制檔案包含大量偵錯資訊,並且未針對執行進行優化。開發人員經常經歷開發的多次迭代,並且需要此偵錯資訊進行分析。同樣,效能並不是開發軟體時的近期目標。因此,對於現在而言是沒問題的。

但是,一旦準備好發布軟體,就不再需要這些偵錯資訊。而是需要對其進行優化以獲得最佳效能。在開發的最後階段,可以將 --release 標誌與 build 一起使用。仔細看,編譯後,你應該會看到 [optimized] 文字:

$ cargo build --release   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)    Finished release [optimized] target(s) in 0.29s

如果願意,你可以通過這種練習來了解執行優化軟體與未優化軟體時節省的時間。

使用 Cargo 建立庫還是二進位制檔案

任何軟體程式都可以粗略地分類為獨立二進位制檔案或庫。一個獨立二進位制檔案也許即使是當做外部庫使用的,自身也是可以執行的。但是,作為一個庫,是可以被另一個獨立二進位制檔案所利用的。到目前為止,你在本教學中構建的所有程式都是獨立二進位制檔案,因為這是 Cargo 的預設設定。 要建立一個,請新增 --lib 選項:

$ cargo new --lib libhello     Created library `libhello` package

這次,Cargo 不會建立 main.rs 檔案,而是建立一個 lib.rs 檔案。 你的庫的程式碼應該是這樣的:

$ tree ..└── libhello    ├── Cargo.toml    └── src        └── lib.rs2 directories, 2 files

Cargo 就是這樣的,不要奇怪,它在你的新庫檔案中新增了一些程式碼。通過移至包目錄並檢視檔案來查詢新增的內容。預設情況下,Cargo 在庫檔案中放置一個測試函數。

使用 Cargo 執行測試

Rust 為單元測試和整合測試提供了一流的支援,而 Cargo 允許你執行以下任何測試:

$ cd libhello/$ cat src/lib.rs#[cfg(test)]mod tests {    #[test]    fn it_works() {        assert_eq!(2 + 2, 4);    }}

Cargo 有一個方便的 test 命令,可以執行程式碼中存在的任何測試。嘗試預設執行 Cargo 在庫程式碼中放入的測試:

$ cargo test   Compiling libhello v0.1.0 (/opensource/libhello)    Finished test [unoptimized + debuginfo] target(s) in 0.55s     Running target/debug/deps/libhello-d52e35bb47939653running 1 testtest tests::it_works ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out   Doc-tests libhellorunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

深入了解 Cargo 內部

你可能有興趣了解在執行一個 Cargo 命令時它底下發生了什麼。畢竟,在許多方面,Cargo 只是個封裝器。要了解它在做什麼,你可以將 -v 選項與任何 Cargo 命令一起使用,以將詳細資訊輸出到螢幕。

這是使用 -v 選項執行 buildclean 的幾個例子。

build 命令中,你可以看到這些給定的命令列選項觸發了底層的 rustc(Rust 編譯器):

$ cargo build -v   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)     Running `rustc --edition=2018 --crate-name hello_opensource src/main.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C debuginfo=2 -C metadata=147b8a0f466515dd -C extra-filename=-147b8a0f466515dd --out-dir /opensource/hello_opensource/target/debug/deps -C incremental=/opensource/hello_opensource/target/debug/incremental -L dependency=/opensource/hello_opensource/target/debug/deps`    Finished dev [unoptimized + debuginfo] target(s) in 0.36s

clean 命令表明它只是刪除了包含中間檔案和二進位制檔案的目錄:

$ cargo clean -v    Removing /opensource/hello_opensource/target

不要讓你的技能生鏽

要擴充套件你的技能,請嘗試使用 Rust 和 Cargo 編寫並執行一個稍微複雜的程式。很簡單就可以做到:例如,嘗試列出當前目錄中的所有檔案(可以用 9 行程式碼完成),或者嘗試自己回顯輸入。小型的實踐應用程式可幫助你熟悉語法以及編寫和測試程式碼的過程。

本文為剛起步的 Rust 程式設計師提供了大量資訊,以使他們可以開始入門 Cargo。但是,當你開始處理更大、更複雜的程式時,你需要對 Cargo 有更深入的了解。當你準備好迎接更多內容時,請下載並閱讀 Rust 團隊編寫的開源的《Cargo 手冊》,看看你可以創造什麼!