外部 crates

外部 crate 的意思是"別人的 crate"。

在本章節中,你 幾乎 需要去安裝 Rust,但我們仍然可以只使用 Playground。現在我們將要學習如何匯入別人所寫的 crate。這在 Rust 中很重要,原因有二:

  • 匯入其他的 crate 很容易,
  • Rust 標準函式庫也相當小。

這意味著為了很多基本功能引進外部 crate 在 Rust 中很普遍。想法是這樣,如果使用外部 crate 很容易,那你就可以選擇最好的那一個。也許某個人會為某個功能做出 crate,當然之後也會有別的人去做出更好的。

在本書中,我們只看最流行的 crate,也就是每個使用 Rust 的人都知道的那些。

要開始學習外部 Crate,我們將從最常見的開始:rand

rand

你有沒有注意到,我們還沒有使用過任何隨機數?那是因為隨機數並不在標準函式庫裡。但是有很多 crate "幾乎是函式標準庫",因為大家都在使用它們。在任何情況下,引進 crate 是非常容易的。如果你的電腦上有安裝 Rust,就會有個叫 Cargo.toml 的檔案,裡面有這些資訊。Cargo.toml 檔在你啟動時像這樣:

[package]
name = "rust_book"
version = "0.1.0"
authors = ["David MacLeod"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

現在,如果你想加上 rand crate 可以在 crates.io 上搜尋它,這是所有 crate 的去處。那會將你帶到 https://crates.io/crates/rand。當你點選那個,你可以看到畫面上寫著 Cargo.toml rand = "0.7.3"。你所要做的就是在 [dependencies] 下新增像這樣的內容:

[package]
name = "rust_book"
version = "0.1.0"
authors = ["David MacLeod"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.7.3"

然後 Cargo 會幫你完成剩下的工作。然後你就可以在 rand 的文件網站上開始編寫像本例程式碼這樣的程式碼。要想進入文件,你可以點選在 crates.io 的頁面 中的 docs 按鈕。

關於 Cargo 的介紹就到這裡了:我們現在使用的還只是 Playground。幸運的是,Playground 已經安裝了前 100 個 crate。所以你還不需要寫進 Cargo.toml。在 Playground 上,你可以想像,它有個像這樣的長長列表,有 100 個 crate:

[dependencies]
rand = "0.7.3"
some_other_crate = "0.1.0"
another_nice_crate = "1.7"

也就是說,如果要使用 rand,你可以直接這樣做:

use rand; // 這是表示整個 crate rand
          // 在你的電腦上你無法只寫這樣;
          // 你需要先寫在 Cargo.toml 檔案裡

fn main() {
    for _ in 0..5 {
        let random_u16 = rand::random::<u16>();
        print!("{} ", random_u16);
    }
}

每次都會列印不同的 u16 號碼,像是 42266 52873 56528 46927 6867

rand 中的主要功能是 randomthread_rng(rng 的意思是"隨機數產生器")。而實際上如果你看 random,它說:"這只是 thread_rng().gen() 的快捷方式"。所以其實是 thread_rng 基本做完了一切。

這裡是個簡單的範例,從 1 到 10 的數字。為了得到這些數字,我們在 1 到 11 之間使用 .gen_range()

use rand::{thread_rng, Rng}; // 或是只用 rand::*; 如果我們有些懶散

fn main() {
    let mut number_maker = thread_rng();
    for _ in 0..5 {
        print!("{} ", number_maker.gen_range(1, 11));
    }
}

會印出像 7 2 4 8 6 的東西。

我們可以用隨機數做一些有趣的事情,比如為遊戲做角色。我們將使用 rand 和其它一些我們知道的東西來做出它們。在這個遊戲中,我們的角色有六種狀態,用 d6 來表示他們。d6 是個立方體,當你投擲它時,它能給出 1、2、3、4、5 或 6。每個角色都會擲三次 d6,所以每個狀態都在 3 到 18 之間。

但是有時候如果你的角色狀態值有一些低,比如 3 或 4,那就不公平了。比如說你的力量是 3,你就不能背東西。所以還有一種方法是用 d6 四次。你擲四次,然後扔掉最低的數字。所以如果你擲出 3、3、1、6,那麼你保留 3、3、6 = 12。我們也會把這個方法做出來,所以遊戲的主人可以決定要不要用。

這是我們的簡單角色建立器。我們為狀態建立了 Character 結構體,甚至還實作 Display 來按照我們想要的方式印出。

use rand::{thread_rng, Rng}; // 或是只用 rand::*; 如果我們有些懶散
use std::fmt; // 要給我們的角色實作 Display


struct Character {
    strength: u8,
    dexterity: u8,    // 這表示 "身體反應速度"
    constitution: u8, // 這表示 "健康程度"
    intelligence: u8,
    wisdom: u8,
    charisma: u8, // 這表示 "受人歡迎的程度"
}

fn three_die_six() -> u8 { // "die" 是讓 你擲出去得到數字的東西
    let mut generator = thread_rng(); // 建立我們的隨機數產生器
    let mut stat = 0; // 這是總合
    for _ in 0..3 {
        stat += generator.gen_range(1..=6); // 加上每次結果
    }
    stat // 回傳總合
}

fn four_die_six() -> u8 {
    let mut generator = thread_rng();
    let mut results = vec![]; // 先把數字放在向量
    for _ in 0..4 {
        results.push(generator.gen_range(1..=6));
    }
    results.sort(); // 現在像是 [4, 3, 2, 6] 的結果會變成 [2, 3, 4, 6]
    results.remove(0); // 現在就會是 [3, 4, 6]
    results.iter().sum() // 回傳這個結果
}

enum Dice {
    Three,
    Four
}

impl Character {
    fn new(dice: Dice) -> Self { // true 是三個骰子, false 則是四個
        match dice {
            Dice::Three => Self {
                strength: three_die_six(),
                dexterity: three_die_six(),
                constitution: three_die_six(),
                intelligence: three_die_six(),
                wisdom: three_die_six(),
                charisma: three_die_six(),
            },
            Dice::Four => Self {
                strength: four_die_six(),
                dexterity: four_die_six(),
                constitution: four_die_six(),
                intelligence: four_die_six(),
                wisdom: four_die_six(),
                charisma: four_die_six(),
            },
        }
    }
    fn display(&self) { // 我們可以這樣做是因為我們在後面有實作 Display
        println!("{}", self);
        println!();
    }
}

impl fmt::Display for Character { // 只是沿用在 https://doc.rust-lang.org/std/fmt/trait.Display.html 的範例程式碼並稍作修改
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Your character has these stats:
strength: {}
dexterity: {}
constitution: {}
intelligence: {}
wisdom: {}
charisma: {}",
            self.strength,
            self.dexterity,
            self.constitution,
            self.intelligence,
            self.wisdom,
            self.charisma
        )
    }
}



fn main() {
    let weak_billy = Character::new(Dice::Three);
    let strong_billy = Character::new(Dice::Four);
    weak_billy.display();
    strong_billy.display();
}

會印出像這樣的東西:

#![allow(unused)]
fn main() {
Your character has these stats:
strength: 9
dexterity: 15
constitution: 15
intelligence: 8
wisdom: 11
charisma: 9

Your character has these stats:
strength: 9
dexterity: 13
constitution: 14
intelligence: 16
wisdom: 16
charisma: 10
}

有四個骰子的角色通常在大多數事情上都會好一點。

rayon

rayon 是個流行的 crate,能讓你為 Rust 程式碼加速。它受歡迎是因為它不需要像 thread::spawn 這樣的東西就能建立執行緒。換句話說,它受歡迎的原因是它既有效又容易編寫。比如說:

  • .iter().iter_mut()into_iter() 在 rayon 中寫起來像這樣:
  • .par_iter().par_iter_mut()par_into_iter()。所以你只需要加上 par_,你的程式碼就會變快很多。(par 表示"並行")

其他方法也一樣:.chars() 就是 .par_chars(),以此類推。

這裡舉例的是一段簡單的程式碼,卻能讓電腦做很多工作:

fn main() {
    let mut my_vec = vec![0; 200_000];
    my_vec.iter_mut().enumerate().for_each(|(index, number)| *number+=index+1);
    println!("{:?}", &my_vec[5000..5005]);
}

這建立有二十萬個元素的向量:每一個都是0,然後呼叫 .enumerate() 來取得每個數字的索引,並將 0 改為索引值。它列印時間太長,所以我們只印出第 5000 到 5004 個元素。這在 Rust 中還是非常快的,但如果你願意,你可以用 Rayon 讓它更快。但程式碼幾乎一樣:

use rayon::prelude::*; // 匯入 rayon

fn main() {
    let mut my_vec = vec![0; 200_000];
    my_vec.par_iter_mut().enumerate().for_each(|(index, number)| *number+=index+1); // 加上 par_ 在 iter_mut 前面
    println!("{:?}", &my_vec[5000..5005]);
}

就這樣。rayon 還有很多其他的方法來訂做你想要的事,但最簡單的就是"加上 _par 來讓你的程式更快"。

serde

serde 是相當流行的 crate,讓你可以在 JSON、YAML 等格式之間相互轉換。最常見的使用方式是透過建立具有兩個屬性在上面的 struct,。看起來像這樣

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}
}

SerializeDeserialize 特徵讓轉換變得容易。(這也是 serde 這個名字的由來)如果你的結構體上有它們,那麼你只需要呼叫一個方法就可以把它在 JSON 或任意格式間轉換。

regex

regex crate 讓你可以使用 正則表示式(Regular expression) 搜尋文字。有了它,你可以只透過一次搜尋便得到諸如 colour, color, colourscolors 的匹配資訊。正則表示式是一門全然不同也需要學習的語言,如果你想使用它們的話。

chrono

chrono 是為給那些需要更多時間功能的人準備的主要 crate。我們會看到標準函式庫現在有時間相關的功能,但是如果你需要更多的功能,那麼這個 crate 是個不錯的選擇。