Rust錯誤處理
Rust錯誤處理
本文同步於ofollow,noindex" target="_blank">Rust中文社群專欄文章:Rust錯誤處理 ,本文時間:2018-12-14, 譯者:krircc ,簡介:天青色,原文出處
歡迎向Rust中文社群投稿,投稿地址 ,好文將在以下地方直接展示
智慧編譯器
Rust編譯器最重要的工作是防止Rust程式中的錯誤。如果程式碼沒有正確遵循記憶體管理規則或生命週期註釋,它會在編譯時分析程式碼併發出警告。例如,
#[allow(unused_variables)] //:bulb: 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` trait error: aborting due to previous error For more information about this error, try `rustc --explain E0382`. // :star: instead using #[allow(unused_variables)], consider using "let _b = a;" in line 4. // Also you can use "let _ =" to completely ignore return values
Rust編譯器不僅檢查與生命週期或記憶體管理相關的問題,還檢查常見的編碼錯誤,如下面的程式碼。
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; };
:bulb:您也可以通過Rust Compiler Error Index
閱讀相同的解釋 。例如,要檢查E0571
錯誤的解釋,您可以使用https://doc.rust-lang.org/error-index.html#E0571
Panicking
panic!()
▸ 在某些情況下,當發生錯誤時,我們無法做任何事情來處理它,如果錯誤是某種情況,那就不應該發生。換句話說,如果這是一個不可恢復的錯誤。
▸ 當我們不使用功能豐富的偵錯程式或正確的日誌時,有時我們需要通過列印特定的訊息或變數繫結的值從特定的程式碼行退出程式來除錯程式碼以瞭解當前的程式的流程。
對於上述情況,我們可以使用panic!
巨集。讓我們看幾個例子。
:star:panic!()
執行基於執行緒。一個執行緒可能會被恐慌,而其他執行緒正在執行。
01.從特定行退出。
fn main() { // some code // if we need to debug in here panic!(); } // -------------- Compile time error -------------- thread 'main' panicked at 'explicit panic', src/main.rs:5:5
02.退出並顯示自定義錯誤訊息。
#[allow(unused_mut)] // :bulb: 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 name if username.is_empty() { panic!("Username is empty!"); } println!("{}", username); } // -------------- Compile time error -------------- thread 'main' panicked at 'Username is empty!', src/main.rs:8:9
03.退出附帶程式碼元素的值。
#[derive(Debug)] // :bulb: A lint attribute which use to implement `std::fmt::Debug` to Color struct Color { r: u8, g: u8, b: u8, } #[allow(unreachable_code)] // :bulb: A lint attribute used to suppress the warning; unreachable statement fn main() { let some_color: Color; // some code to get the color. ex some_color = Color {r: 255, g: 255, b: 0}; // if we need to debug in here panic!("{:?}", 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!()
型別樣式引數。預設情況下,它會輸出錯誤訊息,檔案路徑以及發生錯誤的行號和列號。
unimplemented!()
如果您的程式碼具有未完成的程式碼段,則有一個標準化巨集unimplemented!()
來標記這些路徑。如果程式通過這些路徑執行,程式將panicked
並返回"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:5
unreachable!()
這是標記程式不應輸入的路徑的標準巨集。如果程式進入這些路徑,程式將panicked
並返回"'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
我們也可以為此設定自定義錯誤訊息。
// --- 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:14
assert!(), assert_eq!(), assert_ne!()
這些是標準巨集,通常與測試斷言一起使用。
-
assert!()確保布林表示式為true。如果表示式為false,則會發生
panics
。
fn main() { let f = false; assert!(f) } // -------------- Compile time error -------------- thread 'main' panicked at 'assertion failed: f', src/main.rs:4:5
-
assert_eq!()確保兩個表示式相等。如果表示式不相等則會發生
panics
。
fn main() { let a = 10; let b = 20; assert_eq!(a, b); } // -------------- Compile time error -------------- thread 'main' panicked at 'assertion failed: `(left == right)` left: `10`, right: `20`', src/main.rs:5:5
-
assert_ne!()確保兩個表示式不相等。如果表示式相等,它會發生
panics
。
fn main() { let a = 10; let b = 10; assert_ne!(a, b); } // -------------- Compile time error -------------- thread 'main' panicked at 'assertion failed: `(left != right)` left: `10`, right: `10`', src/main.rs:5:5
:star:使用表示式assert_ne!()和assert_eq!()應返回相同的資料型別。
我們也可以為這些巨集設定自定義錯誤訊息。舉些例子,
- 帶有自定義訊息 assert_eq!()
fn main() { let a = 10; let b = 20; assert_eq!(a, b, "a and b should be equal"); } // -------------- Compile time error -------------- thread 'main' panicked at 'assertion failed: `(left == right)` left: `10`, right: `20`: a and b should be equal', src/main.rs:5:5
- assert_eq!()帶有除錯資料
fn main() { let a = 10; let b = 20; let c = 40; assert_eq!(a+b, c, "a = {} ; b = {}", a, b); } // -------------- Compile time error -------------- thread 'main' panicked at 'assertion failed: `(left == right)` left: `30`, right: `40`: a = 10 ; b = 20', src/main.rs:7:5
debug_assert!(), debug_assert_eq!(), debug_assert_ne!()
:mag_right:這些與上面的assert
巨集類似。但預設情況下,這些語句僅在非優化構建中啟用。debug_assert
除非我們傳遞-C debug-assertions
給編譯器,否則在釋出版本中將省略所有這些巨集。
Option and Result
許多語言使用null\ nil\ undefined
型別來表示空輸出和Exceptions處理錯誤。Rust會同時使用兩者,特別是為了防止諸如空指標異常,異常等敏感資料洩漏等問題。相反,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
的基本用法
編寫函式或資料型別時:
- 如果函式的引數是可選的,
- 如果函式為非空,並且返回的輸出可以為空,
- 如果資料型別的屬性的值可以是空,我們不得不使用他們的資料型別為Option型別
例如,如果函式輸出一個&str
值並且輸出可以為空,則函式的返回型別應設定為Option<&str>
fn get_an_optional_value() -> Option<&str> { //if the optional value is not empty return Some("Some value"); //else None }
同樣,如果資料型別的屬性值可以為空或者像下面示例中middle_name
的Name
資料型別那樣可選,我們應該將其資料型別設定為Option
型別。
struct Name { first_name: String, middle_name: Option<String>, // middle_name can be empty last_name: String, }
:thought_balloon:如您所知,我們可以使用模式匹配match
來捕獲相關的返回型別(Some/ None)
。有一個函式來獲取當前使用者的主目錄在std::env
為home_dir()
。由於所有使用者在Linux等系統中都沒有主目錄,因此使用者的主目錄可以是可選的。所以它返回一個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 playground None => println!("Can not find the home directory!"), } }
:star:但是,當在函式中使用可選引數時,我們必須None在呼叫函式時傳遞空引數的值。
fn get_full_name(fname: &str, lname: &str, mname: Option<&str>) -> String { // middle name can be empty match 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"))); } // :bulb: Better create a struct as Person with fname, lname, mname fields and create a impl function as full_name()
:mag_right:除此之外,Option
型別與Rust中的可空指標一起使用。由於Rust中沒有空指標,因此指標型別應指向有效位置。因此,如果指標可以為空,我們就可以使用了Option<Box<T>>
。
Result
的基本用法
如果函式可以產生錯誤,我們必須Result
通過組合有效輸出的資料型別和錯誤的資料型別來使用型別。例如,如果有效輸出的資料型別為u64
且錯誤型別為String
,則返回型別應為Result<u64, String>
。
fn function_with_error() -> Result<u64, String> { //if error happens return Err("The error message".to_string()); // else, return valid output Ok(255) }
:thought_balloon:如您所知,我們可以使用模式匹配match
來捕獲相關的返回型別(Ok/ Err)
。有一個函式可以獲取std::env
任何環境變數中的值是var()
。它的輸入是環境變數名稱。如果我們傳遞了錯誤的環境變數,或者程式在執行時無法提取環境變數的值,則會產生錯誤。所以它的返回型別是一種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 playground Err(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>
值轉換為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 = None assert_eq!(o.err(), None);// Ok(v) err = None assert_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 empty if false { return Some("abc"); } //else None } // --------------- 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 255 Err(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 happens if true { return Err("some message".to_string()); } // else, return valid output Ok(255) } // ---------- Compile time error ---------- thread 'main' panicked at 'some message', src/main.rs:5:19
上述main函式中的相同程式碼可以使用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
:star:但是正如您所看到的,當使用unwrap()
錯誤訊息時,沒有顯示發生恐慌的確切行號。
expect()
類似unwrap()
但可以為恐慌設定自定義訊息。
// 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:5
unwrap_err() and expect_err() for Result types
unwrap()
和expect()
相反的情況;Ok
時恐慌而不是Err
時。兩者都在Ok
錯誤訊息中列印內部值。
:bulb:通常用於測試。
// 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:5
unwrap_or(), unwrap_or_default() and unwrap_or_else()
:bulb:這些有點類似於unwrap()
,如果Option
型別有Some
值或Result
型別有Ok
值,則它們內部的值傳遞到下一步。但是當有None
或者Err
,功能有點不同。
-
unwrap_or()
:使用None
或Err
,您傳遞給的unwrap_or()
值將傳遞到下一步。但是,您傳遞的值的資料型別應與相關Some
或Ok
的資料型別匹配。
fn main() { let v1 = 8; let v2 = 16; let s_v1 = Some(8); let n = None; assert_eq!(s_v1.unwrap_or(v2), v1); // Some(v1) unwrap_or v2 = v1 assert_eq!(n.unwrap_or(v2), v2);// None unwrap_or v2 = v2 let o_v1: Result<i8, &str> = Ok(8); let e: Result<i8, &str> = Err("error"); assert_eq!(o_v1.unwrap_or(v2), v1); // Ok(v1) unwrap_or v2 = v1 assert_eq!(e.unwrap_or(v2), v2);// Err unwrap_or v2 = v2 }
-
unwrap_or_default(
) :使用None
或Err
,相關的資料型別的預設值Some
或者Ok
,傳遞到下一步。
fn main() { let v = 8; let v_default = 0; let s_v: Option<i8> = Some(8); let n: Option<i8> = None; assert_eq!(s_v.unwrap_or_default(), v);// Some(v) unwrap_or_default = v assert_eq!(n.unwrap_or_default(), v_default); // None unwrap_or_default = default value of v let o_v: Result<i8, &str> = Ok(8); let e: Result<i8, &str> = Err("error"); assert_eq!(o_v.unwrap_or_default(), v);// Ok(v) unwrap_or_default = v assert_eq!(e.unwrap_or_default(), v_default); // Err unwrap_or_default = default value of v }
-
unwrap_or_else()
:類似於unwrap_or()
。唯一的區別是,您必須傳遞一個閉包,它返回一個具有Some
或Ok
相關資料型別的值,而不是傳遞一個值。
fn main() { let v1 = 8; let v2 = 16; let s_v1 = Some(8); let n = None; let fn_v2_for_option = || 16; assert_eq!(s_v1.unwrap_or_else(fn_v2_for_option), v1); // Some(v1) unwrap_or_else fn_v2 = v1 assert_eq!(n.unwrap_or_else(fn_v2_for_option), v2);// None unwrap_or_else fn_v2 = v2 let o_v1: Result<i8, &str> = Ok(8); let e: Result<i8, &str> = Err("error"); let fn_v2_for_result = |_| 16; assert_eq!(o_v1.unwrap_or_else(fn_v2_for_result), v1); // Ok(v1) unwrap_or_else fn_v2 = v1 assert_eq!(e.unwrap_or_else(fn_v2_for_result), v2);// Err unwrap_or_else fn_v2 = v2 }
Error and None Propagation
我們應該使用恐慌panic!()
,unwrap()
,expect()
只有當我們沒有一個更好處理辦法的情況。此外如果一個函式包含表示式既能產生None
也能產生Err
,
▸我們可以在同一函式中處理
▸我們可以立即返回None
和Err
給呼叫者。因此呼叫者可以決定如何處理它們。
:bulb: None型別無需始終由函式的呼叫者處理。但Rusts處理Err
型別的約定是,立即將它們返回給呼叫者,以便給呼叫者更多的控制權來決定如何處理它們。
?
操作符
▸如果Option
型別具有Some
值或Result
型別具有Ok
值,則其中的值將傳遞到下一步。
▸如果Option
型別具有None
值或Result
型別具有Err
值,則立即將它們返回給函式的呼叫者。
示例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, ex println!("{}", 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 empty if false { return Some("abc"); } //else None }
示例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, ex println!("{}", x); // 255 ; if you change line 20 `true` to `false` Ok(0) } fn function_with_error() -> Result<u64, String> { //if error happens if true { return Err("some message".to_string()); } // else, return valid output Ok(255) }
從main()傳播錯誤
在Rust版本1.26之前,我們無法從main()函式傳播Result
和Option
。但是現在,我們可以從main()函式中傳播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
讓我們看看組合器是什麼,
- “組合者”的一個含義是更加非正式的意義,指的是組合模式,一種以組合事物的思想為中心組織圖書館的風格。通常存在一些型別T,一些用於構造型別T的“原始”值的函式,以及一些可以以各種方式組合型別T的值以構建型別T的更復雜值的 “ 組合器 ” 。另一個定義是“ 沒有自由變數的函式 ”(wiki.haskell.org)
- 組合子是一個函式,其從程式片段構建程式片段 ; 從某種意義上說,使用組合器的程式設計師自動構建了大部分所需的程式,而不是手工編寫每個細節。
Rust生態系統中“組合子”的確切定義有點不清楚。
▸or()
,and()
,or_else()
,and_then()
- 組合型別為T的兩個值並返回相同型別T。
▸filter()
對於Option型別
- 使用閉包作為條件函式來過濾型別T.
- 返回相同的型別T.
▸map()
,map_err()
- 通過使用閉包轉換型別T。
-
可以更改T內部值的資料型別。例如:
Some<&str>
可轉化為Some<usize>
或者Err<&str>
可轉化為Err<isize>
等
▸map_or()
,map_or_else()
- 通過應用閉包轉換型別T並返回型別T內的值。
-
對
None
和Err
,應用預設值或其他閉包。
▸ok_or()
,ok_or_else()
對於Option型別
- 將Option型別轉換為Result型別。
▸as_ref()
,as_mut()
- 將型別T轉換為引用或可變引用。
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 = Some1 assert_eq!(s1.or(n), s1);// Some or None = Some assert_eq!(n.or(s1), s1);// None or Some = Some assert_eq!(n.or(n), n);// None1 or None2 = None2 assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 = Ok1 assert_eq!(o1.or(e1), o1); // Ok or Err = Ok assert_eq!(e1.or(o1), o1); // Err or Ok = Ok assert_eq!(e1.or(e2), e2); // Err1 or Err2 = Err2 assert_eq!(s1.and(s2), s2); // Some1 and Some2 = Some2 assert_eq!(s1.and(n), n);// Some and None = None assert_eq!(n.and(s1), n);// None and Some = None assert_eq!(n.and(n), n);// None1 and None2 = None1 assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 = Ok2 assert_eq!(o1.and(e1), e1); // Ok and Err = Err assert_eq!(e1.and(o1), e1); // Err and Ok = Err assert_eq!(e1.and(e2), e1); // Err1 and Err2 = Err1 }
:mag_right:nightly支援Option型別的xor()
,它返回Some
當只有一個表示式返回Some
,而不是兩個。
or_else()
類似於or()。唯一的區別是,第二個表示式應該是一個返回相同型別T 的閉包。
fn main() { // or_else with Option let 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 = Some1 assert_eq!(s1.or_else(fn_none), s1);// Some or_else None = Some assert_eq!(n.or_else(fn_some), s2);// None or_else Some = Some assert_eq!(n.or_else(fn_none), None); // None1 or_else None2 = None2 // or_else with Result let 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 = Ok1 assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err = Ok assert_eq!(e1.or_else(fn_ok), o2);// Err or_else Ok = Ok assert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 = Err2 }
and_then()
類似於and()。唯一的區別是,第二個表示式應該是一個返回相同型別T 的閉包。
fn main() { // and_then with Option let 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 = Some2 assert_eq!(s1.and_then(fn_none), n);// Some and_then None = None assert_eq!(n.and_then(fn_some), n);// None and_then Some = None assert_eq!(n.and_then(fn_none), n);// None1 and_then None2 = None1 // and_then with Result let 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 = Ok2 assert_eq!(o1.and_then(fn_err), e2); // Ok and_then Err = Err assert_eq!(e1.and_then(fn_ok), e1);// Err and_then Ok = Err assert_eq!(e1.and_then(fn_err), e1); // Err1 and_then Err2 = Err1 }
filter()
:bulb:通常在程式語言中,filter函式與陣列或迭代器一起使用,通過函式/閉包過濾自己的元素來建立新的陣列/迭代器。Rust還提供了一個filter()
迭代器介面卡,用於在迭代器的每個元素上應用閉包,將其轉換為另一個迭代器。然而,在這裡,我們正在談論filter()
函式與Option
型別。
僅當我們傳遞一個Some
值並且給定的閉包為它返回true
時,返回相同的Some
型別。如果None
傳遞型別或閉包返回false
,返回None
。閉包使用Some
裡面的值作為引數。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 -> None assert_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()
:bulb:通常在程式語言中,map()函式與陣列或迭代器一起使用,以在陣列或迭代器的每個元素上應用閉包。Rust還提供了一個map()
迭代器介面卡,用於在迭代器的每個元素上應用閉包,將其轉換為另一個迭代器。但是在這裡我們討論的是map()
函式與Option
和Result
型別。
-
map()
:通過應用閉包來轉換型別T. 可以根據閉包的返回型別更改Some
或Ok
塊資料型別。轉換Option<T>
為Option<U>
,轉換Result<T, E>
為Result<U, E>
:star:map()
,僅僅Some
和Ok
值改變。對Err
內部值沒有影響(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 = Some2 assert_eq!(n1.map(fn_character_count), n2); // None1 map = None2 assert_eq!(o1.map(fn_character_count), o2); // Ok1 map = Ok2 assert_eq!(e1.map(fn_character_count), e2); // Err1 map = Err2 }
-
map_err()
對於Result
型別:Err塊的資料型別可以根據閉包的返回型別進行更改。轉換Result<T, E>
為Result<T, F>
。
:star:map_err()
,只有Err
值會發生變化。對Ok
內部的值沒有影響。
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 isize assert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map = Ok2 assert_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
值上應用閉包和返回型別T內的值。
-
map_or()
:僅支援Option
型別(不支援Result
)。將閉包應用於Some
內部值並根據閉包返回輸出。為None型別返回給定的預設值。
fn main() { const V_DEFAULT: i8 = 1; let s = Some(10); let n: Option<i8> = None; let fn_closure = |v: i8| v + 2; assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12); assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT); }
-
map_or_else()
:支援兩種Option
和Result
型別(Result僅限nightly)。類似map_or()
但應該提供另一個閉包而不是第一個引數的預設值。
:star:None
型別不包含任何值。所以不需要將任何東西傳遞給閉包作為輸入Option型別。但是Err
型別在其中包含一些值。因此,預設閉包應該能夠將其作為輸入讀取,同時將其與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 input assert_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
型別轉換為Result
型別。Some
對Ok
和None
對Err
。
-
ok_or()
:預設Err
訊息應作為引數傳遞.
fn main() { const ERR_DEFAULT: &str = "error message"; let s = Some("abcde"); let n: Option<&str> = None; let o: Result<&str, &str> = Ok("abcde"); let e: Result<&str, &str> = Err(ERR_DEFAULT); assert_eq!(s.ok_or(ERR_DEFAULT), o); // Some(T) -> Ok(T) assert_eq!(n.ok_or(ERR_DEFAULT), e); // None -> Err(default) }
-
ok_or_else()
:類似於ok_or()
。應該將閉包作為引數傳遞。
fn main() { let s = Some("abcde"); let n: Option<&str> = None; let fn_err_message = || "error message"; let o: Result<&str, &str> = Ok("abcde"); let e: Result<&str, &str> = Err("error message"); assert_eq!(s.ok_or_else(fn_err_message), o); // Some(T) -> Ok(T) assert_eq!(n.ok_or_else(fn_err_message), e); // None -> Err(default) }
as_ref() and as_mut()
:mag_right:如前所述,這些函式用於借用型別T作為引用或作為可變引用。
-
as_ref()
:轉換Option<T>
到Option<&T>
和Result<T, E>
到Result<&T, &E>
-
as_mut()
:轉換Option<T>
到Option<&mut T>
和Result<T, E>
到Result<&mut T, &mut E>
自定義錯誤型別
Rust允許我們建立自己的Err型別。我們稱之為“ 自定義錯誤型別”。
Error trait
如您所知,traits定義了型別必須提供的功能。但是我們不需要總是為常用功能定義新的特性,因為Rust 標準庫提供了一些可以在我們自己的型別上實現的可重用特性。建立自定義錯誤型別時,std::error::Error
trait可幫助我們將任何型別轉換為Err
型別。
use std::fmt::{Debug, Display}; pub trait Error: Debug + Display { fn source(&self) -> Option<&(Error + 'static)> { ... } }
一個特質可以從另一個特質繼承。trait Error: Debug + Display
意味著Error
特質繼承fmt::Debug
和fmt::Display
特質。
// 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
- 終端使用者應如何將此錯誤視為面向訊息/面向使用者的輸出。
-
通常通過
println!("{}")
或列印eprintln!("{}")
▸ Debug
- 如何顯示Errwhile除錯/面向程式設計師的輸出。
-
通常列印
println!("{:?}")
或eprintln!("{:?}")
-
-
漂亮列印,可以使用
println!("{:#?}")
或eprintln!("{:#?}")
。
-
漂亮列印,可以使用
▸ source()
- 此錯誤的較低級別來源(如果有)。
- 可選的。
首先,讓我們看看如何std::error::Error
在最簡單的自定義錯誤型別上實現特徵。
use std::fmt; // Custom error type; can be any type which defined in the current crate // :bulb: 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 }) }
希望你理解要點。現在,讓我們看一些帶有錯誤程式碼和錯誤訊息的自定義錯誤型別。
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 } // ) }
:star:️Rust標準庫不僅提供了可重用的特性,而且還有助於通過#[derive]
屬性神奇地生成少數特徵的實現。Rust支援derive
std::fmt::Debug
,為除錯訊息提供預設格式。因此,我們可以在struct前宣告使用#[derive(Debug)]
跳過實現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
在編寫真實的程式時,我們大多數時候必須同時處理不同的模組,不同的std和第三方的板條箱。但是每個包都使用自己的錯誤型別,如果我們使用自己的錯誤型別,我們應該將這些錯誤轉換為錯誤型別。我們可以使用std::convert::From
標準化特徵進行這些轉換。
// traits inside Rust standard library core convert module/ std::convert pub trait From<T>: Sized { fn from(_: T) -> Self; }
:bulb:如您所知,String::from()
函式用於建立String
from&str
資料型別。實際上這也是std::convert::From
特質的實現。
讓我們看看如何在自定義錯誤型別上實現std::convert::From
特徵。
use std::fs::File; use std::io; #[derive(Debug)] struct AppError { kind: String,// type of the error message: 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 AppError Ok(()) } // --------------- Run time error --------------- Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }
在上面的例子中,File::open(“nonexistent.txt”)?
產生std::io::Error
。但由於返回型別是Result<(), AppError>
,它轉換為AppError
。因為我們正在從main()
函式傳播錯誤,所以它會打印出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 AppError let mut content = String::new(); file.read_to_string(&mut content)?; // generates an io::Error, if can not read file content and converts to an AppError let _number: usize; _number = content.parse()?; // generates num::ParseIntError, if can not convert file content to usize and converts to an AppError Ok(()) } // --------------- 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" }
:mag_right: 搜尋有關實現的內容std::io::ErrorKind ,以瞭解如何進一步組織錯誤型別。