# Rust 讀書會 Week 3
# Ch8 Common collections
這章教大家怎用使用一些常用的 heap object
- vector
- string
- hash map
## Vector
### Create
```rust
let v: Vec<i32> = Vec::new();
let v = vec![2, 3, 4, 5];
```
### Update
```rust
// no need to explicitly write type
let v = Vec::new();
v.push(5);
v.push(6);
```
### Access
```rust
let v = vec![1, 2, 3, 4, 5];
let fifth: &i32 = &v[4];
println!("The fifth element is {}", fifth);
// or
match v.get(4) {
Some(fifth) => println!("The fifth element is {}", fifth),
None => println!("There is no fifth element."),
}
```
使用 get method 無論如何都可以拿到一個型別為 `Option<&T>` 的物件,而直接對 vector 取 reference 的話則可能產生 panic
```rust
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {}", first);
```
若是在獲得 immutable reference 後,對物件以 mutable reference 做操作則會在編譯時報錯

### Iterate
```rust
let mut v = vec![1, 2, 3, 4, 5];
for i in &mut v {
*i += 20;
}
for i in &v {
println!("{}", i);
}
for i in v.iter_mut() {
*i += 20
}
for i in v.iter() {
println!("{}", i);
}
```
### Work with Enum
```rust
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
```
有關 vector 的 pitfall 和其它用法可以參考[這篇](https://stackoverflow.com/questions/34733811/what-is-the-difference-between-iter-and-into-iter)跟[這篇](https://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html)
## String
### Create
我們可以用 `String::from()` 將一個 `str` slice 轉為 `String` 物件
```rust
let hello1 = String::from("shaaark");
let hello2 = String::from("こんばんは");
let hello3 = String::from("起來嗨");
for c in hello2.chars() {
print!("{}", c);
}
```
### Update
有很多種方法可以協助使用者修改字串
```rust
let hello = String::from("shaaark");
hello.push_str("こんばんは");
let ninoko = String::from("ニノコ");
let welcome = hello + &ninoko
welcome += "起來嗨";
for c in welcome.chars() {
print!("{}", c);
}
```
若是要串接很多字串可能會這樣寫,看起來有夠醜
```rust
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = s1 + "-" + &s2 + "-" + &s3;
```
幸好有個很棒的 macro 可以用
```rust
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
// won't take the ownership
let s = format!("{}-{}-{}", s1, s2, s3);
```
### Access
字串在 Rust 中分成兩種: String 和 str,其中後者只能(?)以 reference 方式存取,兩者的差別簡單來說就是前者是可以被動態修改而後者不行,在[官方文件](https://doc.rust-lang.org/std/string/struct.String.html)中有詳細的說明可以參考
此外 Rust 中的 String/&str 物件 (以下通稱字串) 皆是以 UTF-8 儲存,也因此在一些存取方式上需要多加注意,例如說下方這段程式碼是沒辦法 compile 的
```rust
let s = "shaaark";
for i in 0..s.len() {
print!("{}", s[i]);
}
```
因為 Rust 希望所有 indexing 操作都是在常數時間內完成,但 UTF-8 的單個字元不一定只佔一個位元寬度,所以在設計上就不允許以 index 直接取值,而是要改成
```rust
let s = "shaaark";
for i in 0..s.len() {
print!("{}", s.chars().nth(i).unwrap());
}
```
或是簡潔一點
```rust
let s = "shaaark";
for c in s.chars() {
print!("{}", c);
}
```
## Hashmap
### Create
由於 hashmap 並沒有被包含在 prelude 中,我們必須要手動將它引入
```rust
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
```
注意到建立 hashmap 時所有權的轉移是根據型別是否有實作 Copy trait 來決定的,以 i32 為例因為有實作 Copy 則會直接複製一份到 hashmap 中
### Update
我們可以透過 `insert` 直接修改對應的值
```rust
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
```
或是以 `entry` 加上 `or_insert` method 判斷初值是否存在,再 insert
```rust
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
// return &mut
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
```
### Access
```rust
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let blue_val = scores.get("Blue".to_string()).unwrap();
for (key, value) in &scores {
println!("{}: {}", key, value);
}
```
# CH9 Error handling
搞定錯誤處理是寫程式必經的一段痛苦旅程,幸運的是 Rust 在語言層面上提供了方便的語法及功能讓開發者可以較為輕鬆的完成這件事
## panic!
看到 hello world 跟著錯誤訊息彈出來感覺如何呢?
```rust
fn main() {
panic!("hello world");
}
```
如果想要得知完整的 stack trace 可以在執行 `cargo run` 加上 `RUST_BACKTRACE=1`

## Result
Result 是個用來協助錯誤處理而生的 enum,定義如下
```rust
enum Result<T, E> {
Ok(T),
Err(E),
}
```
這邊以開檔為例,我們可以對 return value 做 pattern matching
```rust
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
```
上面這個例子可能沒有辦法分辨是權限不足,還是檔案不存在,所以我們可以再對 Result 做一次 pattern matching
```rust
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error)
}
},
};
}
```
不過上面這個例子似乎是用到太多 match 了,可以再精簡一點,用 `unwrap_or_else` 搭配 lambda function 來寫
```rust
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
})
} else {
panic!("Problem opening the file: {:?}", error);
}
});
}
```
當然,如果你是個慵懶的開發者,啥錯誤都不想要處理的話,也可以直接
```rust
let f = File::open("hello.txt").unwrap();
```
或是想讓 debug 容易一點的話
```rust
let f = File::open("hello.txt").expect("damn!");
```
## ? (symbol)
章節一開始提到的語法就是指 "?" symbol,開發者可以利用他方便的將錯誤作為 return 值回傳
```rust
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
```
```rust
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
let mut f = File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
```