Option 和 Result

我們現在理解了列舉和泛型,所以我們也能理解 OptionResult。Rust 用這兩種列舉來使程式碼更安全。

我們將從 Option 開始。

Option

當你有一個值,它可能存在,也可能不存在時,你就該用 Option。當一個值存在時它就是 Some(value),不存在時就是 None,下面是一個可以用Option 來改進的壞程式碼範例。

    // ⚠️
fn take_fifth(value: Vec<i32>) -> i32 {
    value[4]
}

fn main() {
    let new_vec = vec![1, 2];
    let index = take_fifth(new_vec);
}

當我們執行這段程式碼時,它發生恐慌(panic)。這是訊息:

thread 'main' panicked at 'index out of bounds: the len is 2 but the index is 4', src\main.rs:34:5

恐慌的意思是,程式在問題發生前就停止了。Rust 看到函式想要做些不可能的事情,就會停止。它"解開(unwind)堆疊"(從堆疊中取出值),並告訴你"對不起,我不能那樣做"。

所以現在我們將回傳型別從 i32 改為 Option<i32>。這意味著"如果有的話給我 Some(i32),如果沒有的話給我 None"。我們說 i32 是"包"在 Option 裡面,也就是說它放在 Option 裡面。你必須做些事情才能把這個值取出來。

fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 { // .len() 給出向量的長度。
                         // 它必需是至少是 5。
        None
    } else {
        Some(value[4])
    }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    println!("{:?}, {:?}", take_fifth(new_vec), take_fifth(bigger_vec));
}

印出的是 None, Some(5)。這下好了,因為現在我們再也不恐慌了。但是我們要如何得到 5 這個值呢?

我們可以用 .unwrap() 從 Option 裡面得取值,但要小心使用 .unwrap()。這就像拆禮物一樣:也許裡面有好東西,也許裡面有條憤怒的蛇。只有在你確定的情況下,你才會想要用 .unwrap()。如果你拆開一個 None 的值,程式就會恐慌。

// ⚠️
fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 {
        None
    } else {
        Some(value[4])
    }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    println!("{:?}, {:?}",
        take_fifth(new_vec).unwrap(), // 這個是 None。 .unwrap() 會恐慌!
        take_fifth(bigger_vec).unwrap()
    );
}

訊息是:

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src\main.rs:14:9

但我們可以不需要用 .unwrap()。我們能用 match。那麼我們就可以把我們有 Some 的值印出來,如果是 None 的值就不要碰。比如說:

fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 {
        None
    } else {
        Some(value[4])
    }
}

fn handle_option(my_option: Vec<Option<i32>>) {
  for item in my_option {
    match item {
      Some(number) => println!("Found a {}!", number),
      None => println!("Found a None!"),
    }
  }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    let mut option_vec = Vec::new(); // 用新的向量存放我們的 option
                                     // 向量的型別: Vec<Option<i32>>。那是 Option<i32> 的向量。

    option_vec.push(take_fifth(new_vec)); // 這會推送 "None" 進向量
    option_vec.push(take_fifth(bigger_vec)); // 這會推送 "Some(5)" 進向量

    handle_option(option_vec); // handle_option 查看向量裡的每個 option。
                               // 並印出值如果是 Some。如果是 None 就不碰。
}

印出:

Found a None!
Found a 5!

因為我們知道泛型,所以我們能夠讀懂 Option 的程式碼。它看起來像這樣:

enum Option<T> {
    None,
    Some(T),
}

fn main() {}

要記得的重點是:有了 Some,你就有了型別為 T 的值(任何型別)。還要注意的是,enum 名字後面有圍繞著 T 的角括號是用來告訴編譯器它是泛型。且它沒有 Display 這樣的特徵(trait)或任何東西來限制它,所以它可以是任何東西。但 None 的話,你就什麼都沒有。

所以在 Option 的 match 陳述式中,你不能說:

#![allow(unused)]
fn main() {
// 🚧
Some(value) => println!("The value is {}", value),
None(value) => println!("The value is {}", value),
}

因為 None 就只是 None

當然,還有更簡單的方式來使用 Option。在這段程式碼中,我們將會使用一個叫做 .is_some() 的方法來告訴我們它是否是 Some。(對,還有個叫做 .is_none() 的方法。)在這個更簡單的方式中,我們不再需要 handle_option() 了。我們也不需要存放 Option 的向量了。

fn take_fifth(value: Vec<i32>) -> Option<i32> {
    if value.len() < 5 {
        None
    } else {
        Some(value[4])
    }
}

fn main() {
    let new_vec = vec![1, 2];
    let bigger_vec = vec![1, 2, 3, 4, 5];
    let vec_of_vecs = vec![new_vec, bigger_vec];
    for vec in vec_of_vecs {
        let inside_number = take_fifth(vec);
        if inside_number.is_some() {
            // 如果我們得到 Some,.is_some() 就回傳 true,None 就回傳 false
            println!("We got: {}", inside_number.unwrap()); // 因為我們已經檢查過了,現在它能安全的使用 .unwrap()
        } else {
            println!("We got nothing.");
        }
    }
}

印出:

We got nothing.
We got: 5

Result

Result 和 Option 類似,但區別是:

  • Option 和 SomeNone 有關 (有值或無值),
  • Result 和 OkErr 有關 (成功的,或錯誤的結果)。

所以 Option 是用在如果你思考的是:"也許會有東西,也許不會有。"但 Result 則是用在如果你思考的是:"也許會失敗。"

比較一下,這是 Option 和 Result 的簽名(signature)。

enum Option<T> {
    None,
    Some(T),
}

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

fn main() {}

所以 Result 在 "Ok" 裡面有值,在 "Err" 裡面也有值。這是因為錯誤裡通常有包含描述錯誤的資訊。

Result<T, E> 的意思是你要想好 Ok 要回傳什麼,Err 要回傳什麼。其實,你可以決定任何事情。甚至這樣也可以:

fn check_error() -> Result<(), ()> {
    Ok(())
}

fn main() {
    check_error();
}

check_error 說"如果得到 Ok 就回傳 (),如果得到 Err 就回傳 ()"。然後我們用 () 回傳 Ok

編譯器給了我們有趣的警告:

warning: unused `std::result::Result` that must be used
 --> src\main.rs:6:5
  |
6 |     check_error();
  |     ^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_must_use)]` on by default
  = note: this `Result` may be an `Err` variant, which should be handled

這是真的:我們只回傳了 Result,但它可能是 Err。所以讓我們稍微處理一下這個錯誤,儘管我們仍然沒有真的做任何事情。

fn give_result(input: i32) -> Result<(), ()> {
    if input % 2 == 0 {
        return Ok(())
    } else {
        return Err(())
    }
}

fn main() {
    if give_result(5).is_ok() {
        println!("It's okay, guys")
    } else {
        println!("It's an error, guys")
    }
}

印出 It's an error, guys。所以我們只處理了第一個錯誤。

記住,輕鬆檢查的四種方法是.is_some()is_none()is_ok()is_err()

有時一個帶有 Result 的函式會用 String 來表示 Err 的值。這不是最好的方法,但比我們目前所做的要好一些。

fn check_if_five(number: i32) -> Result<i32, String> {
    match number {
        5 => Ok(number),
        _ => Err("Sorry, the number wasn't five.".to_string()), // 這是我們的錯誤訊息
    }
}

fn main() {
    let mut result_vec = Vec::new(); // 建立新的向量放結果

    for number in 2..7 {
        result_vec.push(check_if_five(number)); // 推送每個結果進向量
    }

    println!("{:?}", result_vec);
}

我們的向量印出:

[Err("Sorry, the number wasn\'t five."), Err("Sorry, the number wasn\'t five."), Err("Sorry, the number wasn\'t five."), Ok(5),
Err("Sorry, the number wasn\'t five.")]

就像 Option 一樣,在 Err 上用 .unwrap() 就會恐慌。

    // ⚠️
fn main() {
    let error_value: Result<i32, &str> = Err("There was an error"); // 建立已經是Err的Result
    println!("{}", error_value.unwrap()); // 拆開它
}

程式恐慌並印出:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "There was an error"', src\main.rs:30:20

這些資訊幫助你修正你的程式碼。src\main.rs:30:20 的意思是"在目錄 src 的 main.rs 內,第 30 行和第 20 列"。所以你可以去那裡檢視你的程式碼並修復問題。

你也可以建立自己的錯誤型別,標準函式庫中的 Result 函式和其他人的程式碼通常都會這樣做。例如,標準函式庫中的這個函式:

#![allow(unused)]
fn main() {
// 🚧
pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>
}

這個函式接受位元組向量(u8),並嘗試做出 String,所以 Result 的成功情況是 String,錯誤情況是 FromUtf8Error。你可以給你的錯誤型別取任何你想要的名字。

OptionResult 一起使用的 match 有時需要很多程式碼。例如,在 Vec.get() 方法回傳 Option

fn main() {
    let my_vec = vec![2, 3, 4];
    let get_one = my_vec.get(0); // 用 0 來取得第一個數
    let get_two = my_vec.get(10); // 回傳 None
    println!("{:?}", get_one);
    println!("{:?}", get_two);
}

印出:

Some(2)
None

所以現在我們可以匹配得到值了。讓我們使用 0 到 10 的範圍,看看是否匹配 my_vec 中的數字。

fn main() {
    let my_vec = vec![2, 3, 4];

    for index in 0..10 {
      match my_vec.get(index) {
        Some(number) => println!("The number is: {}", number),
        None => {}
      }
    }
}

這不錯,但是我們不對 None 做任何處理,因為我們不關心。這裡我們可以用 if let 讓程式碼變小。if let 的意思是"匹配就做,否則不做"。if let 是在你不要求對所有的東西都匹配的時候使用。

fn main() {
    let my_vec = vec![2, 3, 4];

    for index in 0..10 {
      if let Some(number) = my_vec.get(index) {
        println!("The number is: {}", number);
      }
    }
}

切記if let Some(number) = my_vec.get(index) 的意思是 "如果你從 my_vec.get(index) 得到 Some(number)"。

另外注意:它使用的是 =。它不是布林值。

while let 是像 if let 的 while 迴圈。想象一下,我們有這樣的氣象站資料:

["Berlin", "cloudy", "5", "-7", "78"]
["Athens", "sunny", "not humid", "20", "10", "50"]

我們想拿到數字,而不是文字。對於數字,我們可以使用叫做 parse::<i32>() 的方法。parse() 是方法,::<i32> 是型別。它將嘗試把 &str 變成 i32,如果成功的話就把結果給我們。它回傳 Result,因為它可能無法執行(比如你想讓它解析"Billybrobby"──那不是一個數字)。

我們也會用 .pop()。這會從向量中取出最後一個元素。

fn main() {
    let weather_vec = vec![
        vec!["Berlin", "cloudy", "5", "-7", "78"],
        vec!["Athens", "sunny", "not humid", "20", "10", "50"],
    ];
    for mut city in weather_vec {
        println!("For the city of {}:", city[0]); // 在我們的資料中,每一筆的第一個元素都是城市名
        while let Some(information) = city.pop() {
            // 這行意思是:直到你不能 pop 前繼續執迴圈
            // 當向量沒有元素時,它會回傳 None
            // 並且它會停正。
            if let Ok(number) = information.parse::<i32>() {
                // 試著解析我們稱作information的變數
                // 這裡的回傳結果如果是 Ok(number),它會印出數值
                println!("The number is: {}", number);
            }  // 這裡我們不寫任何東西,因為如果我們遇到錯誤我們不做處理。會把(錯誤)它們都拋出去
        }
    }
}

將印出:

For the city of Berlin:
The number is: 78
The number is: -7
The number is: 5
For the city of Athens:
The number is: 50
The number is: 10
The number is: 20