# 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