入門Rust的固定套路:錯誤處理模式有三大類,幫你總結(jié)了
最近在學(xué)習(xí)總結(jié)Rust的各種場景的語法模式,也就是Rust寫代碼的模式。
今天分享關(guān)于Rust的錯誤處理的三大類語法模式。
先列出一個大綱
第一類:有意不處理錯誤,忽略錯誤
- unwrap()
- .fn( )? 符號,代替rust早期版本中的try!宏
第二類:對錯誤做自定義信息提示
- 使用expect()。
第三類:推薦!根據(jù)正確和錯誤情況分開處理,錯誤還可以進(jìn)一步分流處理
- match(包括 match處理 Result<T,E>或 match處理Option<>, 或 使用map_err())
- 使用if let Some(value)= fn() {} else {}
- 使用特定的函數(shù):and_then() 和 or_else()
我對Rust的錯誤處理的印象
Rust的錯誤處理方式比起Golang更靈活,可以針對錯誤和當(dāng)下代碼需要賦值前做錯誤判斷、故意忽略異常、異常時打印錯誤并終止等的不同編碼場景,選用不同的語法模式。
錯誤處理涉及到數(shù)據(jù)類型、錯誤處理的控制語法、相關(guān)的crate模塊。
Rust錯誤處理涉及到的數(shù)據(jù)類型
錯誤處理的類型1:Result
Result屬于Rust的核心crate提供的功能(core::result::Result)。Result 有2個特點(diǎn):
首先,Result<T,E> 屬于泛型,所以T和E可以是任意類型。但常見的使用模式是以T存儲正常情況的信息,E存儲錯誤和異常情況的信息。
其次,Result是枚舉類型,其內(nèi)部實(shí)際只包含:
Ok()
Err()
這兩個枚舉,所以它的實(shí)例只會是Ok()或Err()之一。
Result的代碼實(shí)現(xiàn)中就用的枚舉類型的,代碼如下:
/// Result的定義在 rust核心代碼 src/rust/library/core/src/result.rs 代碼文件中:
/// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]).
///
/// See the [module documentation](self "module documentation") for details.
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
#[rustc_diagnostic_item = "Result"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result<T, E> {
/// Contains the success value
#[lang = "Ok"]
#[stable(feature = "rust1", since = "1.0.0")]
Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
/// Contains the error value
#[lang = "Err"]
#[stable(feature = "rust1", since = "1.0.0")]
Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}
實(shí)際使用的一個例子:例如Result<String, &str> 是一個泛型類型,則此時的T類型為String,E類型為&str。它表示一個可能成功或失敗的操作的結(jié)果,其中成功時的返回類型為 String,失敗時的錯誤類型為 &str。
總結(jié)一下Result<Ok(),Err()>: result 類型通常用于表示可能成功或失敗的操作的結(jié)果。它使用 Result<T, E> 類型來表示,其中 T 是成功時的返回類型,E 是失敗時的錯誤類型。
錯誤處理的類型2:Option
Option也是枚舉類型,其內(nèi)部實(shí)際只包含兩種值:Some()和None,這個可以從其代碼中得到印證。Option的實(shí)現(xiàn)代碼為:
/// 代碼文件 rustlib/src/rust/library/core/src/option.rs 定義了Option
/// The `Option` type. See [the module level documentation](self "the module level documentation") for more.
#[derive(Copy, PartialOrd, Eq, Ord, Debug, Hash)]
#[rustc_diagnostic_item = "Option"]
#[lang = "Option"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Option<T> {
/// No value.
#[lang = "None"]
#[stable(feature = "rust1", since = "1.0.0")]
None,
/// Some value of type `T`.
#[lang = "Some"]
#[stable(feature = "rust1", since = "1.0.0")]
Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}
Result 和 Option 有什么區(qū)別?
首先,從名稱上理解一下:
- Result 中文意思是結(jié)果。一般表示成功或失敗,所以它的枚舉值會是Ok()和Err();
- Option,中文意思是選項(xiàng)。那么它的側(cè)重點(diǎn)不是成功失敗,而是不同的選項(xiàng)(有選項(xiàng)則為Some()、無選項(xiàng)則為None),所以它的枚舉值也很清楚,只有Some()和None兩個值。
其次,它們之間可以互相轉(zhuǎn)化。對于錯誤處理來說,match處理Result或Option時,都要處理錯誤或None等異常值,就實(shí)現(xiàn)了Rust的最重要的錯誤處理邏輯。
錯誤處理的控制語法的具體分析和舉例
1.Rust的第一類錯誤處理模式:忽略錯誤,不處理錯誤:
(1) unwrap()
在 Rust 中,unwrap() 方法用于從 Result 類型中提取成功時的返回值。如果 Result 類型的值是 Ok(表示成功),則 unwrap() 方法將返回 T;如果 Result 類型的值是 Err(表示失?。?,則 unwrap() 方法將觸發(fā)一個 panic,拋出一個 E 類型的錯誤。如果您在調(diào)用 unwrap() 方法時遇到錯誤,說明您正在處理一個 Err 類型的值,即失敗的情況。在這種情況下,您應(yīng)該使用其他方法來處理錯誤,而不是直接使用 unwrap() 方法。總之,在處理 Result 類型時,應(yīng)該始終考慮可能的失敗情況,并使用適當(dāng)?shù)姆椒▉硖幚礤e誤。直接使用 unwrap() 方法可能會導(dǎo)致程序崩潰(panic),因此應(yīng)該謹(jǐn)慎使用。只應(yīng)該在非生產(chǎn)的代碼中使用。
(2) .fn()?符號
這個符號在Rust中的術(shù)語是“提前返回選項(xiàng)”(early return option),作用等同于unwrap()。只允許用于返回Result<>或Option<>類型的函數(shù)之后。在rust的早期版本中,有個try!宏具有等效的功能。
代碼演示:(演示打開文件,返回Result<File, Error> 類型的值,然后可以被main()中的match方式處理):
use std::fs::File;
use std::io::Error;
fn open_file(file_path: &str) -> Result<File, Error> {
let mut file = File::open(file_path)?;
Ok(file)
}
這段函數(shù)內(nèi)部使用File::open(file_path)?; 打開指定路徑的文件,open()是rust的內(nèi)部函數(shù),原始定義為:
pub fn open<P>(path: P) -> io::Result<File>
而演示代碼中有意忽略了錯誤的情況,以?結(jié)尾,來調(diào)用open()函數(shù):
let mut file = File::open(file_path)?;
2.Rust的第二類錯誤處理模式:對錯誤做自定義信息提示:
expect() 可作為代替unwrap()或 ? 相比unwrap(), expect()是一個更好的選擇,因?yàn)樗试S發(fā)生錯誤時打印一個簡單的消息并終止運(yùn)行。
3.Rust的第三類錯誤處理模式(推薦?。?/h4>
根據(jù)正確和錯誤情況分開處理,錯誤還可以進(jìn)一步分流處理。
(1) match,根據(jù)正確和錯誤情況分開處理。
- 使用match 分流處理 Result<T,E>中包含錯誤的情況處理:
use std::fs::File;
use std::io::Error;
/// 演示打開文件,返回Result<File, Error> 類型的值,然后被main()中的match方式處理
fn open_file(file_path: &str) -> Result<File, Error> {
let mut file = File::open(file_path)?;
Ok(file)
}
fn main() {
let file_path = "file.txt";
let file = open_file(file_path);
match file {
Ok(file) => println!("文件打開成功 {:?}", file),
Err(error) => println!("文件打開失敗 {}", error),
}
}
- 使用 match 分流處理 Option<>包含錯誤的情況處理:
use std::fs::File;
use std::io::ErrorKind;
use std::io::{Error, Read};
/// 演示文件打開時 如何返回Option<T> 類型值
fn open_file(file_path: &str) -> Option<File> {
let mut file = File::open(file_path).unwrap();
Some(file)
}
/// 演示 match 如何處理 Option<T> 類型值,其中有None類型的情況
fn read_file(file: Option<File>) -> Result<String, Error> {
match file {
//處理文件的正常情況
Some(mut file) => {
let mut buffer = String::new();
let file_content = file.read_to_string(&mut buffer);
Ok(buffer)
}
//處理文件的異常情況
None => Err(Error::new(ErrorKind::NotFound, "File not found")),
}
}
fn main() {
let file_path = "file.txt";
let file = open_file(&file_path);
let strings_in_file = read_file(file);
/// 在文件 file.txt 不存在的情況下,以下代碼會導(dǎo)致軟件崩潰。
println!("{}", strings_in_file.unwrap());
///echo "1111">> file.txt
/// 創(chuàng)建 file.txt文件
/// 然后重復(fù)上面代碼
}
- 使用 map_err() 鏈?zhǔn)教幚?/li>
map_err() 將在以后發(fā)布的文章中再講解。本文不做詳細(xì)介紹。
(2) if let,適合直接在賦值前做錯誤處理。代碼模式為:
let final_value = if let Some(T) = Rust語句 {
//語句正確和成功的情況,如獲取有效數(shù)據(jù),將作為作用域的返回值賦值給final_value
} else {
//錯誤或異常的情況的處理,如賦值為"",同樣會作為作用域的返回值賦值給 final_value
}
以下是一些常見的處理錯誤的方法:使用 match 表達(dá)式:通過使用 match 表達(dá)式,您可以根據(jù) Result 類型的值來執(zhí)行不同的操作。如果是 Ok 類型,可以提取成功的值;如果是 Err 類型,可以處理錯誤。使用 map_err() 方法:map_err() 方法可以將 Err 類型的值轉(zhuǎn)換為另一種錯誤類型,并返回一個新的 Result 類型。使用 and_then() 或 or_else() 方法:這些方法可以在成功或失敗的情況下執(zhí)行不同的操作,并返回一個新的 Result 類型。
(3) 使用特定的函數(shù):and_then() 和 or_else()和 ok_or()
這3個函數(shù)在Rust中的術(shù)語為組合算子,如果你已理解C/C++中的 && 和 ||或 Python中的and以及or語法的意義,那么你大概已經(jīng)理解了 and_then() 這3個函數(shù)的意思。比如 and_then()是當(dāng)調(diào)用者為true或調(diào)用者為正常的時候,才會調(diào)用and_then(...)函數(shù)。那么對于錯誤處理就非常有用。
下面的代碼例子演示了烹飪的邏輯:當(dāng)有食材的時候,才能按照食譜制作好菜品。隱含的意思就是(錯誤的情況下),沒有食材的情況下,就不用照著食譜做菜了。
#![allow(dead_code)]
#[derive(Debug)] enum Food { CordonBleu, Steak, Sushi }
#[derive(Debug)] enum Day { Monday, Tuesday, Wednesday }
// 我們沒有原材料(ingredient)來制作壽司。
fn have_ingredients(food: Food) -> Option<Food> {
match food {
Food::Sushi => None,
_ => Some(food),
}
}
// 我們擁有全部食物的食譜,除了欠缺高超的烹飪手藝。
fn have_recipe(food: Food) -> Option<Food> {
match food {
Food::CordonBleu => None,
_ => Some(food),
}
}
// 做一份好菜,我們需要原材料和食譜這兩者。
// 我們可以借助一系列 `match` 來表達(dá)相應(yīng)的邏輯:
// (原文:We can represent the logic with a chain of `match`es:)
fn cookable_v1(food: Food) -> Option<Food> {
match have_ingredients(food) {
None => None,
Some(food) => match have_recipe(food) {
None => None,
Some(food) => Some(food),
},
}
}
// 這可以使用 `and_then()` 方便重寫出更緊湊的代碼:
fn cookable_v2(food: Food) -> Option<Food> {
have_ingredients(food).and_then(have_recipe)
}
fn eat(food: Food, day: Day) {
match cookable_v2(food) {
Some(food) => println!("Yay! On {:?} we get to eat {:?}.", day, food),
None => println!("Oh no. We don't get to eat on {:?}?", day),
}
}
fn main() {
let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi);
eat(cordon_bleu, Day::Monday);
eat(steak, Day::Tuesday);
eat(sushi, Day::Wednesday);
}