外部 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
中的主要功能是 random
和 thread_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, } }
Serialize
和 Deserialize
特徵讓轉換變得容易。(這也是 serde
這個名字的由來)如果你的結構體上有它們,那麼你只需要呼叫一個方法就可以把它在 JSON 或任意格式間轉換。
regex
regex crate 讓你可以使用 正則表示式(Regular expression) 搜尋文字。有了它,你可以只透過一次搜尋便得到諸如 colour
, color
, colours
和 colors
的匹配資訊。正則表示式是一門全然不同也需要學習的語言,如果你想使用它們的話。
chrono
chrono 是為給那些需要更多時間功能的人準備的主要 crate。我們會看到標準函式庫現在有時間相關的功能,但是如果你需要更多的功能,那麼這個 crate 是個不錯的選擇。