Rust使用kind進行異常處理(錯誤的分類與傳遞)
前言
Rust 有一套獨特的處理異常情況的機制,它并不像其它語言中的 try 機制那樣簡單。
在Rust 中的錯誤分為兩大類:可恢復(fù)錯誤和不可恢復(fù)錯誤。大多數(shù)編程語言用 Exception
(異常)類來表示錯誤。在 Rust 中沒有 Exception。對于可恢復(fù)錯誤用 Result<T, E>
類來處理,對于不可恢復(fù)錯誤使用 panic!
宏來處理。
1、不可恢復(fù)錯誤
- 由編程中無法解決的邏輯錯誤導(dǎo)致的,例如訪問數(shù)組末尾以外的位置。
1.1、panic! 宏的使用
宏的使用較為簡單,讓我們來看一個具體例子:
fn main() { panic!("Error occured"); println!("Hello, rust"); }
運行結(jié)果:
很顯然,程序并不能如約運行到 println!("Hello, rust")
,而是在 panic!
宏被調(diào)用時停止了運行,不可恢復(fù)的錯誤一定會導(dǎo)致程序受到致命的打擊而終止運行。
1.2、通過 Powershell命令行分析錯誤原因
我們來分析一下終端命令行中的報錯信息:
thread 'main' panicked at 'Error occured', src\main.rs:2:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
- 第一行輸出了 panic! 宏調(diào)用的位置以及其輸出的錯誤信息
- 第二行是一句提示,翻譯成中文就是"通過
RUST_BACKTRACE=full
環(huán)境變量運行以顯示回溯"。
接下來看一下回溯(backtrace
)信息:
stack backtrace:
0: std::panicking::begin_panic_handler
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library\std\src\panicking.rs:584
1: core::panicking::panic_fmt
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library\core\src\panicking.rs:142
2: error_deal::main
at .\src\main.rs:2
3: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3\library\core\src\ops\function.rs:248
回溯是不可恢復(fù)錯誤的另一種處理方式,它會展開運行的棧并輸出所有的信息,然后程序依然會退出。通過大量的輸出信息,我們可以找到我們編寫的 panic! 宏觸發(fā)的錯誤。
2、可恢復(fù)的錯誤
- 如果訪問一個文件失敗,有可能是因為它正在被占用,是正常的,我們可以通過等待來解決。
2.1、Rustlt<T,E>枚舉類的使用
此概念十分類似于 Java 編程語言中的異常,而在 C 語言中我們就常常將函數(shù)返回值設(shè)置成整數(shù)來表達函數(shù)遇到的錯誤,在 Rust 中通過 Result<T, E>
枚舉類作返回值來進行異常表達:
enum Result<T, E> { Ok(T), Err(E), }//T的類型不定,相當(dāng)于C++中模板的寫法
我們知道
enum
常常與match
配合使用,當(dāng)匹配到OK
時就會執(zhí)行相應(yīng)代碼。
在 Rust 標準庫中可能產(chǎn)生異常的函數(shù)的返回值都是 Result
類型。
例如:當(dāng)我們嘗試打開一個文件時:
use std::fs::File; fn main() { let fp = File::open("hello_rust.txt"); match fp { Ok(file) => { println!("File opened successfully."); }, Err(err) => { println!("Failed to open the file."); } } }//OK里的參數(shù)file是File類型,相當(dāng)于填充了枚舉里的T類型
如果
hello_rust.txt
文件不存在,會打印 Failed to open the file.
當(dāng)然,我們在枚舉類章節(jié)講到的 if let
模式匹配語法可以簡化 match
語法塊:
use std::fs::File; fn main() { let fp = File::open("hello_rust.txt"); if let Ok(file) = fp { println!("File opened successfully."); } else { println!("Failed to open the file."); } }
2.2、Result 類的unwrap() 和 expect(message: &str) 方法
將一個可恢復(fù)錯誤按不可恢復(fù)錯誤處理
舉個例子:
use std::fs::File; fn main() { let fp1 = File::open("hello_rust.txt").unwrap(); let fp2 = File::open("hello_rust.txt").expect("Failed to open."); }
- 這段程序相當(dāng)于在 Result 為
Err
時調(diào)用 panic!宏 - 兩者的區(qū)別在于
expect
能夠向 panic! 宏發(fā)送一段指定的錯誤信息 panic!
宏是不可恢復(fù)錯誤,這樣就完成了轉(zhuǎn)變
3、可恢復(fù)的錯誤的傳遞
之前所講的是接收到錯誤的處理方式,接下來講講怎么把錯誤信息傳遞出去
我們先來編寫一個函數(shù):
fn f(i: i32) -> Result<i32, bool> { if i >= 0 { Ok(i) } else { Err(false) } } fn main() { let r = f(10000); if let Ok(v) = r { println!("Ok: f(-1) = {}", v); } else { println!("Err"); } }//運行結(jié)果:Ok: f(-1) = 10000
這里
r
的結(jié)果是f
函數(shù)返回的ok(10000)
,經(jīng)過if let
模式匹配后v
的值為10000
這段程序中函數(shù) f
是錯誤的根源,現(xiàn)在我們再寫一個傳遞錯誤的函數(shù) g
:
fn g(i: i32) -> Result<i32, bool> { let t = f(i); return match t { Ok(i) => Ok(i), Err(b) => Err(b) }; }
函數(shù) g 傳遞了函數(shù) f 可能出現(xiàn)的錯誤,這樣寫有些冗長,Rust 中可以在 Result 對象后添加 ?
操作符將同類的 Err
直接傳遞出去:
fn f(i: i32) -> Result<i32, bool> { if i >= 0 { Ok(i) } else { Err(false) } } fn g(i: i32) -> Result<i32, bool> { let t = f(i)?; Ok(t) // 因為確定 t 不是 Err, t 在這里已經(jīng)推導(dǎo)出是 i32 類型 } fn main() { let r = g(10000); if let Ok(v) = r { println!("Ok: g(10000) = {}", v); } else { println!("Err"); } }//運行結(jié)果:Ok: g(10000) = 10000
?
符的實際作用是將 Result 類非異常的值直接取出,如果有異常就將異常 Result 返回出去。所以? 符僅用于返回值類型為 Result<T, E> 的函數(shù),且其中E
類型必須和?
所處理的 Result 的 E 類型一致。
4、結(jié)合kind方法處理異常
雖然前面提到Rust 異常不像其他語言這么簡單,但這并不意味著 Rust 實現(xiàn)不了:我們完全可以把 try
塊在獨立的函數(shù)中實現(xiàn),將所有的異常都傳遞出去解決。
實際上這才是一個分化良好的程序應(yīng)當(dāng)遵循的編程方法:應(yīng)該注重獨立功能的完整性。
但是這樣需要判斷 Result 的 Err 類型,獲取 Err 類型的函數(shù)是 kind()
做一個打開文件的實例:
use std::io; use std::io::Read; use std::fs::File; fn read_text_from_file(path: &str) -> Result<String, io::Error> { let mut f = File::open(path)?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } fn main() { let str_file = read_text_from_file("hello_rust.txt"); match str_file { Ok(s) => println!("{}", s), Err(e) => { match e.kind() { io::ErrorKind::NotFound => { println!("No such file"); }, _ => { println!("Cannot read the file"); } } } } }//這里我沒有創(chuàng)建hello_rust.txt文件,因此運行結(jié)果為:No such file
代碼解釋:
- 使用
read_text_from_file()
函數(shù)將文件打開的結(jié)果傳給了str_file
變量 - 這里并不存在
hello_rust.txt
,因此File::open(path)?
不會打開文件,異常會存到f
中 f.read_to_string(&mut s)?
并不能讀出文件內(nèi)容,ok(s)
無內(nèi)容- 通過分析,分支會執(zhí)行
Err(e)
的代碼塊,使用e.kind()
得到了錯誤類型并再次進行match
分支 - 如果是
NotFound
錯誤就會打印No such file - 其他情錯誤均提示Cannot read the file
到此這篇關(guān)于Rust指南錯誤的分類與傳遞|使用kind進行異常處理的文章就介紹到這了,更多相關(guān)Rust錯誤處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何在Rust中處理命令行參數(shù)和環(huán)境變量
在本章節(jié)中, 我們探討了Rust處理命令行參數(shù)的常見的兩種方式和處理環(huán)境變量的兩種常見方式,感興趣的朋友一起看看吧2023-12-12如何使用bindgen將C語言頭文件轉(zhuǎn)換為Rust接口代碼
這篇文章主要介紹了使用bindgen將C語言頭文件轉(zhuǎn)換為Rust接口代碼,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01