# Programming Rust, 2nd Edtion Start From: 2023-01-05 ## 1. Systems Programmers Can Have Nice Things ## 2. A Tour of Rust ## 3. Fundamental Types ## 4. Ownership and Moves ## 5. References ## 6. Expressions ## 7. Error Handling ## 8. Crates and Modules ## 9. Structs 三種不同的 `struct`: 1. Named-Field Structs 2. Tuple-Like Structs 3. Unit-Like Structs ### Named-Field Structs - 一般來說,Rust 中型別(包括 `struct`)的名稱,會用 `CamelCase`;而 fields 和 methods/functions,則是用 `snake_case`。 - 可以用 struct expression 來建立 struct 的數值: ```rust let image = GrayscaleMap { pixels: vec![0; width * height], size: (width, height) }; ``` - 如果 scope 中有和 fields 名稱相同的變數,可以利用 shorthand 的方式使用在 struct 數值的初始值中: ```rust fn new_map(size: (usize, usize)) -> GrayscaleMap { let pixels = vec![0; size.0 * size.1]; GrayscaleMap { pixels, size } }; ``` - 把 `struct` 宣告成 `pub`,並不代表 fields 也會變成 `pub`。如果要公開,就必須一個一個宣告。 - 如果想宣告兩個 values,其中大部份的 fields 都是相同的,只有少數不一樣的話,可以利用 `.. EXPR` 的語法節省打字精力。但要注意所有權的問題:沒有 `Copy` 的型別,就會被 move.... ```rust let b: Boom = Boom { name: String::from("MyName"), height: 100 }; // b.name 的所有權會被 boom1 搶走 let boom1 = Boom { height: b.height / 2, .. b}; // 為了不搶走 boom1.name,因此用 clone() 的。 let boom2 = Boom { name: boom1.name.clone(), .. boom1}; ``` ### Tuple-Like Structs ```rust= // 型別宣告 struct Bounds(usize, usize); fn main() { // 使用型別宣告數值 let image_bounds = Bounds(1024, 768); // 和 tuple 一樣,使用 .0, .1.... 去存取 fields let area = image_bounds.0 * image_bounds.1; } ``` #### 該如何選擇 Named-Field or Tuple-Like Structs? - 如果比較常會用到 `.` 去存取每一個欄位、而且每個欄位又有特別的意義,Named-Field 較好。 - 如果常會以整個 struct 的內容去做 pattern matching,Tuple-Like 較好。 另外,Tuple-Like struct 也可以用在給予單一欄位的型別新的型別意義,讓 compiler 做進一步的 type checking。這樣比單純加註解更好。 ### Unit-Like Structs 只有型別資訊,沒有占記憶體的 memory 空間 --> 給 compiler 的 type system 檢查用的。 ### Struct Layout - Rust 不保證 fields 在記憶體中的排列順序;但保證一定會讓 struct 本身和 fields 放在相同的記憶體區塊中。例如 struct 是放在 stack 中,那 fields 就一定在 stack 中,不會放在 heap。當然,若是 fields 數值需要在 heap 中配置記憶體,那還是會放在 heap。 - 若是希望 Rust 用 C/C++ 的方式配置 memory layout,可以加上 `#[repr(C)]` 屬性。 ### Defining Methods with `impl` - 要在 methods 中使用 stcut value 本身,必須在 methods 的第一個參數中宣告 `self`。這和 Python 的語法類似。 - `self` 參數有不同的宣告方式: 1. `&self`: 以 immutable ref 的方式借用。不會修改 fields 的內容。 2. `&mut self`: 以 mutable ref 的方式借用。可能會修改 fields 的內容。 3. `self`: 直接把 struct value 的所有權拿走。 4. Smart pointers: 例如 `self: Box<Self>`, `self: Rc<Self>`, `self: Arc<Self>`. - **Type-Associated Fuction**: 不接收 `self` 的 associated function。類似 C++ 中的 static member function。呼叫的時候要用 `TypeName::function_name()` 的型式呼叫。 - 為什麼 data fields 和 methods 的宣告要分開? - 容易閱讀(不用在一堆 symbols 中分辨 data or methods) - 不是只有 structs 才有 methods。enums or even primitives... 分開宣告語法可以統一。 - 方便實作 traits。 ### Associated Constants 定義在 `impl` block 中的常數。使用時也是要用 `TypeName::CONST_NAME` 的型式。 ## 10. Enums and Patterns ## 11. Traits and Generics ## 12. Operator Overloading ## 13. Utility Traits ## 14. Closures ## 15. Iterators - Iterators: 實作了 `Iterator` 的型別。 - Iterable: 實作了 `IntoIterator` 的型別。 ### Creating Iterators - 型別可能會提供 `iter()`, `iter_mut()` 或是相關的 iterator 產生 methods(如 `chars()`, `bytes()`)。 - 型別也可以實作 `IntoIterator` trait,就有 `into_iter()` 可用。 - `(&v).into_iter()`: 產生 item 的 ref。 - `(&mut v).into_iter()`: 產生 item 的 mutable ref。 - `v.into_iter()`: item 的所有權會轉移。 不一定三種都會被實作。也就是說,三種必須分開實作。(可以參考 `std::vec::Vec` 的文件。) ### Iterator Adapters - Lazy evalutaion - 在呼叫 consumer 之前,adapters 是不做事的。 - 從 closure 回傳值的角度來看 `map()`, `filter_map()` 和 `flat_map()`: 1. `map()`: 剛剛好回傳一個 item。 2. `filter_map()`: 回傳 1 個 or 0 個(所以是 `Option<U>`) 3. `flat_map()`: 回傳任意個(所以是 iterator) 傳入的都是一個值。 `flatten()` : 1. 把 iterator of iterators 變成 iterator。 2. 把 iterator of `Option<T>` 變成 iterator of T。(因為 `Option<T>` 實作了 `IntoIterator`) 3. 把 iterator of `Result<T, E>` 變成 iterator of T。 `take()` : 取前 `n` 個 item。 `take_while()` : 在 pred 為 false 前的都取。 `skip()` : 跳過 n 個 `skip_while()` : pred 為 true 的都跳過。 `peekable()` : 建立 peekable(可以偷看下一個 item 的)iterator。 Peekable iterator 會多一個叫 `peek()` 的 method。 `fuse()` : 建立一個保證在「`next()` 第一次回傳 `None` 之後會持續回傳 `None`」的 iterator。 `rev()` : 從後面開始反著 iterate。 但這是 `DoubleEndedIterator` trait 提供的方法。不是所有的 iterators 都支援。 `inspect()` : 主要是用來 debug 的。不會影響 item 的內容,但可以透過傳入的 closure 把值印出來看。 `chain()` : 將兩個 Item 一樣的 iterator 串起來。 如果兩個 iterator 都是 reversable 的話,那串起來的也是。 `enumerate()` : 加上 index。 ```rust= let items = ["Apple", "Banana", "Candy"]; let ol_result: Vec<_> = items .into_iter() .enumerate() .map(|(idx, item)| format!("{}. {}", idx + 1, item)) .collect(); println!("{:?}", ol_result); // ["1. Apple", "2. Banana", "3. Candy"] ``` `zip()` : 將兩個 iterator 組合起來,變成兩邊 items 的 tuples 的 iterator。 只要有一邊回傳 `None` 了,這個組合起來的 iterator 就會回傳 `None`。 `by_ref()` : 以 `&mut` 方式使用 iterator。 `cloned()`, `copy()` : 用於產生 ref item 的 iterator,每一次都 clone 一份 item。 `cycle()` : 無止盡地重覆元素。 ### Consuming Iterators `count()` : 計算個數。 `sum()` 和 `product()` : 總合和乘積。Item 必須為整數或浮點數,或是實作 `std::iter::Sum`/`std::iter::Product` 的型別。 `max()` 和 `min()` : 取最大/最小值。Item 必須實作 `std::cmp::Ord`。無法處理浮點數(因為浮點數只有 `PartialOrd`)。 `max_by()` 和 `min_by()` : 用指定的比較 closure(回傳 `std::cmp::Ordering`)取最大/最小值。 `max_by_key()` 和 `min_by_key()` : 用 closure 回傳的值(實作 `std::cmp::Ord`)比較大小後,取最大/最小值。 `lt()`, `eq()` 和 `gt()` : 回傳兩個 iterator 依序比較後的結果。 `any()` : 對所有的 items 執行 closure,其中一個的結果為 true 時就回傳 true。 `all()` : 對所有的 items 執行 closure,所有的結果為 true 時才回傳 true。 `position()` : 從頭開始,對 items 執行 closure,回傳「第一個結果為 true 的 item」的 index 值。 `rposition()` : 從尾巴開始,對 items 執行 closure,回傳「第一個結果為 true 的 item」的 index 值。 - 必須實作 `DoubleEndedIterator`,才能從尾巴開始取 item。 - 必須實作 `ExactSizeIterator`,才能算出結果的 index。(像是 `str::chars` 就無法得知詳細的 size,因為 UTF-8 的每個 `char` 長度不固定。 `fold()` 和 `rfold()` : 將 items 依續累積運算,最後算出一個結果。 即一般 functional programming 常見的 reduce 函數。 `try_fold()` 和 `try_rfold()` : - 和 `fold()`/`rfold()` 一樣,會累積運算。但依 closure 的回傳結果,可能會提前結束(不會走完所有的 items)。 - 回傳型別為 `Result<T,E>`:若回傳 `Err(e)`,則 `try_fold()` 直接回傳 `Err(e)`;若回傳 `Ok(a)`,則會把 `a` 當成 cloure 的 accu 繼續做下去,最後回傳 `Ok(v)`。 - 回傳型別為 `Option<T>`:若回傳 `None`,則 `try_fold()` 直接回傳 `None`;若回傳 `Some(a)`,則會把 `a` 當成 cloure 的 accu 繼續做下去,最後回傳 `Some(v)`。 - 回傳型別為 `std::ops::ControlFlow`:若回傳 `Break(b)`,則 `try_fold()` 直接回傳 `b`;若回傳 `Continue(c)`,則會把 `c` 當成 accu 繼續做下去,最後回傳 `v`。 - Iterator 中有很多 methods 會以 `try_fold()` 為基礎來實作,因此如果能自行實作更有效率的 `try_fold()`,可以顯著提升 iterator 的效率。 `nth()` : 取得第 n 個 item。不會占用 iterator 的所有權,也不會改變 iterator 本身。 `nth_back()` : 取得倒數第 n 個 item。 `last()` : 取得最後一個 item。 ==會耗用 iterator 中所有的 items,就算是`DoubleEndedIterator`也一樣。== 因此,如果該 iterator 是 `DoubleEndedIterator` 的話,可以改用 `next_back()` 會比較好。 `find()` 和 `rfind()` : 從 items 中找到第一個讓 closure 回傳 true 的 item,並回傳。 `find_map()` : 傳入的 closure 要依每一個 item 算出一個 `Option<B>`;`find_map()` 會回傳第一個 `Some(b)`。 `collection()` : - 只要是實作了 `trait FromIterator` 的型別(例如 `std::collections::*`),都可以利用 `iterator.collect()` 來建立。 - 建立 collection 的過程中,有時如果能知道 iterator 可能的長度,會更有效率(可以事先 allocate 需要的空間)。此時 iterator type 可以實作 `Iterator::size_hint()`,給 collection 使用。 ```rust // 利用 turbofish 語法指定要呼叫哪個版本的 collect() let args = std::env::args().collect::<Vec<String>>(); // 利用型別推斷幫你選擇對應的 collect() let args: Vec<String> = std::env::args().collect(); ``` `extend()` : - 這是 `trait Extend` 提供的方法,不是 `trait Iterator`。 - 把 iterable 的 items 附加到實作了 `Extend` 的集合上。 `partition()` : 依據 closure 回傳的 `true` or `false`,將 items 分成兩個不同的 group。 `for_each()` : - 把每一個 item 都丟到 closure 去執行。 - 相當於 `for`-loop,只是不能用 `break` 和 `continue`。 - 如果 iterator 加了很多 adapters 變得很長,用 `for_each` 就比 `for`-loop 優雅。 - 某些情況下,`for_each()` 會比 `for`-loop 快。 `try_for_each()` : 和 `try_fold()` 很像,closure 可以回傳 `Option<_>`, `Result<_,_>` 或 `ControlFlow<_>`。一旦 `None`, `Err(_)`, `Break(_)` 的時候,就提前 return。 ### ## 16. Collections ## 17. Strings and Text ## 18. Input and Output ## 19. Concurrency 三種使用 Rust thread 功能的方法: - Fork-join parallelism - Channels - Shared mutable state ### Fork-Join Parallelism Pros: 1. Simple. 2. 沒有執行上的效率瓶頸:只有最後要 join 時 threads 才需要等其他的 threads。 3. 效率提升的記算直接單純。 但效率提升還是不太可能達成完美的 n 倍,因為: - 工作無法真正平分。 - 在合併計算結果時還是有 overheads。 4. 正確性可期,因為不會有 data racing。 Cons: - 必須要能把工作分割成獨立的部份。不是所有的問題都可以這樣解決。 #### `spawn` 和 `join` - `spawn()` 的參數為 `FnOnce` 的 closure。捕捉的 values 必須被 move 進 closure 中。 - 如果 `main()` 沒有呼叫 `join()` 就直接結束的話,所有還沒跑完的 threads 就會直接被殺掉,不會跑完。 - 如果 thread panic 的話,`join()` 就會回傳 `Result::Err`。不會因為 children thread panic 就害 parent thread 也 panic。 #### 在 threads 間共享 immutable data - 我們無法直接在 closure 中使用 `&data`。因為我們無法確定 threads 的結束時間。萬一 `data` 已經被消滅了,但還有 thread 在跑,怎麼辦? - 解決方法:使用 `Arc` = atomic reference counting (pointer)。 #### Crate `rayon` 可以很方便地把兩個 closure 丟到不同的 threads 去執行,或是把 iterator 的資料分配給不同的 threads。 - 自動依照系統 CPU core 數做最好的分配。 - 不同的 threads 中可以直覺地使用 immutable ref,不需要再額外用 `Arc` 去包裝。 ### Channels - 把資料從一個 thread 送到另一個 thread 的單行道 -- thread-safe queue。 - Channel 的兩端各被不同的 threads 擁有。Sender 只有寫入的權限,receiver 只有移出的權限。 - Value 的 ownership 透過 channel 從 sender 傳送給 receiver。 - 如果 channel 中沒有資料,receive 端就會被 block 住。 ```rust= use std::{fs, thread}; use std::sync::mpsc; // multi-producer, single consumer let (sender, receiver) = mpsc::channel(); let handle = thread::spawn(move || { for filename in documents { let text = fs::read_to_string(filename)?; if sender.send(text).is_err() { break; } } Ok(()) }); ``` - `mpsc::channel()` 會產生 both sender & receiver。 - `sender` 的 ownership 會被 move 進 closure 中。 - `sender.send()` 在 `receiver` 被 dropped 時會回傳 `Err`。 - `receiver.recv()` 在 `sender` 被 dropped 時會回傳 `Err`。 - 第 6 行開始的 closure,回傳值為 `Result`,可以 handle 第 8 行中 `read_to_string()` 可以回傳的 `Err`。這個 closure 的回傳值會被放在 thread 的 `JoinHandle` 中。 #### Performance pitfalls ### `Send` & `Sync` - Send: 此型別的資料可以在 threads 間移動。 - Sync: 此型別的資料可以用 immutable ref 的方式在 threads 間共享。 是 Sync 就一定是 Send,但反之不然。像 `Receiver<T>` 就只能 Send,但不能 Sync。 ### Shared Mutability: `Mutex<T>` - `Mutex::lock()` 回傳的是 `Result`,若為 `Err`,表示 mutex「中毒」了。 - 其他 thread 在 lock mutex 的時候發生 panic。 ## 20. Asynchronous Programming ## 21. Macros ## 22. Unsafe Code ## 23. Foreign Functions