【譯】Rust 的 Result 類型入門
- A Primer on Rust’s Result Type 譯文
- 原文鏈接:https://medium.com/@JoeKreydt/a-primer-on-rusts-result-type-66363cf18e6a
- 原文作者:Joe Kreydt
- 譯文出處:https://github.com/suhanyujie/article-transfer-rs
- 譯者:suhanyujie
- tips:水平有限,翻譯不當之處,還請指正,謝謝!
Result
類型是 Rust 中處理錯誤的常用方法類型,它比較靈活;應該是非常靈活!
對於那些正在學 Rust 的人來講,Result 可能不太直觀,你可以通過閱讀它的標准庫文檔來了解如何使用是個不錯的方法。如果你想迫切的學會它,也是可以的,但如果你只是用它處理錯誤或者使用某個返回 Result 類型的函數(很多人都這樣做),你可能體會不到它的妙處。
為了節省大家的時間,我打算使用英語來解釋 Rust 的 Result 類型。
Result 是什么?
參考Rust 權威指南
“Result 表達的是錯誤的可能性。通常錯誤是用來解釋某種任務執行失敗的原因。”
用朴素的英語解釋
Result 是一個函數返回的類型,它可以是 Ok,也可以是 Err。如果是 Ok,則表示函數按照預期執行完成。如果是 Err,則該函數出現了錯誤。
Result 用來做什么?
根據 Rust 權威指南
Result 類型是對計算過程中可能出現的結果的表示方式。按照慣例,如果一個結果是預期的 Ok
,那么另一個結果則是意料之外的,即 Err
。
請再直觀點
函數返回了值。這些值具有特定的數據類型。函數可以返回 Result 類型的結果。Result 類型根據函數是否按預期執行而變化。然后,程序員可以編寫一些代碼,如果函數按預期執行則返回 A,如果遇到異常,則返回 B。
不處理 Result,則產生異常
error[E0308]: mismatched types
--> main.rs:20:26
|
20 | let my_number: f64 = my_string.trim().parse(); //.unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^ expected f64, found enum `std::result::Result`
|
= note: expected type `f64`
found type `std::result::Result<_, _>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
compiler exit status 1
報錯信息中關鍵的部分是,“expected f64, found enum.”,類似的場景中,可能還會有:
- “expected u32, found enum”
- “expected String, found enum”
- “expected [insert type here], found enum”
如果你得到一個類似上面的錯誤,那是因為需要你處理函數返回的 Result 類型數據
類型為 Error 的 Result 的程序
use std::io::{stdin, self, Write};
fn main(){
let mut my_string = String::new();
print!(“Enter a number: “);
io::stdout().flush().unwrap();
stdin().read_line(&mut my_string)
.expect(“Did not enter a correct string”);
let my_number: f64 = my_string.trim().parse();
println!(“Yay! You entered a number. It was {:?}”, my_num);
}
在這個程序中,它提示用戶輸入一個數字。然后將輸入作為字符串讀入並存儲下來。我們想要的是一個數值類型,不是 String,所以我們需要使用 parse() 函數將其轉換為一個 64 位浮點數(f64)。
如果用戶輸入的是一個數字,那么 parse() 函數將其轉換為 f64 沒什么大問題。但我們仍然會得到一個錯誤。
發生錯誤是因為 parse() 函數不只是將 String 轉換為數字並返回。相反,它接受字符串,將其轉換為數字,然后返回 Result 類型。Result 類型需要被解包才能得到我們需要的數值。
用 Unwrap() 或 Expect() 修復錯誤
轉換后的數字可以可以通過在 parse() 后面附加調用 unwrap() 函數將數字從 Result 中“解包”出來,類似於這樣:
let my_number: f64 = my_string.trim().parse().unwrap();
unwrap() 函數可以看出 Result 中類型,可能是 Ok,也可能是 Err。如果 Result 中包裹的類型是 Ok,那么 unwrap() 則返回它的值。如果 Result 中的類型是 Err,unwrap() 則會讓程序崩潰。
你也可以用 expect() 函數像下方這樣來處理 Result:
let my_number: f64 = my_string.trim().parse().expect(“Parse failed”);
expect() 的工作方式類似於 unwrap(),假如 Result 是 Err,expect() 將會使程序崩潰並且將其中的字符串內容 —— “Parse failed.”展示在標准輸出中。
使用 unwrap() 和 expect() 的缺點
當我們使用 unwrap() 和 expect() 函數時,如果遇到錯誤,程序會發生崩潰。如果錯誤發生的幾率非常小,這也許可以容忍,但在某些情況下,錯誤發生的概率會比較大。
在上面的示例中,用戶可能輸入錯誤,輸入的不是數值(可能是字母或者特殊符號)。我們並不想每次用戶輸入錯誤的內容程序就發生崩潰。相反,我們應該提示用戶應該輸入數字。這種場景下,Result 就非常有用,尤其是當它與一個模式匹配的表達式相結合的時候。
用匹配表達式修復錯誤
use std::io::{stdin, self, Write};
fn main(){
let mut my_string = String::new();
print!(“Enter a number: “);
io::stdout().flush().unwrap();
let my_num = loop {
my_string.clear();
stdin().read_line(&mut my_string)
.expect(“Did not enter a correct string”);
match my_string.trim().parse::<f64>() {
Ok(_s) => break _s,
Err(_err) => println!(“Try again. Enter a number.”)
}
};
println!(“You entered {:?}”, my_num);
}
如果你問我怎么實現,上面就是示例代碼!
前面提到的不優雅的實現和優雅的實現方式的不同點是在循環體內部。我們可以分解一下。
代碼分析
在 loop 之前,我們提示用戶輸入一個數字。接着我們聲明 my_num。
我們將循環體中返回的值(用戶的輸入,它將從字符串轉換為數字)賦給 my_num:
let my_num = loop {
在循環體中,我們阻塞等待用戶輸入。然后接收用戶的輸入,在這個過程中我們有三個問題要解決。
- 1.我們需要確定用戶輸入的是數字而非其他的字符,一個詞或者一個字母。
- 2.Rust 中的 read_line() 函數能夠以字符串的類型拿到用戶的輸入。我們需要將其轉換為浮點數。
- 3.如果用戶沒有輸入數字,我們需要清理變量,並提示和等待用戶再次輸入。
在第三部分問題(清理 my_string 變量)在循環體內的第一行就已經實現了:
my_string.clear();
下一步,我們接收用戶的輸入:
stdin().read_line(&mut my_string)
.expect(“Did not enter a correct string”);
read_line() 函數返回一個 Result 類型。我們使用 expect() 函數處理它。在這種情形下是完全沒問題的,因為 read_line() 出錯的幾率非常小。用戶通常只能在終端輸入一個字符串,而這正是 read_line() 所需要處理的。
The string of user input returned by read_line() is stored in the my_string variable.
通過 read_line() 把用戶輸入的字符串返回並存在 my_string 變量中。
The Juicy Part
漸入佳境
現在我們已經將輸入的字符串存在 my_string 中,我們需要將其轉換為浮點數。使用 parse() 函數可以實現,然后將浮點數結果返回。所以我們有不止 Result 的類型需要處理,但這一次,我們很可能會出現一個錯誤。如果用戶輸入的是非數字, parse() 將會返回一個錯誤類型的 Result(Err)。如果發生這種情況,我們不希望程序崩潰。而是希望提示用戶沒有輸入正確的數字,請再試一次。為此,我們需要寫好調用 parse() 成功時的邏輯,還要寫好調用失敗時的邏輯。類似於逐個處理匹配表達式可能的結果。
分析匹配表達式
match my_string.trim().parse::<f64>() {
Ok(_s) => break _s,
Err(_err) => println!(“Try again. Enter a number.”)
}
首先,我們使用 match 關鍵字來聲明匹配表達式。然后,我們提供與表達式匹配的可能的值。這個值就是下面所示:
my_string.trim().parse::<f64>()
這段代碼接收 my_string 參數,它將用戶輸入的內容保存下來,並提供給 trim() 函數。trim() 函數會刪除掉字符串兩側可能存在的額外空行或空格。我們之所以需要 trim() 是因為 read_line() 函數在輸入中附加了一個額外的空行,這會導致轉換會出現異常。然后將清理了空格字符的 my_string 傳遞到 parse() 函數中,該函數會嘗試將其轉換為浮點數。
如果 parse() 成功地將 my_string 轉換為數字,則返回 Ok。在這個情況下,我們可以得到浮點數。如果用戶輸入的不是數字,那么 parse() 將無法正常完成轉換,它會返回 Err。
在匹配表達式的花括號(主體)中,我們根據 parse() 返回的類型告訴計算機怎么做:
Ok(_s) => break _s,
Err(_err) => println!(“Try again. Enter a number.”)
如果結果是 Ok,則表示 parse() 能夠轉換該類型。這時,我們調用一個 break,停止循環,並返回存儲在 Ok 中的值,這個值會被放在 _s 變量中。
如果結果是 Err,parse() 無法完成轉換。這時,我們會告訴用戶“重試一次。輸入一個數字”。由於我們不調用 break,所以循環重新開始。
如果必須用一句話解釋 Result,那就是:如果一個函數返回 Result,一個匹配表達式可以根據結果是 Ok 還是 Err 來執行不同的代碼。
在你的函數中使用 Result
既然你已經了解了處理 Result 的方法,那么你可能會希望在你自己創建的函數中使用它。
我們先看一個例子。
fn main(){
let my_num = 50;
fn is_it_fifty(num: u32) -> Result<u32, &’static str> {
let error = “It didn’t work”;
if num == 50 {
Ok(num)
} else {
Err(error)
}
}
match is_it_fifty(my_num) {
Ok(_v) => println!(“Good! my_num is 50”),
Err(_e) => println!(“Error. my_num is {:?}”, my_num)
}
}
這個程序檢查 my_num 的值。如果值為 50,則表示成功;如果不是,則表示錯誤。
這段代碼的主體是 is_it_fifty() 函數。它是有返回結果的聲明式函數。我們逐行看其中的代碼。
首先,我們聲明 my_num 並給它賦值。然后,我們聲明 is_it_fifty() 函數:
fn is_it_fifty(num: u32) -> Result<u32, &’static str> {
在我們的聲明中,我們指定該函數接收一個名為 num 的參數,其類型是 32 位無符號整數類型(u32)。接下來,我們指定函數的返回值類型。表示函數會返回一個結果,類型是 u32 或字串(&'static str)
然后,我們編寫 is_it_fifty() 的函數體。
let error = “It didn’t work”;
if num == 50 {
Ok(num)
} else {
Err(error)
}
函數體中的代碼是一個 if else 表達式。它用於判斷傳入的參數。
如果值是 50,那么函數將返回 Ok 的 Result。Ok 中將會包含傳遞給函數的值(num)。
如果參數不是 50,函數將返回 Err 的 Result。Err 會包含錯誤變量的值,也即 “It didn’t work.”
無論何時使用該函數,都必須處理它返回的 Result。在我們的程序中,與大多數 Rust 程序一樣,是通過一個匹配表達式完成的。我在之前已經描述過部分匹配表達式。
Result 類型可以使用 unwrap() 或 expect() 來處理 —— 前面也已經解釋過。
總結
Result 是一個函數的返回類型,它表示函數執行是否成功。
Rust 的許多內置函數都是返回 Result 類型,如果是這樣的話,就沒有辦法避開它。如果一個函數返回 Result,它必須要被妥善處理。
處理 Result 常用的方法是使用 unwrap() 和 _expect() 函數以及匹配表達式。
可以從自己定義的函數中返回 Result。這是處理錯誤的好辦法。
關於 Rust 的 Result 類型,你需要知道的就這些了,但是如果想了解更多信息,或者想知道我從哪兒收集的這些信息,可以參考下方的資源列表。
資源
- https://doc.rust-lang.org/std/result/
- https://doc.rust-lang.org/1.2.0/book/match.html
- 查看
matching on enums
部分
- 查看
- https://doc.rust-lang.org/1.30.0/book/first-edition/error-handling.html
- https://doc.rust-lang.org/rust-by-example/flow_control/match.html
- https://blog.jonstodle.com/things-i-enjoy-in-rust-error-handling/
- https://stevedonovan.github.io/rust-gentle-intro/6-error-handling.html
- https://doc.rust-lang.org/book/ch03-03-how-functions-work.html
- https://doc.rust-lang.org/std/result/enum.Result.html#method.expect