Try   HackMD

Rust 讀書會 Week 3

Ch8 Common collections

這章教大家怎用使用一些常用的 heap object

  • vector
  • string
  • hash map

Vector

Create

let v: Vec<i32> = Vec::new();
let v = vec![2, 3, 4, 5];

Update

// no need to explicitly write type
let v = Vec::new();
v.push(5);
v.push(6);

Access

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

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 做操作則會在編譯時報錯

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Iterate

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

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 和其它用法可以參考這篇這篇

String

Create

我們可以用 String::from() 將一個 str slice 轉為 String 物件

let hello1 = String::from("shaaark");
let hello2 = String::from("こんばんは");
let hello3 = String::from("起來嗨");

for c in hello2.chars() {
    print!("{}", c);
}

Update

有很多種方法可以協助使用者修改字串

let hello = String::from("shaaark");
hello.push_str("こんばんは");

let ninoko = String::from("ニノコ");
let welcome = hello + &ninoko
welcome += "起來嗨";

for c in welcome.chars() {
    print!("{}", c);
}

若是要串接很多字串可能會這樣寫,看起來有夠醜

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = s1 + "-" + &s2 + "-" + &s3;

幸好有個很棒的 macro 可以用

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 方式存取,兩者的差別簡單來說就是前者是可以被動態修改而後者不行,在官方文件中有詳細的說明可以參考

此外 Rust 中的 String/&str 物件 (以下通稱字串) 皆是以 UTF-8 儲存,也因此在一些存取方式上需要多加注意,例如說下方這段程式碼是沒辦法 compile 的

let s = "shaaark";
for i in 0..s.len() {
    print!("{}", s[i]);
}

因為 Rust 希望所有 indexing 操作都是在常數時間內完成,但 UTF-8 的單個字元不一定只佔一個位元寬度,所以在設計上就不允許以 index 直接取值,而是要改成

let s = "shaaark";
for i in 0..s.len() {
    print!("{}", s.chars().nth(i).unwrap());
}

或是簡潔一點

let s = "shaaark";
for c in s.chars() {
    print!("{}", c);
}

Hashmap

Create

由於 hashmap 並沒有被包含在 prelude 中,我們必須要手動將它引入

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 直接修改對應的值

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

或是以 entry 加上 or_insert method 判斷初值是否存在,再 insert

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

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 跟著錯誤訊息彈出來感覺如何呢?

fn main() {
    panic!("hello world");
}

如果想要得知完整的 stack trace 可以在執行 cargo run 加上 RUST_BACKTRACE=1

Result

Result 是個用來協助錯誤處理而生的 enum,定義如下

enum Result<T, E> {
    Ok(T),
    Err(E),
}

這邊以開檔為例,我們可以對 return value 做 pattern matching

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

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 來寫

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);
        }
    });
}

當然,如果你是個慵懶的開發者,啥錯誤都不想要處理的話,也可以直接

let f = File::open("hello.txt").unwrap();

或是想讓 debug 容易一點的話

let f = File::open("hello.txt").expect("damn!");

? (symbol)

章節一開始提到的語法就是指 "?" symbol,開發者可以利用他方便的將錯誤作為 return 值回傳

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)
}
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)
}