問號(?)運算子
有一種更短的方式來處理 Result
(及 Option
),它比 match
和 if 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::Result
、std::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
。如果你這樣做,它永遠不會恐慌。也就是:
-
- 很好,因為你的程式不會恐慌,但是
-
- 可能不太好,如果你想讓程式在出現問題時恐慌。
但通常我們都不希望自己的程式恐慌,所以 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。