# 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 做操作則會在編譯時報錯 ![](https://i.imgur.com/FMVTUYP.png) ### 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` ![](https://i.imgur.com/ps00JoV.png) ## 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) } ```