Rust错误处理
Rust錯誤處理
本文同步于Rust中文社區(qū)專欄文章:Rust錯誤處理 ,本文時間:2018-12-14, 譯者:krircc,簡介:天青色,原文出處歡迎向Rust中文社區(qū)投稿,投稿地址 ,好文將在以下地方直接展示
智能編譯器
Rust編譯器最重要的工作是防止Rust程序中的錯誤。如果代碼沒有正確遵循內(nèi)存管理規(guī)則或生命周期注釋,它會在編譯時分析代碼并發(fā)出警告。例如,
#[allow(unused_variables)] //? A lint attribute used to suppress the warning; unused variable: `b` fn main() {let a = vec![1, 2, 3];let b = a;println!("{:?}", a); }// ------ Compile time error ------ error[E0382]: use of moved value: `a`--> src/main.rs:6:22| 3 | let b = a;| - value moved here 4 | 5 | println!("{:?}", a);| ^ value used here after move|= note: move occurs because `a` has type `std::vec::Vec<i32>`, which does not implement the `Copy` traiterror: aborting due to previous error For more information about this error, try `rustc --explain E0382`.// ? instead using #[allow(unused_variables)], consider using "let _b = a;" in line 4. // Also you can use "let _ =" to completely ignore return valuesRust編譯器不僅檢查與生命周期或內(nèi)存管理相關(guān)的問題,還檢查常見的編碼錯誤,如下面的代碼。
struct Color {r: u8,g: u8,b: u8, }fn main() {let yellow = Color {r: 255,g: 255,d: 0,};println!("Yellow = rgb({},{},{})", yellow.r, yellow.g, yellow.b); }// ------------ Compile time error ------------ error[E0560]: struct `Color` has no field named `d`--> src/main.rs:11:9| 11 | d: 0,| ^ field does not exist - did you mean `b`?error: aborting due to previous error For more information about this error, try `rustc --explain E0560`.以上錯誤消息非常具有描述性,我們可以很容易地看出錯誤在哪里。但是,雖然我們無法通過錯誤消息識別問題,但rustc --explain 命令通過顯示表達相同問題的簡單代碼示例以及我們必須使用的解決方案來幫助我們識別錯誤類型以及如何解決它。例如,在控制臺中顯示以下輸出。rustc --explain E0571
// A `break` statement with an argument appeared in a non-`loop` loop.// Example of erroneous code:let result = while true {if satisfied(i) {break 2*i; // error: `break` with value from a `while` loop}i += 1; };// The `break` statement can take an argument (which will be the value of the loop expression if the `break` statement is executed) in `loop` loops, but not `for`, `while`, or `while let` loops.Make sure `break value;` statements only occur in `loop` loops: let result = loop { // ok!if satisfied(i) {break 2*i;}i += 1; };?您也可以通過Rust Compiler Error Index閱讀相同的解釋 。例如,要檢查E0571錯誤的解釋,您可以使用https://doc.rust-lang.org/error-index.html#E0571
Panicking
panic!()
? 在某些情況下,當(dāng)發(fā)生錯誤時,我們無法做任何事情來處理它,如果錯誤是某種情況,那就不應(yīng)該發(fā)生。換句話說,如果這是一個不可恢復(fù)的錯誤。
? 當(dāng)我們不使用功能豐富的調(diào)試器或正確的日志時,有時我們需要通過打印特定的消息或變量綁定的值從特定的代碼行退出程序來調(diào)試代碼以了解當(dāng)前的程序的流程。
對于上述情況,我們可以使用panic!宏。讓我們看幾個例子。
? panic!() 運行基于線程。一個線程可能會被恐慌,而其他線程正在運行。01.從特定行退出。
fn main() {// some code// if we need to debug in herepanic!(); }// -------------- Compile time error -------------- thread 'main' panicked at 'explicit panic', src/main.rs:5:502.退出并顯示自定義錯誤消息。
#[allow(unused_mut)] // ? A lint attribute used to suppress the warning; username variable does not need to be mutable fn main() {let mut username = String::new();// some code to get the nameif username.is_empty() {panic!("Username is empty!");}println!("{}", username); }// -------------- Compile time error -------------- thread 'main' panicked at 'Username is empty!', src/main.rs:8:903.退出附帶代碼元素的值。
#[derive(Debug)] // ? A lint attribute which use to implement `std::fmt::Debug` to Color struct Color {r: u8,g: u8,b: u8, }#[allow(unreachable_code)] // ? A lint attribute used to suppress the warning; unreachable statement fn main() {let some_color: Color;// some code to get the color. exsome_color = Color {r: 255, g: 255, b: 0};// if we need to debug in herepanic!("{:?}", some_color);println!("The color = rgb({},{},{})",some_color.r, some_color.g, some_color.b); }// -------------- Compile time error -------------- thread 'main' panicked at 'Color { r: 255, g: 255, b: 0 }', src/main.rs:16:5正如您在上面的示例中所看到的,panic!()支持println!()類型樣式參數(shù) 。默認情況下,它會輸出錯誤消息,文件路徑以及發(fā)生錯誤的行號和列號。
unimplemented!()
如果您的代碼具有未完成的代碼段,則有一個標(biāo)準化宏unimplemented!()來標(biāo)記這些路徑。如果程序通過這些路徑運行,程序?qū)anicked并返回"not yet implemented"的錯誤消息。
// error messages with panic!() thread 'main' panicked at 'explicit panic', src/main.rs:6:5 thread 'main' panicked at 'Username is empty!', src/main.rs:9:9 thread 'main' panicked at 'Color { r: 255, g: 255, b: 0 }', src/main.rs:17:5// error messages with unimplemented!() thread 'main' panicked at 'not yet implemented', src/main.rs:6:5 thread 'main' panicked at 'not yet implemented: Username is empty!', src/main.rs:9:9 thread 'main' panicked at 'not yet implemented: Color { r: 255, g: 255, b: 0 }', src/main.rs:17:5unreachable!()
這是標(biāo)記程序不應(yīng)輸入的路徑的標(biāo)準宏。如果程序進入這些路徑,程序?qū)anicked并返回"'internal error: entered unreachable code'"錯誤消息。
fn main() {let level = 22;let stage = match level {1...5 => "beginner",6...10 => "intermediate",11...20 => "expert",_ => unreachable!(),};println!("{}", stage); }// -------------- Compile time error -------------- thread 'main' panicked at 'internal error: entered unreachable code', src/main.rs:7:20我們也可以為此設(shè)置自定義錯誤消息。
// --- with a custom message --- _ => unreachable!("Custom message"), // -------------- Compile time error -------------- thread 'main' panicked at 'internal error: entered unreachable code: Custom message', src/main.rs:7:20// --- with debug data --- _ => unreachable!("level is {}", level), // -------------- Compile time error -------------- thread 'main' panicked at 'internal error: entered unreachable code: level is 22', src/main.rs:7:14assert!(), assert_eq!(), assert_ne!()
這些是標(biāo)準宏,通常與測試斷言一起使用。
- assert!()確保布爾表達式為true。如果表達式為false,則會發(fā)生panics。
- assert_eq!()確保兩個表達式相等。如果表達式不相等則會發(fā)生panics。
- assert_ne!()確保兩個表達式不相等。如果表達式相等,它會發(fā)生panics。
我們也可以為這些宏設(shè)置自定義錯誤消息。舉些例子,
debug_assert!(), debug_assert_eq!(), debug_assert_ne!()
?這些與上面的assert宏類似。但默認情況下,這些語句僅在非優(yōu)化構(gòu)建中啟用。debug_assert除非我們傳遞-C debug-assertions給編譯器,否則在發(fā)布版本中將省略所有這些宏。
Option and Result
許多語言使用null\ nil\ undefined 類型來表示空輸出和Exceptions處理錯誤。Rust會同時使用兩者,特別是為了防止諸如空指針異常,異常等敏感數(shù)據(jù)泄漏等問題。相反,Rust提供了兩個特殊的通用枚舉 ; Option和Result處理上述案件。
如您所知:
? Option可以包含某個值Some或沒有值/ None。
? Result可以表示成功/ Ok 或失敗/Err。
// An output can have either Some value or no value/ None. enum Option<T> { // T is a generic and it can contain any type of value.Some(T),None, }// A result can represent either success/ Ok or failure/ Err. enum Result<T, E> { // T and E are generics. T can contain any type of value, E can be any error.Ok(T),Err(E), }Option的基本用法
編寫函數(shù)或數(shù)據(jù)類型時:
- 如果函數(shù)的參數(shù)是可選的,
- 如果函數(shù)為非空,并且返回的輸出可以為空,
- 如果數(shù)據(jù)類型的屬性的值可以是空,我們不得不使用他們的數(shù)據(jù)類型為Option類型
例如,如果函數(shù)輸出一個&str值并且輸出可以為空,則函數(shù)的返回類型應(yīng)設(shè)置為Option<&str>
fn get_an_optional_value() -> Option<&str> {//if the optional value is not emptyreturn Some("Some value");//elseNone }同樣,如果數(shù)據(jù)類型的屬性值可以為空或者像下面示例中middle_name的Name數(shù)據(jù)類型那樣可選,我們應(yīng)該將其數(shù)據(jù)類型設(shè)置為Option類型。
struct Name {first_name: String,middle_name: Option<String>, // middle_name can be emptylast_name: String, }?如您所知,我們可以使用模式匹配match來捕獲相關(guān)的返回類型(Some/ None) 。有一個函數(shù)來獲取當(dāng)前用戶的主目錄在std::env為home_dir() 。由于所有用戶在Linux等系統(tǒng)中都沒有主目錄,因此用戶的主目錄可以是可選的。所以它返回一個Option類型; Option<PathBuf>.
use std::env;fn main() {let home_path = env::home_dir();match home_path {Some(p) => println!("{:?}", p), // This prints "/root", if you run this in Rust playgroundNone => println!("Can not find the home directory!"),} }?但是,當(dāng)在函數(shù)中使用可選參數(shù)時,我們必須None在調(diào)用函數(shù)時傳遞空參數(shù)的值。
fn get_full_name(fname: &str, lname: &str, mname: Option<&str>) -> String { // middle name can be emptymatch mname {Some(n) => format!("{} {} {}", fname, n, lname),None => format!("{} {}", fname, lname),} }fn main() {println!("{}", get_full_name("Galileo", "Galilei", None));println!("{}", get_full_name("Leonardo", "Vinci", Some("Da"))); }// ? Better create a struct as Person with fname, lname, mname fields and create a impl function as full_name() ?除此之外,Option類型與Rust中的可空指針一起使用。由于Rust中沒有空指針,因此指針類型應(yīng)指向有效位置。因此,如果指針可以為空,我們就可以使用了Option<Box<T>> 。Result的基本用法
如果函數(shù)可以產(chǎn)生錯誤,我們必須Result通過組合有效輸出的數(shù)據(jù)類型和錯誤的數(shù)據(jù)類型來使用類型。例如,如果有效輸出的數(shù)據(jù)類型為u64且錯誤類型為String ,則返回類型應(yīng)為Result<u64, String> 。
fn function_with_error() -> Result<u64, String> {//if error happensreturn Err("The error message".to_string());// else, return valid outputOk(255) }?如您所知,我們可以使用模式匹配match來捕獲相關(guān)的返回類型(Ok/ Err)。有一個函數(shù)可以獲取std::env 任何環(huán)境變量中的值是var() 。它的輸入是環(huán)境變量名稱。如果我們傳遞了錯誤的環(huán)境變量,或者程序在運行時無法提取環(huán)境變量的值,則會產(chǎn)生錯誤。所以它的返回類型是一種Result類型; Result<String, VarError>.
use std::env;fn main() {let key = "HOME";match env::var(key) {Ok(v) => println!("{}", v), // This prints "/root", if you run this in Rust playgroundErr(e) => println!("{}", e), // This prints "environment variable not found", if you give a nonexistent environment variable} }is_some(), is_none(), is_ok(), is_err()
除了match表情,rust還提供is_some() ,is_none()并且is_ok() ,is_err()功能,以確定返回類型。
fn main() {let x: Option<&str> = Some("Hello, world!");assert_eq!(x.is_some(), true);assert_eq!(x.is_none(), false);let y: Result<i8, &str> = Ok(10);assert_eq!(y.is_ok(), true);assert_eq!(y.is_err(), false); }ok(), err() for Result types
rust另外提供ok()和err()為Result類型。它們將Result類型的Ok<T>值和Err<E>值轉(zhuǎn)換為Option類型。
fn main() {let o: Result<i8, &str> = Ok(8);let e: Result<i8, &str> = Err("message");assert_eq!(o.ok(), Some(8)); // Ok(v) ok = Some(v)assert_eq!(e.ok(), None); // Err(v) ok = Noneassert_eq!(o.err(), None); // Ok(v) err = Noneassert_eq!(e.err(), Some("message")); // Err(v) err = Some(v) }Unwrap and Expect
unwrap()
?如果Option類型具有Some值或Result類型具有Ok值,則其中的值將傳遞到下一步。
?如果Option類型具有None值或Result類型具有Err值,則編程panics ; 如果Err,panics攜帶錯誤消息。
該功能與以下代碼類似,使用match而不是使用unwrap() 。
示例使用Option和match
fn main() {let x;match get_an_optional_value() {Some(v) => x = v, // if Some("abc"), set x to "abc"None => panic!(), // if None, panic without any message}println!("{}", x); // "abc" ; if you change line 14 `false` to `true` }fn get_an_optional_value() -> Option<&'static str> {//if the optional value is not emptyif false {return Some("abc");}//elseNone }// --------------- Compile time error --------------- thread 'main' panicked at 'explicit panic', src/main.rs:5:17示例使用Result和match
fn main() {let x;match function_with_error() {Ok(v) => x = v, // if Ok(255), set x to 255Err(e) => panic!(e), // if Err("some message"), panic with error message "some message"}println!("{}", x); // 255 ; if you change line 13 `true` to `false` }fn function_with_error() -> Result<u64, String> {//if error happensif true {return Err("some message".to_string());}// else, return valid outputOk(255) }// ---------- Compile time error ---------- thread 'main' panicked at 'some message', src/main.rs:5:19上述main函數(shù)中的相同代碼可以使用unwrap()兩行來編寫。
// 01. unwrap error message for None fn main() {let x = get_an_optional_value().unwrap();println!("{}", x); }// --------------- Compile time error --------------- thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', libcore/option.rs:345:21// 02. unwrap error message for Err fn main() {let x = function_with_error().unwrap();println!("{}", x); }// --------------- Compile time error --------------- thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "some message"', libcore/result.rs:945:5?但是正如您所看到的,當(dāng)使用unwrap()錯誤消息時,沒有顯示發(fā)生恐慌的確切行號。
expect()
類似unwrap()但可以為恐慌設(shè)置自定義消息。
// 01. expect error message for None fn main() {let n: Option<i8> = None;n.expect("empty value returned"); }// --------------- Compile time error --------------- thread 'main' panicked at 'empty value returned', libcore/option.rs:989:5// 02. expect error message for Err fn main() {let e: Result<i8, &str> = Err("some message");e.expect("expect error message"); }// --------------- Compile time error --------------- thread 'main' panicked at 'expect error message: "some message"', libcore/result.rs:945:5unwrap_err() and expect_err() for Result types
unwrap()和expect()相反的情況; Ok時恐慌而不是Err時。兩者都在Ok錯誤消息中打印內(nèi)部值。
?通常用于測試。
// 01. unwrap_err error message for Ok fn main() {let o: Result<i8, &str> = Ok(8);o.unwrap_err(); }// ---------- Compile time error ---------- thread 'main' panicked at 'called `Result::unwrap_err()` on an `Ok` value: 8', libcore/result.rs:945:5// 02. expect_err error message for Ok fn main() {let o: Result<i8, &str> = Ok(8);o.expect_err("Should not get Ok value"); }// ---------- Compile time error ---------- thread 'main' panicked at 'Should not get Ok value: 8', libcore/result.rs:945:5unwrap_or(), unwrap_or_default() and unwrap_or_else()
?這些有點類似于unwrap(),如果Option類型有Some值或Result類型有Ok值,則它們內(nèi)部的值傳遞到下一步。但是當(dāng)有None 或者 Err,功能有點不同。- unwrap_or() :使用None或Err,您傳遞給的unwrap_or()值將傳遞到下一步。但是,您傳遞的值的數(shù)據(jù)類型應(yīng)與相關(guān)Some或Ok的數(shù)據(jù)類型匹配。
- unwrap_or_default() :使用None或Err,相關(guān)的數(shù)據(jù)類型的默認值Some或者Ok,傳遞到下一步。
- unwrap_or_else() :類似于unwrap_or()。唯一的區(qū)別是,您必須傳遞一個閉包,它返回一個具有Some或Ok相關(guān)數(shù)據(jù)類型的值,而不是傳遞一個值。
Error and None Propagation
我們應(yīng)該使用恐慌panic!(),unwrap(),expect()只有當(dāng)我們沒有一個更好處理辦法的情況。此外如果一個函數(shù)包含表達式既能產(chǎn)生None也能產(chǎn)生Err,
?我們可以在同一函數(shù)中處理
?我們可以立即返回None 和Err給調(diào)用者。因此調(diào)用者可以決定如何處理它們。
? None類型無需始終由函數(shù)的調(diào)用者處理。但Rusts處理Err類型的約定是,立即將它們返回給調(diào)用者,以便給調(diào)用者更多的控制權(quán)來決定如何處理它們。
?操作符
?如果Option類型具有Some值或Result類型具有Ok值,則其中的值將傳遞到下一步。
?如果Option類型具有None值或Result類型具有Err值,則立即將它們返回給函數(shù)的調(diào)用者。
示例Option類型,
fn main() {if complex_function().is_none() {println!("X not exists!");} }fn complex_function() -> Option<&'static str> {let x = get_an_optional_value()?; // if None, returns immidiately; if Some("abc"), set x to "abc"// some other code, exprintln!("{}", x); // "abc" ; if you change line 19 `false` to `true` Some("") }fn get_an_optional_value() -> Option<&'static str> {//if the optional value is not emptyif false {return Some("abc");}//elseNone }示例Result類型,
fn main() {// `main` function is the caller of `complex_function` function// So we handle errors of complex_function(), inside main()if complex_function().is_err() {println!("Can not calculate X!");} }fn complex_function() -> Result<u64, String> {let x = function_with_error()?; // if Err, returns immidiately; if Ok(255), set x to 255// some other code, exprintln!("{}", x); // 255 ; if you change line 20 `true` to `false`Ok(0) }fn function_with_error() -> Result<u64, String> {//if error happensif true {return Err("some message".to_string());}// else, return valid outputOk(255) }從main()傳播錯誤
在Rust版本1.26之前,我們無法從main()函數(shù)傳播Result和Option。但是現(xiàn)在,我們可以從main()函數(shù)中傳播Result類型,并打印出Err的Debug表示形式。
use std::fs::File;fn main() -> std::io::Result<()> {let _ = File::open("not-existing-file.txt")?;Ok(()) // Because of the default return value of Rust functions is an empty tuple/ () }// Because of the program can not find not-existing-file.txt , it produces, // Err(Os { code: 2, kind: NotFound, message: "No such file or directory" }) // While propagating error, the program prints, // Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }Combinators
讓我們看看組合器是什么,
- “組合者”的一個含義是更加非正式的意義,指的是組合模式,一種以組合事物的思想為中心組織圖書館的風(fēng)格。通常存在一些類型T,一些用于構(gòu)造類型T的“原始”值的函數(shù),以及一些可以以各種方式組合類型T的值以構(gòu)建類型T的更復(fù)雜值的 “ 組合器 ” 。另一個定義是“ 沒有自由變量的函數(shù) ”(wiki.haskell.org)
- 組合子是一個函數(shù),其從程序片段構(gòu)建程序片段 ; 從某種意義上說,使用組合器的程序員自動構(gòu)建了大部分所需的程序,而不是手工編寫每個細節(jié)。
Rust生態(tài)系統(tǒng)中“組合子”的確切定義有點不清楚。
? or(),and(),or_else(),and_then()
- 組合類型為T的兩個值并返回相同類型T。
? filter()對于Option類型
- 使用閉包作為條件函數(shù)來過濾類型T.
- 返回相同的類型T.
? map(),map_err()
- 通過使用閉包轉(zhuǎn)換類型T。
- 可以更改T內(nèi)部值的數(shù)據(jù)類型。例如:Some<&str>可轉(zhuǎn)化為Some<usize>或者Err<&str>可轉(zhuǎn)化為Err<isize>等
? map_or(),map_or_else()
- 通過應(yīng)用閉包轉(zhuǎn)換類型T并返回類型T內(nèi)的值。
- 對None 和Err,應(yīng)用默認值或其他閉包。
? ok_or(),ok_or_else()對于Option類型
- 將Option類型轉(zhuǎn)換為Result類型。
? as_ref(),as_mut()
- 將類型T轉(zhuǎn)換為引用或可變引用。
or() and and()
組合兩個表達式返回Option/ Result
?or() :如果任何一個得到Some或Ok`,該值立即返回。
? and() :如果兩者都得到Some或Ok,則返回第二個表達式中的值。如果任何一個獲得None或Err該值立即返回。
fn main() {let s1 = Some("some1");let s2 = Some("some2");let n: Option<&str> = None;let o1: Result<&str, &str> = Ok("ok1");let o2: Result<&str, &str> = Ok("ok2");let e1: Result<&str, &str> = Err("error1");let e2: Result<&str, &str> = Err("error2");assert_eq!(s1.or(s2), s1); // Some1 or Some2 = Some1assert_eq!(s1.or(n), s1); // Some or None = Someassert_eq!(n.or(s1), s1); // None or Some = Someassert_eq!(n.or(n), n); // None1 or None2 = None2assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 = Ok1assert_eq!(o1.or(e1), o1); // Ok or Err = Okassert_eq!(e1.or(o1), o1); // Err or Ok = Okassert_eq!(e1.or(e2), e2); // Err1 or Err2 = Err2assert_eq!(s1.and(s2), s2); // Some1 and Some2 = Some2assert_eq!(s1.and(n), n); // Some and None = Noneassert_eq!(n.and(s1), n); // None and Some = Noneassert_eq!(n.and(n), n); // None1 and None2 = None1assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 = Ok2assert_eq!(o1.and(e1), e1); // Ok and Err = Errassert_eq!(e1.and(o1), e1); // Err and Ok = Errassert_eq!(e1.and(e2), e1); // Err1 and Err2 = Err1 } ?nightly支持Option類型的xor(),它返回Some當(dāng)只有一個表達式返回Some,而不是兩個。or_else()
類似于or()。唯一的區(qū)別是,第二個表達式應(yīng)該是一個返回相同類型T 的閉包。
fn main() {// or_else with Optionlet s1 = Some("some1");let s2 = Some("some2");let fn_some = || Some("some2"); // similar to: let fn_some = || -> Option<&str> { Some("some2") };let n: Option<&str> = None;let fn_none = || None;assert_eq!(s1.or_else(fn_some), s1); // Some1 or_else Some2 = Some1assert_eq!(s1.or_else(fn_none), s1); // Some or_else None = Someassert_eq!(n.or_else(fn_some), s2); // None or_else Some = Someassert_eq!(n.or_else(fn_none), None); // None1 or_else None2 = None2// or_else with Resultlet o1: Result<&str, &str> = Ok("ok1");let o2: Result<&str, &str> = Ok("ok2");let fn_ok = |_| Ok("ok2"); // similar to: let fn_ok = |_| -> Result<&str, &str> { Ok("ok2") };let e1: Result<&str, &str> = Err("error1");let e2: Result<&str, &str> = Err("error2");let fn_err = |_| Err("error2");assert_eq!(o1.or_else(fn_ok), o1); // Ok1 or_else Ok2 = Ok1assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err = Okassert_eq!(e1.or_else(fn_ok), o2); // Err or_else Ok = Okassert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 = Err2 }and_then()
類似于and()。唯一的區(qū)別是,第二個表達式應(yīng)該是一個返回相同類型T 的閉包。
fn main() {// and_then with Optionlet s1 = Some("some1");let s2 = Some("some2");let fn_some = |_| Some("some2"); // similar to: let fn_some = |_| -> Option<&str> { Some("some2") };let n: Option<&str> = None;let fn_none = |_| None;assert_eq!(s1.and_then(fn_some), s2); // Some1 and_then Some2 = Some2assert_eq!(s1.and_then(fn_none), n); // Some and_then None = Noneassert_eq!(n.and_then(fn_some), n); // None and_then Some = Noneassert_eq!(n.and_then(fn_none), n); // None1 and_then None2 = None1// and_then with Resultlet o1: Result<&str, &str> = Ok("ok1");let o2: Result<&str, &str> = Ok("ok2");let fn_ok = |_| Ok("ok2"); // similar to: let fn_ok = |_| -> Result<&str, &str> { Ok("ok2") };let e1: Result<&str, &str> = Err("error1");let e2: Result<&str, &str> = Err("error2");let fn_err = |_| Err("error2");assert_eq!(o1.and_then(fn_ok), o2); // Ok1 and_then Ok2 = Ok2assert_eq!(o1.and_then(fn_err), e2); // Ok and_then Err = Errassert_eq!(e1.and_then(fn_ok), e1); // Err and_then Ok = Errassert_eq!(e1.and_then(fn_err), e1); // Err1 and_then Err2 = Err1 }filter()
?通常在編程語言中,filter函數(shù)與數(shù)組或迭代器一起使用,通過函數(shù)/閉包過濾自己的元素來創(chuàng)建新的數(shù)組/迭代器。Rust還提供了一個filter()迭代器適配器,用于在迭代器的每個元素上應(yīng)用閉包,將其轉(zhuǎn)換為另一個迭代器。然而,在這里,我們正在談?wù)揻ilter()函數(shù)與Option類型。僅當(dāng)我們傳遞一個Some值并且給定的閉包為它返回true時,返回相同的Some類型。如果None傳遞類型或閉包返回false,返回None。閉包使用Some里面的值作為參數(shù)。Rust仍然支持filter()只支持Option的類型。
fn main() {let s1 = Some(3);let s2 = Some(6);let n = None;let fn_is_even = |x: &i8| x % 2 == 0;assert_eq!(s1.filter(fn_is_even), n); // Some(3) -> 3 is not even -> Noneassert_eq!(s2.filter(fn_is_even), s2); // Some(6) -> 6 is even -> Some(6)assert_eq!(n.filter(fn_is_even), n); // None -> no value -> None }map() and map_err()
?通常在編程語言中,map()函數(shù)與數(shù)組或迭代器一起使用,以在數(shù)組或迭代器的每個元素上應(yīng)用閉包。Rust還提供了一個map()迭代器適配器,用于在迭代器的每個元素上應(yīng)用閉包,將其轉(zhuǎn)換為另一個迭代器。但是在這里我們討論的是map()函數(shù)與Option和Result類型。- map() :通過應(yīng)用閉包來轉(zhuǎn)換類型T. 可以根據(jù)閉包的返回類型更改Some或Ok塊數(shù)據(jù)類型。轉(zhuǎn)換Option<T>為Option<U> ,轉(zhuǎn)換Result<T, E>為Result<U, E>
? map(),僅僅 Some和Ok值改變。對Err內(nèi)部值沒有影響(None根本不包含任何值)。
fn main() {let s1 = Some("abcde");let s2 = Some(5);let n1: Option<&str> = None;let n2: Option<usize> = None;let o1: Result<&str, &str> = Ok("abcde");let o2: Result<usize, &str> = Ok(5);let e1: Result<&str, &str> = Err("abcde");let e2: Result<usize, &str> = Err("abcde");let fn_character_count = |s: &str| s.chars().count();assert_eq!(s1.map(fn_character_count), s2); // Some1 map = Some2assert_eq!(n1.map(fn_character_count), n2); // None1 map = None2assert_eq!(o1.map(fn_character_count), o2); // Ok1 map = Ok2assert_eq!(e1.map(fn_character_count), e2); // Err1 map = Err2 }- map_err()對于Result類型:Err塊的數(shù)據(jù)類型可以根據(jù)閉包的返回類型進行更改。轉(zhuǎn)換Result<T, E>為Result<T, F>。
?map_err(),只有Err值會發(fā)生變化。對Ok內(nèi)部的值沒有影響。
fn main() {let o1: Result<&str, &str> = Ok("abcde");let o2: Result<&str, isize> = Ok("abcde");let e1: Result<&str, &str> = Err("404");let e2: Result<&str, isize> = Err(404);let fn_character_count = |s: &str| -> isize { s.parse().unwrap() }; // convert str to isizeassert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map = Ok2assert_eq!(e1.map_err(fn_character_count), e2); // Err1 map = Err2 }map_or() and map_or_else()
這些功能也與unwrap_or()和unwrap_or_else()相似。但是map_or()和map_or_else()在Some,Ok值上應(yīng)用閉包和返回類型T內(nèi)的值。
- map_or() :僅支持Option類型(不支持Result)。將閉包應(yīng)用于Some內(nèi)部值并根據(jù)閉包返回輸出。為None類型返回給定的默認值。
- map_or_else() :支持兩種Option和Result類型(Result僅限nightly)。類似map_or()但應(yīng)該提供另一個閉包而不是第一個參數(shù)的默認值。
? None類型不包含任何值。所以不需要將任何東西傳遞給閉包作為輸入Option類型。但是Err類型在其中包含一些值。因此,默認閉包應(yīng)該能夠?qū)⑵渥鳛檩斎胱x取,同時將其與Result類型一起使用。
#![feature(result_map_or_else)] // enable unstable library feature 'result_map_or_else' on nightly fn main() {let s = Some(10);let n: Option<i8> = None;let fn_closure = |v: i8| v + 2;let fn_default = || 1; // None doesn't contain any value. So no need to pass anything to closure as input.assert_eq!(s.map_or_else(fn_default, fn_closure), 12);assert_eq!(n.map_or_else(fn_default, fn_closure), 1);let o = Ok(10);let e = Err(5);let fn_default_for_result = |v: i8| v + 1; // Err contain some value inside it. So default closure should able to read it as inputassert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6); }ok_or() and ok_or_else()
如前所述ok_or(),ok_or_else()將Option類型轉(zhuǎn)換為Result類型。Some對Ok和None對Err 。
- ok_or() :默認Err消息應(yīng)作為參數(shù)傳遞.
- ok_or_else() :類似于ok_or()。應(yīng)該將閉包作為參數(shù)傳遞。
as_ref() and as_mut()
?如前所述,這些函數(shù)用于借用類型T作為引用或作為可變引用。
- as_ref() :轉(zhuǎn)換Option<T>到Option<&T>和Result<T, E>到Result<&T, &E>
- as_mut() :轉(zhuǎn)換Option<T>到Option<&mut T>和Result<T, E>到Result<&mut T, &mut E>
自定義錯誤類型
Rust允許我們創(chuàng)建自己的Err類型。我們稱之為“ 自定義錯誤類型”。
Error trait
如您所知,traits定義了類型必須提供的功能。但是我們不需要總是為常用功能定義新的特性,因為Rust 標(biāo)準庫提供了一些可以在我們自己的類型上實現(xiàn)的可重用特性。創(chuàng)建自定義錯誤類型時,std::error::Error trait可幫助我們將任何類型轉(zhuǎn)換為Err類型。
use std::fmt::{Debug, Display};pub trait Error: Debug + Display {fn source(&self) -> Option<&(Error + 'static)> { ... } }一個特質(zhì)可以從另一個特質(zhì)繼承。trait Error: Debug + Display意味著Error特質(zhì)繼承fmt::Debug和fmt::Display特質(zhì)。
// traits inside Rust standard library core fmt module/ std::fmt pub trait Display {fn fmt(&self, f: &mut Formatter) -> Result<(), Error>; }pub trait Debug {fn fmt(&self, f: &mut Formatter) -> Result<(), Error>; }? Display
- 最終用戶應(yīng)如何將此錯誤視為面向消息/面向用戶的輸出。
- 通常通過println!("{}")或打印eprintln!("{}")
? Debug
- 如何顯示Errwhile調(diào)試/面向程序員的輸出。
- 通常打印println!("{:?}")或eprintln!("{:?}")
- 漂亮打印,可以使用println!("{:#?}")或eprintln!("{:#?}")。
? source()
- 此錯誤的較低級別來源(如果有)。
- 可選的。
首先,讓我們看看如何std::error::Error在最簡單的自定義錯誤類型上實現(xiàn)特征。
use std::fmt;// Custom error type; can be any type which defined in the current crate // ? In here, we use a simple "unit struct" to simplify the example struct AppError;// Implement std::fmt::Display for AppError impl fmt::Display for AppError {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "An Error Occurred, Please Try Again!") // user-facing output} }// Implement std::fmt::Debug for AppError impl fmt::Debug for AppError {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "{{ file: {}, line: {} }}", file!(), line!()) // programmer-facing output} }// A sample function to produce an AppError Err fn produce_error() -> Result<(), AppError> {Err(AppError) }fn main() {match produce_error() {Err(e) => eprintln!("{}", e), // An Error Occurred, Please Try Again!_ => println!("No error"),}eprintln!("{:?}", produce_error()); // Err({ file: src/main.rs, line: 17 }) }希望你理解要點。現(xiàn)在,讓我們看一些帶有錯誤代碼和錯誤消息的自定義錯誤類型。
use std::fmt;struct AppError {code: usize,message: String, }// Different error messages according to AppError.code impl fmt::Display for AppError {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {let err_msg = match self.code {404 => "Sorry, Can not find the Page!",_ => "Sorry, something is wrong! Please Try Again!",};write!(f, "{}", err_msg)} }// A unique format for dubugging output impl fmt::Debug for AppError {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f,"AppError {{ code: {}, message: {} }}",self.code, self.message)} }fn produce_error() -> Result<(), AppError> {Err(AppError {code: 404,message: String::from("Page not found"),}) }fn main() {match produce_error() {Err(e) => eprintln!("{}", e), // Sorry, Can not find the Page!_ => println!("No error"),}eprintln!("{:?}", produce_error()); // Err(AppError { code: 404, message: Page not found })eprintln!("{:#?}", produce_error());// Err(// AppError { code: 404, message: Page not found }// ) }??Rust標(biāo)準庫不僅提供了可重用的特性,而且還有助于通過#[derive]屬性神奇地生成少數(shù)特征的實現(xiàn)。Rust支持derive std::fmt::Debug,為調(diào)試消息提供默認格式。因此,我們可以在struct前聲明使用#[derive(Debug)]跳過實現(xiàn)std::fmt::Debug 自定義錯誤類型。
use std::fmt;#[derive(Debug)] // derive std::fmt::Debug on AppError struct AppError {code: usize,message: String, }impl fmt::Display for AppError {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {let err_msg = match self.code {404 => "Sorry, Can not find the Page!",_ => "Sorry, something is wrong! Please Try Again!",};write!(f, "{}", err_msg)} }fn produce_error() -> Result<(), AppError> {Err(AppError {code: 404,message: String::from("Page not found"),}) }fn main() {match produce_error() {Err(e) => eprintln!("{}", e), // Sorry, Can not find the Page!_ => println!("No error"),}eprintln!("{:?}", produce_error()); // Err(AppError { code: 404, message: Page not found })eprintln!("{:#?}", produce_error());// Err(// AppError {// code: 404,// message: "Page not found"// }// ) }From trait
在編寫真實的程序時,我們大多數(shù)時候必須同時處理不同的模塊,不同的std和第三方的板條箱。但是每個包都使用自己的錯誤類型,如果我們使用自己的錯誤類型,我們應(yīng)該將這些錯誤轉(zhuǎn)換為錯誤類型。我們可以使用std::convert::From標(biāo)準化特征進行這些轉(zhuǎn)換。
// traits inside Rust standard library core convert module/ std::convert pub trait From<T>: Sized {fn from(_: T) -> Self; } ?如您所知,String::from()函數(shù)用于創(chuàng)建String from &str數(shù)據(jù)類型。實際上這也是std::convert::From特質(zhì)的實現(xiàn)。讓我們看看如何在自定義錯誤類型上實現(xiàn)std::convert::From特征。
use std::fs::File; use std::io;#[derive(Debug)] struct AppError {kind: String, // type of the errormessage: String, // error message }// Implement std::convert::From for AppError; from io::Error impl From<io::Error> for AppError {fn from(error: io::Error) -> Self {AppError {kind: String::from("io"),message: error.to_string(),}} }fn main() -> Result<(), AppError> {let _file = File::open("nonexistent_file.txt")?; // This generates an io::Error. But because of return type is Result<(), AppError>, it converts to AppErrorOk(()) }// --------------- Run time error --------------- Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }在上面的例子中,File::open(“nonexistent.txt”)?產(chǎn)生std::io::Error。但由于返回類型是Result<(), AppError>,它轉(zhuǎn)換為AppError。因為我們正在從main()函數(shù)傳播錯誤,所以它會打印出Err的Debug表示形式。
在上面的例子中,我們只處理一種std錯誤類型std::io::Error。讓我們看一些處理多種std錯誤類型的例子。
use std::fs::File; use std::io::{self, Read}; use std::num;#[derive(Debug)] struct AppError {kind: String,message: String, }// Implement std::convert::From for AppError; from io::Error impl From<io::Error> for AppError {fn from(error: io::Error) -> Self {AppError {kind: String::from("io"),message: error.to_string(),}} }// Implement std::convert::From for AppError; from num::ParseIntError impl From<num::ParseIntError> for AppError {fn from(error: num::ParseIntError) -> Self {AppError {kind: String::from("parse"),message: error.to_string(),}} }fn main() -> Result<(), AppError> {let mut file = File::open("hello_world.txt")?; // generates an io::Error, if can not open the file and converts to an AppErrorlet mut content = String::new();file.read_to_string(&mut content)?; // generates an io::Error, if can not read file content and converts to an AppErrorlet _number: usize;_number = content.parse()?; // generates num::ParseIntError, if can not convert file content to usize and converts to an AppErrorOk(()) }// --------------- Few possible run time errors ---------------// 01. If hello_world.txt is a nonexistent file Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }// 02. If user doesn't have relevant permission to access hello_world.txt Error: AppError { kind: "io", message: "Permission denied (os error 13)" }// 03. If hello_world.txt contains non-numeric content. ex Hello, world! Error: AppError { kind: "parse", message: "invalid digit found in string" } ? 搜索有關(guān)實現(xiàn)的內(nèi)容std::io::ErrorKind,以了解如何進一步組織錯誤類型。總結(jié)
- 上一篇: Java中可变长参数的使用及注意事项
- 下一篇: 二进制-八进制-十进制-16进制之间的转