2020年5月18日

rust14 進一步了解 Cargo 及 Crates.io

cargo 除了 build, run, test 以外,還有更多功能

  • 以 release profile 自訂 build process
  • 發布 library 到 crates.io
  • 以 workspace 組織大型專案
  • 由 crates.io 安裝 binaries
  • 擴充 cargo 的自訂指令

以 release profile 自訂 build process

rust 的 release profile 是 pre-defined 可自訂的,帶有不同的 options,每一個 profile 都是獨立的

cargo 有兩個主要的 profile:執行 cargo build 使用 dev profile,執行 cargo build --release 使用 release profile。

當專案的 Cargo.toml 中沒有任何 [profile.*] 時,cargo 會使用預設 profiles 設定。

opt-level 控制 Rust 會對代碼進行何種程度的優化,數值是 0~3,可在 Cargo.toml 增加這幾行設定,修改 opt-level

Cargo.toml

[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

發布 library 到 crates.io

crates.io 用來分發 library 的 source code。

rust, cargo 有提供讓別人更容易找到與使用我們發布的 library 的功能

提供有用的註解

// 是 rust 的註解

/// 是 documentation comments,可產生 html 文件,主要是用來撰寫如何 "使用" 這個 crate 的說明。支援 Mardown 格式的註解

一開始是函數的說明,然後是如何使用該函數的範例

src/lib.rs

/// 將給定的數字加一
///
/// # Examples
///
/// ```
/// let five = 5;
///
/// assert_eq!(6, my_crate::add_one(5));
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

cargo doc 會產生文件

cargo doc --open 會產生文件的 html 並打開 browser

常用的 documentation comments

  • Examples

    範例

  • Panics

    該函數可能會產生 panic!

  • Errors

    如果函數回傳 Result,這裡說明可能會出現的錯誤及錯誤訊息

  • Safety

    如果函數使用 unsafe ,希望呼叫函數者,支援確保 unsafe 區塊正常工作的不變條件 invariants

Documentation Comments as Tests

Examples 區塊說明如何使用library,而 cargo test 也會像測試一樣,執行該區塊的範例程式碼。如果程式被改變了,那麼測試也會發生問題。這可確保程式跟文件是同步的。

   Doc-tests my_crate

running 1 test
test src/lib.rs - add_one (line 5) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Commenting Contained Items

//! 這是另一種文件註解,這是包含 items 的註解,通常用在 crate 根文件,也就是 src/lib.rs,或模塊的根文件為 crate 或模塊整體提供文件。

ex: 希望增加包含 add_one 函數的 my_crate crate 文件,可在 src/lib.rs 增加 //! 開頭的註解

//! # My Crate
//!
//! `my_crate` 是一個使得特定計算更方便的
//! 工具集合

/// 將給定的數字加一。
// --snip--

使用 pub use 匯出 public api

chap 7 介紹了使用 mod 將程式整合到 module 中,及如何使用 pub 將 item 變成公有的,及如何使用 use 將 item 引入作用域。

但開發的文件結構,可能會是多層級,不方便使用者查詢,也會討厭使用 use my_crate::some_module::another_module::UsefulType; 而不是 use my_crate::UsefulType; 來使用類別。

公有 API 結構,是發布 crate 要考慮的事情

即使原始檔的結構複雜,可用 pub use 重新匯出不同的 public api 結構

ex: 有一個 art library,裡面有兩個 enum: PrimaryColor, SecondaryColor 的模塊 kinds,及一個包含函數 mix 的模塊 utils

src/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
    }
}

main.rs

use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}

使用者要搞清楚 PrimaryColor 在 kinds 模塊,而 mix 在 utils 模塊

可以採用實例中的 art crate 並增加 pub use 語句來重導出項到頂層結構

lib.rs

//! # Art
//!
//! 一個描述美術信息的庫。

pub use kinds::PrimaryColor;
pub use kinds::SecondaryColor;
pub use utils::mix;

pub mod kinds {
    // --snip--
}

pub mod utils {
    // --snip--
}

可改用 pub use 的結構

use art::PrimaryColor;
use art::mix;

fn main() {
    // --snip--
}

建立 Crates.io 帳號

發布 crate 前,要先在 crates.io 註冊並獲取一個 API token

然後用 cargo login 登入

cargo login abcdefghijklmnopqrstuvwxyz012345

這個命令會通知 Cargo,你的 API token 並將其儲存在本地的 ~/.cargo/credentials 文件中。注意這個 token 是一個 秘密secret)且不應該與其他人共享。

發布 crate 前

在 crate 的 Cargo.toml 的 [package] 部分增加這個 crate 的 metadata

crate 需要一個唯一的名稱,另外要填寫關於該 crate 用途的描述和用戶可能在何種條款下使用該 crate 的 license,可使用 Linux 基金會的 Software Package Data Exchange SPDX

Cargo.toml

[package]
name = "guessing_game"
license = "MIT"

如果要用自訂的 license,可放到一個文件,並改用 license-file 指定該文件名稱

很多 Rust 社區成員選擇與 Rust 自身相同的 license,這是一個雙許可的 MIT OR Apache-2.0

[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]

發布到 Crates.io

因發布是 permanent 永久的,無法覆蓋舊版,也無法刪除。

cargo publish 用這個指令發布

新版就根據 語義化版本規則 來根據修改的類型決定下一個版本號,修改 version 的值,再 cargo publish 即可

使用 cargo yank 從 Crates.io 撤銷版本

雖不能刪除舊版 crate,但可阻止其他專案加入 dependencies 中

撤銷版本可阻止新專案依賴此版本的函式庫,但現存的依賴還是可以繼續使用。撤銷帶有 Cargo.lock 專案的依賴,不會發生問題,但新產生的 Cargo.lock 將不能使用已經被撤銷的版本。

cargo yank --vers 1.0.1 可撤銷版本 1.0.1

cargo yank --vers 1.0.1 --undo 允許再次依賴該版本

Cargo 的 workspace

如果 crate 持續擴大,可拆分成多個 library crate,cargo 提供 workspace 功能,可管理多個協同開發的 package

建立 workspace

workspace 是一系列共享同樣的 Cargo.lock 與輸出目錄的 package。

現在建立一個 workspace,有一個 binary project 及兩個 libraries,一個提供 addone,一個提供 addtwo function。

mkdir add
cd add

在 add 目錄產生 Cargo.toml 檔案。內容為

[workspace]

members = [
    "adder",
]

產生 adder binary crate

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

現在可以用 cargo build 建構專案,目錄及檔案如下

├── Cargo.lock
├── Cargo.toml
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

在 workspace 產生第二個 crate

修改 Cargo.toml

[workspace]

members = [
    "adder",
    "add-one",
]

產生 add-one library

$ cargo new add-one --lib
     Created library `add-one` package

現在的目錄及檔案為

├── Cargo.lock
├── Cargo.toml
├── add-one
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

add-one/src/lib.rs 增加一個 add_one 函數:

pub fn add_one(x: i32) -> i32 {
    x + 1
}

adder 依賴 crate add-one。首先需要在 adder/Cargo.toml 文件中增加 add-one 作為路徑依賴。工作空間中的 crate 不必相互依賴,所以仍需表明工作空間中 crate 的依賴關係。

adder/Cargo.toml

[dependencies]

add-one = { path = "../add-one" }

在 adder 使用 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));
}

build 專案

$ cargo build
   Compiling add-one v0.1.0 (/Users/charley/project/add/add-one)
   Compiling adder v0.1.0 (/Users/charley/project/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.78s

為了在頂層 add 目錄執行二進制 crate,需要通過 -p 參數和包名稱來運行 cargo run 指定工作空間中我們希望使用的包

$ cargo run -p adder
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
     Running `target/debug/adder`
Hello, world! 10 plus one is 11!

在 workspace 依賴外部 crate

注意:工作空間只在根目錄有一個 Cargo.lock,而不是在每一個 crate 目錄都有 Cargo.lock

這確保了所有的 crate 都使用完全相同版本的依賴。如果在 Cargo.tomladd-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.3.14"

現在就可以在 add-one/src/lib.rs 中增加 use rand; 了,接著在 add 目錄運行 cargo build 構建整個工作空間就會引入並編譯 rand crate

$ cargo build
    Updating crates.io index
  Downloaded libc v0.2.60
   Compiling libc v0.2.60
   Compiling rand v0.4.6
   Compiling rand v0.3.23
   Compiling add-one v0.1.0 (/Users/charley/project/add/add-one)
   Compiling adder v0.1.0 (/Users/charley/project/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 56.98s

頂層的 Cargo.lock 包含了 add-onerand 依賴的訊息。但即使 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));
    }
}
$ cargo test
   Compiling add-one v0.1.0 (/Users/charley/project/add/add-one)
   Compiling adder v0.1.0 (/Users/charley/project/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 1.30s
     Running target/debug/deps/add_one-cbf892206c67cf71

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/adder-0108381d26b7c5be

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests add-one

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

也可指定測試的 crate

$ cargo test -p add-one

由 crates.io 安裝 binaries

cargo install 用來本地安裝及使用 binary crate

binary crate 是在 crate 有 src/main.rs 或其他可執行程式,不同於本身不能執行的 library。通常 crate 的 README 有該 crate 是 library 或 binary 的資訊。

所有來自 cargo install 的 binary crate 都安裝到 rust 安裝根目錄的 bin folder 中。如果用 rustup.rs 安裝的 rust,且沒有自訂設定,目錄會是 $HOME/.cargo/bin

將該目錄新增到 $PATH 就可以執行程式

ex: ripgrep 用於搜索文件的 grep 的 Rust 實現

$ cargo install ripgrep
Updating registry `https://github.com/rust-lang/crates.io-index`
 Downloading ripgrep v0.3.2
 --snip--
   Compiling ripgrep v0.3.2
    Finished release [optimized + debuginfo] target(s) in 97.91 secs
  Installing ~/.cargo/bin/rg

擴充 cargo 的自訂指令

如果 $PATH 中有類似 cargo-something 的 binary file,就可以用 cargo something 來執行。

cargo --list 會列出所有自訂指令

References

The Rust Programming Language

中文版

中文版 2

沒有留言:

張貼留言