錯(cuò)誤是軟件中不可避免的,所以 Rust 有一些處理出錯(cuò)情況的特性。在許多情況下,Rust 要求你承認(rèn)錯(cuò)誤的可能性,并在你的代碼編譯前采取一些行動(dòng)。這一要求使你的程序更加健壯,因?yàn)樗梢源_保你在將代碼部署到生產(chǎn)環(huán)境之前就能發(fā)現(xiàn)錯(cuò)誤并進(jìn)行適當(dāng)?shù)奶幚怼?/p>
Rust中的錯(cuò)誤可分為可 恢復(fù)錯(cuò)誤 (recoverable)和 不可恢復(fù)錯(cuò)誤 (unrecoverable)
可恢復(fù)錯(cuò)誤代表一種可以恢復(fù)的情況下失敗,可以向用戶報(bào)告問題并重試操作,例如未找到文件;
不可恢復(fù)錯(cuò)誤代表一種不可處理的狀態(tài),會(huì)導(dǎo)致程序 崩2 ,例如嘗試訪問超過數(shù)組結(jié)尾的位置;
對(duì)比其他編程語言的錯(cuò)誤處理:
1. Java語言采用了異常機(jī)制的方式來處理,并沒有明確區(qū)分可恢復(fù)錯(cuò)誤和不可恢復(fù)錯(cuò)誤。
(ps: 雖然Java的異常給出了Throwable, Error, Exception, RuntimeException的
繼承關(guān)系體系,異常分為checked exception和uncheced exceppion,但在異常傳播上
采用的是通過?;厮莸姆绞揭粚訉觽鬟f,直到出現(xiàn)捕獲異常的地方。) Java語言這種異常
處理方式的優(yōu)點(diǎn)是簡(jiǎn)化了錯(cuò)誤處理流程,但在運(yùn)行時(shí)開銷比使用返回值返回錯(cuò)誤信息的方式要大很多。
2. Go語言是明確區(qū)分可恢復(fù)錯(cuò)誤(error)和不可恢復(fù)錯(cuò)誤(panic)的。Go對(duì)可恢復(fù)錯(cuò)誤
采用了以函數(shù)返回錯(cuò)誤值的形式,在函數(shù)返回時(shí)額外返回一個(gè)錯(cuò)誤對(duì)象(error),這種方式的優(yōu)點(diǎn)
是錯(cuò)誤處理的運(yùn)行時(shí)開銷小,缺點(diǎn)是返回的錯(cuò)誤必須處理或者顯式傳播返回給上級(jí)調(diào)用,
因此一個(gè)Go程序代碼中會(huì)有大量的if err!= nil {return err;}。
Rust中沒有異常,對(duì)于可恢復(fù)錯(cuò)誤使用了類型Result
,即函數(shù)返回的錯(cuò)誤信息通過類型系統(tǒng)描述。對(duì)于在程序遇到不可恢復(fù)的錯(cuò)誤時(shí)panic!
時(shí)停止執(zhí)行
1. Result和可恢復(fù)錯(cuò)誤
Result
是一個(gè)枚舉類型,其定義如下:
#[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_type"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result
Result
枚舉有兩個(gè)成員,Ok
和Err
。T
和E
是泛型參數(shù),T
代表成功返回的Ok
成員中的數(shù)據(jù)類型。E
代表失敗返回的Err
成員中的錯(cuò)誤的類型。有了這兩個(gè)泛型參數(shù),可以將Result枚舉作為函數(shù)的返回值,用于各種場(chǎng)景下的可恢復(fù)錯(cuò)誤的處理,當(dāng)函數(shù)成功時(shí)返回Ok(T)
,失敗時(shí)返回Err(E)
。
Result的定義上面有一個(gè)must_use
的標(biāo)注,rust的編譯器會(huì)對(duì)must_use
標(biāo)注的類型做特殊處理,如果該類型對(duì)應(yīng)的值沒有被顯式使用,則就會(huì)有一個(gè)警告。例如下面的代碼。
fn foo() -> Result<(), String> {
Ok(())
}
fn main() {
foo(); // unused `std::result::Result` that must be used
}
代碼在調(diào)用foo
函數(shù)時(shí),忽略了返回值Result,因?yàn)镽esult上有must_use
標(biāo)注,所以Rust的編譯器在編譯時(shí)會(huì)報(bào)一個(gè)警告:
warning: unused `Result` that must be used
--> src/main.rs:7:5
|
7 | foo(); // unused `std::result::Result` that must be used
| ^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled
1.1 匹配不同的錯(cuò)誤原因
在處理錯(cuò)誤時(shí),很多時(shí)候需要針對(duì)不同的錯(cuò)誤原因進(jìn)行不同的處理。下面來學(xué)習(xí)一下rust標(biāo)準(zhǔn)庫中的std::io
module中是如何設(shè)計(jì)錯(cuò)誤處理的。
std::io
中定義了一個(gè)std::io::Result
:
#[stable(feature = "rust1", since = "1.0.0")]
pub type Result
從io::Result
的定義可以看出,io::Result
實(shí)際上是result::Result
的別名。 io::Result
中的Err
成員類型是io::Error
。
io::Error
是一個(gè)結(jié)構(gòu)體,它由一個(gè)kind()
方法簽名是pub fn kind(&self) -> ErrorKind
,返回描述錯(cuò)誤原因枚舉ErrorKind
。
ErrorKind
的成員是各種io錯(cuò)誤原因,比如NotFound
, PermissionDenied
…
因此如果函數(shù)返回io::Result
,失敗時(shí)返回的是io::Error
時(shí),就可以調(diào)用kind方法,進(jìn)一步匹配不同的錯(cuò)誤原因進(jìn)行不同處理。
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt").unwrap_or_else(|err| {
match err.kind() {
ErrorKind::NotFound => File::create("hello.tx").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
}), // 匹配錯(cuò)誤原因, 對(duì)于文件不存在的錯(cuò)誤處理為創(chuàng)建文件
other_error_kind => panic!("Problem opening the file: {:?}", other_error_kind)
}
});
println!("{:?}", f);
}
例2中還用到了Result的unwrap_or_else
方法,Result
類型定義了很多輔助方法來處理各種情況。除了unwrap_or_else
外,還有:
unwrap
方法: 如果Result的值是成員Ok
,unwrap
就返回Ok
的值;如果Result的值是成員Err
,unwrap
就會(huì)調(diào)用panic!
expect
方法: 與unwrap的使用方式一樣,允許我們傳參指定panic!
的信息
1.2 使用?
操作符傳播錯(cuò)誤
經(jīng)常在編寫一個(gè)函數(shù)實(shí)現(xiàn)時(shí)會(huì)調(diào)用另一個(gè)返回Result
的函數(shù),除了在這個(gè)函數(shù)中處理錯(cuò)誤之外,還可以選擇將錯(cuò)誤傳播到上游調(diào)用者,這就是傳播錯(cuò)誤。
rust還提供了強(qiáng)大的?
操作符,如果我們只想要傳播錯(cuò)誤,而不想直接處理,可以使用?
操作符。
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
代碼中第6行的?
操作符會(huì)被展開成類似下面的代碼:
match result {
Ok(v) => v,
Err(e) => Err(e.into())
}
```Result 值之后的 ? 作用為與 match 表達(dá)式有著完全相同的工作方式。如果 Result 的值是 Ok,這個(gè)表達(dá)式將會(huì)返回 Ok 中的值而程序?qū)⒗^續(xù)執(zhí)行。如果值是 Err,Err 中的值將作為整個(gè)函數(shù)的返回值,就好像使用了 return 關(guān)鍵字一樣,這樣錯(cuò)誤值就被傳播給了調(diào)用者。
match 表達(dá)式與 ? 運(yùn)算符所做的有一點(diǎn)不同:? 運(yùn)算符所使用的錯(cuò)誤值被傳遞給了 from 函數(shù),它定義于標(biāo)準(zhǔn)庫的 From trait 中,其用來將錯(cuò)誤從一種類型轉(zhuǎn)換為另一種類型。當(dāng) ? 運(yùn)算符調(diào)用 from 函數(shù)時(shí),收到的錯(cuò)誤類型被轉(zhuǎn)換為由當(dāng)前函數(shù)返回類型所指定的錯(cuò)誤類型。
## 2. panic! 和不可恢復(fù)錯(cuò)誤
突然有一天,代碼出問題了,程序崩潰,對(duì)于這種情況,Rust提供了一個(gè)panic!宏,當(dāng)執(zhí)行這個(gè)宏時(shí),程序會(huì)打印出一個(gè)錯(cuò)誤信息,展開并清理?xiàng)?shù)據(jù),然后接著退出。出現(xiàn)這種情況的場(chǎng)景通常是檢測(cè)到一些類型的 bug,Rust程序員可以讓 Rust 在 panic 發(fā)生時(shí)打印調(diào)用堆棧(call stack)以便于定位 panic 的原因。
在實(shí)踐中有兩種方法造成 panic:
* 執(zhí)行會(huì)造成代碼 panic 的操作,比如訪問超過數(shù)組結(jié)尾的內(nèi)容
* 顯式調(diào)用 panic! 宏,比如`panic!("crash and burn");`。
panic!表示不可恢復(fù)的錯(cuò)誤,希望程序馬上終止運(yùn)行并得到崩潰信息。
rust標(biāo)準(zhǔn)庫還提供了`catch_unwind()`,可以把panic的調(diào)用?;厮莸絚atch_unwind的時(shí)候。
use std::panic;
fn main() {
let result = panic::catch_unwind(|| {
panic!("crash");
});
if result.is_err() {
println!("panic reover: {:#?}", result);
}
println!("exit ok!");
}
代碼運(yùn)行結(jié)果如下:
thread 'main' panicked at 'crash', src/main.rs:4:9
note: run with RUST_BACKTRACE=1
environment variable to display
a backtracepanic reover: Err(
Any { .. },
)
exit ok!
### 2.1 使用 `panic!` 的 backtrace
可以使用 `RUST_BACKTRACE=1 cargo run` 來得到一個(gè) backtrace,backtrace 是一個(gè)執(zhí)行到目前位置所有被調(diào)用的函數(shù)的列表。Rust 的 backtrace 跟其他語言中的一樣:閱讀 backtrace 的關(guān)鍵是從頭開始讀直到發(fā)現(xiàn)你編寫的文件。這就是問題的發(fā)源地。這一行往上是你的代碼所調(diào)用的代碼;往下則是調(diào)用你的代碼的代碼。這些行可能包含核心 Rust 代碼,標(biāo)準(zhǔn)庫代碼或用到的 crate 代碼。
// src/main.rs
fn main() {
let v = vec![1, 2, 3];
v[99];
}
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
stack backtrace:
0: rust_begin_unwind at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5
1: core::panicking::panic_fmt at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14
2: core::panicking::panic_bounds_check at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5
3: >::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10
4: core::slice::index:: for [T]>::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9
5: [alloc::vec::Vec as core::ops::index::Index*>::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9
6: panic::main at ./src/main.rs:4:5
7: core::ops::function::FnOnce::call_once at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with RUST_BACKTRACE=full
for a verbose backtrace.*](alloc::vec::Vec%3CT,A)
[**
## 總結(jié)
Rust 的錯(cuò)誤處理功能被設(shè)計(jì)為幫助你編寫更加健壯的代碼。`panic!` 宏代表一個(gè)程序**無法處理**的狀態(tài),并停止執(zhí)行而不是使用無效或不正確的值繼續(xù)處理。Rust 類型系統(tǒng)的 `Result` 枚舉代表操作可能會(huì)在一種**可以恢復(fù)**的情況下失敗,可以使用 `Result` 來告訴代碼調(diào)用者他需要處理潛在的成功或失敗。在適當(dāng)?shù)膱?chǎng)景使用 `panic!` 和 `Result` 將會(huì)使你的代碼在面對(duì)不可避免的錯(cuò)誤時(shí)顯得更加可靠。
* 可恢復(fù)錯(cuò)誤:想向用戶報(bào)告問題并**重試**操作,Result處理;
* 不可恢復(fù)錯(cuò)誤:導(dǎo)致程序**崩潰的**panic,catch_unwind() 棧回溯;
* match匹配: 處理返回值`Result`,**匹配**出不同錯(cuò)誤的原因;
* ?運(yùn)算符: **傳播錯(cuò)誤** ,將錯(cuò)誤從一種類型轉(zhuǎn)換為另一種類型,返回給調(diào)用者;
**](alloc::vec::Vec%3CT,A)
-
JAVA
+關(guān)注
關(guān)注
19文章
2952瀏覽量
104489 -
編譯器
+關(guān)注
關(guān)注
1文章
1617瀏覽量
49016 -
rust語言
+關(guān)注
關(guān)注
0文章
57瀏覽量
3001
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論