問號(?)運算子

有一種更短的方式來處理 Result(及 Option),它比 matchif let 更短。它叫做"問號運算子",就是 ?。在回傳 Result 的函式後,可以加上 ?。這會:

  • 如果是 Ok,回傳 Result 裡面的內容。
  • 如果是 Err,則將錯誤送回。

換句話說,它幾乎為你做了所有的事情。

我們可以用 .parse() 再試一次。我們將編寫名為 parse_str 的函式,試圖將 &str 變成 i32。它看起來像這樣:

use std::num::ParseIntError;

fn parse_str(input: &str) -> Result<i32, ParseIntError> {
    let parsed_number = input.parse::<i32>()?; // 問號在這
    Ok(parsed_number)
}

fn main() {}

這個函式接受 &str。如果是 Ok,則它給出包在 Ok 中的 i32。如果是 Err,則回傳包起來的 ParseIntError。然後我們嘗試解析這個數字,並加上 ?。也就是"檢查是否錯誤,如果沒問題就給出 Result 裡面的內容"。如果有問題,就會返回錯誤並結束。但如果沒問題,就會進入下一行。下一行是 Ok() 裡面的數字。我們需要用 Ok 來包裝,因為要回傳的是 Result<i32, ParseIntError>,而不是 i32

現在我們可以試試我們的函式。讓我們看看它對 &str 的向量有什麼作用。

fn parse_str(input: &str) -> Result<i32, std::num::ParseIntError> {
    let parsed_number = input.parse::<i32>()?;
    Ok(parsed_number)
}

fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
        let parsed = parse_str(item);
        println!("{:?}", parsed);
    }
}

印出:

Err(ParseIntError { kind: InvalidDigit })
Ok(8)
Err(ParseIntError { kind: InvalidDigit })
Err(ParseIntError { kind: InvalidDigit })
Ok(6060)

我們是怎麼找到 std::num::ParseIntError 的呢?一個簡單的方法就是再"問"一下編譯器。

fn main() {
    let failure = "Not a number".parse::<i32>();
    failure.rbrbrb(); // ⚠️ 編譯器: "rbrbrb()是什麼???"
}

編譯器無法了解,並說:

error[E0599]: no method named `rbrbrb` found for enum `std::result::Result<i32, std::num::ParseIntError>` in the current scope
 --> src\main.rs:3:13
  |
3 |     failure.rbrbrb();
  |             ^^^^^^ method not found in `std::result::Result<i32, std::num::ParseIntError>`

所以 std::result::Result<i32, std::num::ParseIntError> 就是我們所需要的簽名。

我們不需要寫 std::result::Result,因為 Result 總是"在範圍內"(in scope = 準備好使用)。Rust 對我們經常使用的所有型別都是這樣做的,所以我們不必寫 std::result::Resultstd::collections::Vec 等。

我們現在還沒有處理到像檔案這樣的東西,所以 ? 運算子看起來還不太有用。但這裡有個無用但快速的例子,說明你如何在單行上使用它。與其用 .parse() 建立 i32,不如做更多。我們將做個 u16,然後把它變成 String,再變成 u32,然後再變回 String,最後變成 i32

use std::num::ParseIntError;

fn parse_str(input: &str) -> Result<i32, ParseIntError> {
    let parsed_number = input.parse::<u16>()?.to_string().parse::<u32>()?.to_string().parse::<i32>()?; // 每次檢查時加上 ? 並傳下去
    Ok(parsed_number)
}

fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
        let parsed = parse_str(item);
        println!("{:?}", parsed);
    }
}

印出同樣的東西,但這次我們在一行中處理了三個 Result。稍後我們將對檔案進行處理,因為很多事情都可能出錯,它們總是回傳 Result

想像這件事:你想開啟檔案,向它寫入,然後關閉它。首先你需要成功找到這個檔案(這是 Result)。然後你需要成功地寫入它(也是 Result)。有了 ? 你可以用一行做到那些事。

何時善用 panic 和 unwrap

Rust 有個 panic! 巨集讓你可以用來讓程式恐慌。它很容易使用:

fn main() {
    panic!("Time to panic!");
}

"Time to panic!" 這個訊息在你執行程式時會顯示:thread 'main' panicked at 'Time to panic!', src\main.rs:2:3

你會記得 src\main.rs 是目錄和檔名,2:3 是行號和列號。有了這些資訊,你就可以找到程式碼並修復它。

panic! 是個很好用的巨集,以確保你知道東西何時有變化。例如,這個叫做 prints_three_things 的函式總是從向量中印出索引 [0]、[1] 和 [2]。這沒關係,因為我們總是給它有三個元素的向量:

fn prints_three_things(vector: Vec<i32>) {
    println!("{}, {}, {}", vector[0], vector[1], vector[2]);
}

fn main() {
    let my_vec = vec![8, 9, 10];
    prints_three_things(my_vec);
}

印出 8, 9, 10,一切正常。

但試想之後我們程式碼愈寫越多,忘記了 my_vec 只能有三個元素。現在 my_vec 在這部分有六個元素:

fn prints_three_things(vector: Vec<i32>) {
  println!("{}, {}, {}", vector[0], vector[1], vector[2]);
}

fn main() {
  let my_vec = vec![8, 9, 10, 10, 55, 99]; // 現在 my_vec 有六個東西
  prints_three_things(my_vec);
}

沒有發生錯誤,因為 [0]、[1] 和 [2] 都在這個較長的 Vec 裡面。但如果只能有三個元素真的很重要呢?因為程式不會恐慌,我們也就不會知道有問題了。我們反而應該這樣做:

fn prints_three_things(vector: Vec<i32>) {
    if vector.len() != 3 {
        panic!("my_vec must always have three items") // 如果長度不是 3 會恐慌
    }
    println!("{}, {}, {}", vector[0], vector[1], vector[2]);
}

fn main() {
    let my_vec = vec![8, 9, 10];
    prints_three_things(my_vec);
}

現在我們知道向量是否有三個元素,因為它如預期的發生恐慌:

    // ⚠️
fn prints_three_things(vector: Vec<i32>) {
    if vector.len() != 3 {
        panic!("my_vec must always have three items")
    }
    println!("{}, {}, {}", vector[0], vector[1], vector[2]);
}

fn main() {
    let my_vec = vec![8, 9, 10, 10, 55, 99];
    prints_three_things(my_vec);
}

我們得到了 thread 'main' panicked at 'my_vec must always have three items', src\main.rs:8:9。多虧了 panic!,我們現在記得 my_vec 應該只能有三個元素。所以 panic! 是個可以在你的程式碼中建立提醒的好巨集。

還有三個與 panic! 類似的巨集,你會在測試中經常使用。它們分別是 assert!assert_eq!assert_ne!

這是它們的涵義:

  • assert!(): 如果 () 裡面的部分不是 true,程式就會恐慌。
  • assert_eq!(): () 裡面的兩個元素必須相同(equal)。
  • assert_ne!(): () 裡面的兩個元素必須不相同。(ne 表示不相同)

一些範例:

fn main() {
    let my_name = "Loki Laufeyson";

    assert!(my_name == "Loki Laufeyson");
    assert_eq!(my_name, "Loki Laufeyson");
    assert_ne!(my_name, "Mithridates");
}

這沒做任何事,因為三個 assert 巨集都沒出錯。(也是我們想要的)

如果你願意,還可以加個提示訊息。

fn main() {
    let my_name = "Loki Laufeyson";

    assert!(
        my_name == "Loki Laufeyson",
        "{} should be Loki Laufeyson",
        my_name
    );
    assert_eq!(
        my_name, "Loki Laufeyson",
        "{} and Loki Laufeyson should be equal",
        my_name
    );
    assert_ne!(
        my_name, "Mithridates",
        "You entered {}. Input must not equal Mithridates",
        my_name
    );
}

這些訊息只有在程式恐慌時才會顯示。所以如果你執行:

fn main() {
    let my_name = "Mithridates";

    assert_ne!(
        my_name, "Mithridates",
        "You enter {}. Input must not equal Mithridates",
        my_name
    );
}

會顯示:

thread 'main' panicked at 'assertion failed: `(left != right)`
  left: `"Mithridates"`,
 right: `"Mithridates"`: You entered Mithridates. Input must not equal Mithridates', src\main.rs:4:5

所以它說 "你說左邊 != 右邊,但是左邊 == 右邊"。而且它顯示我們寫的訊息為 You entered Mithridates. Input must not equal Mithridates

unwrap 也適合用在你寫自己的程式,並想讓它在出現問題時崩潰。之後等你的程式碼寫完後,把 unwrap 改成其他不會崩潰的東西就好了。

你也可以用 expect,它像是 unwrap 但更好一些,因為它讓你寫自己的訊息內容。教科書通常會給出這樣的建議:"如果你經常使用 .unwrap(), 至少也要用 .expect() 來獲得更好的錯誤訊息。"

這樣會崩潰:

   // ⚠️
fn get_fourth(input: &Vec<i32>) -> i32 {
    let fourth = input.get(3).unwrap();
    *fourth
}

fn main() {
    let my_vec = vec![9, 0, 10];
    let fourth = get_fourth(&my_vec);
}

錯誤訊息是 thread 'main' panicked at 'called Option::unwrap() on a None value', src\main.rs:7:18

現在我們用 expect 來寫自己的訊息:

   // ⚠️
fn get_fourth(input: &Vec<i32>) -> i32 {
    let fourth = input.get(3).expect("Input vector needs at least 4 items");
    *fourth
}

fn main() {
    let my_vec = vec![9, 0, 10];
    let fourth = get_fourth(&my_vec);
}

又崩潰了,但錯誤內容比較好:thread 'main' panicked at 'Input vector needs at least 4 items', src\main.rs:7:18.expect() 因為這個原因比 .unwrap() 要好一點,但是在 None 上還是會恐慌。現在這裡有個不太好的案例,一個函式試圖 unwrap 兩次。它接受一個 Vec<Option<i32>>,所以可能每個部分會有 Some<i32>,也可能是 None

fn try_two_unwraps(input: Vec<Option<i32>>) {
    println!("Index 0 is: {}", input[0].unwrap());
    println!("Index 1 is: {}", input[1].unwrap());
}

fn main() {
    let vector = vec![None, Some(1000)]; // 這個向量有None,所以會恐慌
    try_two_unwraps(vector);
}

訊息是:thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src\main.rs:2:32。我們不確定是第一個 .unwrap() 還是第二個,直到我們去檢查行號。最好是檢查一下長度,也不要 unwrap。不過有了 .expect() 至少會好 一點。這裡用 .expect()

fn try_two_unwraps(input: Vec<Option<i32>>) {
    println!("Index 0 is: {}", input[0].expect("The first unwrap had a None!"));
    println!("Index 1 is: {}", input[1].expect("The second unwrap had a None!"));
}

fn main() {
    let vector = vec![None, Some(1000)];
    try_two_unwraps(vector);
}

所以這有好一點:thread 'main' panicked at 'The first unwrap had a None!', src\main.rs:2:32。我們也有行號讓我們可以找到它。

如果你要永遠有值且是你想選擇的,也可以用unwrap_or。如果你這樣做,它永遠不會恐慌。也就是:

    1. 很好,因為你的程式不會恐慌,但是
    1. 可能不太好,如果你想讓程式在出現問題時恐慌。

但通常我們都不希望自己的程式恐慌,所以 unwrap_or 是個適合拿來用的方法。

fn main() {
    let my_vec = vec![8, 9, 10];

    let fourth = my_vec.get(3).unwrap_or(&0); // 如果 .get 沒成功,我們會傳回值 &0。
                                              // .get 回傳的是參考,所以我們需要的是 &0 而非 0
                                              // 如果你想要 fourth 是 0 而非 &0,你可以寫帶有 * 的
                                              // "let *fourth",但這裡我們只是要印出也就無關緊要

    println!("{}", fourth);
}

印出 0,因為 .unwrap_or(&0) 即使在 None 時也會回傳 0。