首頁 > 軟體

Rust使用kind進行例外處理(錯誤的分類與傳遞)

2022-09-28 14:00:56

前言

Rust 有一套獨特的處理異常情況的機制,它並不像其它語言中的 try 機制那樣簡單。
在Rust 中的錯誤分為兩大類:可恢復錯誤和不可恢復錯誤。大多數程式語言用 Exception (異常)類來表示錯誤。在 Rust 中沒有 Exception。對於可恢復錯誤用 Result<T, E> 類來處理,對於不可恢復錯誤使用 panic! 宏來處理。

1、不可恢復錯誤

  • 由程式設計中無法解決的邏輯錯誤導致的,例如存取陣列末尾以外的位置

1.1、panic! 宏的使用

的使用較為簡單,讓我們來看一個具體例子:

fn main() {
    panic!("Error occured");
    println!("Hello, rust");
}

執行結果:

很顯然,程式並不能如約執行到 println!("Hello, rust") ,而是在 panic! 宏被呼叫時停止了執行,不可恢復的錯誤一定會導致程式受到致命的打擊而終止執行。

1.2、通過 Powershell命令列分析錯誤原因

我們來分析一下終端命令列中的報錯資訊:

thread 'main' panicked at 'Error occured', srcmain.rs:2:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

  • 第一行輸出了 panic! 宏呼叫的位置以及其輸出的錯誤資訊
  • 第二行是一句提示,翻譯成中文就是"通過 RUST_BACKTRACE=full 環境變數執行以顯示回溯"。

接下來看一下回溯(backtrace)資訊:

stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/librarystdsrcpanicking.rs:584
   1: core::panicking::panic_fmt
             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/librarycoresrcpanicking.rs:142
   2: error_deal::main
             at .srcmain.rs:2
   3: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3librarycoresrcopsfunction.rs:248

回溯是不可恢復錯誤的另一種處理方式,它會展開執行的棧並輸出所有的資訊,然後程式依然會退出。通過大量的輸出資訊,我們可以找到我們編寫的 panic! 宏觸發的錯誤。

2、可恢復的錯誤

  • 如果存取一個檔案失敗,有可能是因為它正在被佔用,是正常的,我們可以通過等待來解決。

2.1、Rustlt<T,E>列舉類的使用

此概念十分類似於 Java 程式語言中的異常,而在 C 語言中我們就常常將函數返回值設定成整數來表達函數遇到的錯誤,在 Rust 中通過 Result<T, E> 列舉類作返回值來進行異常表達:

enum Result<T, E> {
    Ok(T),
    Err(E),
}//T的型別不定,相當於C++中模板的寫法

我們知道enum常常與match配合使用,當匹配到OK時就會執行相應程式碼。

在 Rust 標準庫中可能產生異常的函數的返回值都是 Result 型別。

例如:當我們嘗試開啟一個檔案時:

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裡的引數file是File型別,相當於填充了列舉裡的T型別

如果 hello_rust.txt 檔案不存在,會列印 Failed to open the file.

當然,我們在列舉類章節講到的 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) 方法

將一個可恢復錯誤按不可恢復錯誤處理

舉個例子:

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.");
}
  • 這段程式相當於在 Result 為 Err 時呼叫 panic!宏
  • 兩者的區別在於 expect 能夠向 panic! 宏傳送一段指定的錯誤資訊
  • panic!宏是不可恢復錯誤,這樣就完成了轉變

3、可恢復的錯誤的傳遞

之前所講的是接收到錯誤的處理方式,接下來講講怎麼把錯誤資訊傳遞出去

我們先來編寫一個函數:

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");
    }
}//執行結果:Ok: f(-1) = 10000

這裡r的結果是f函數返回的ok(10000),經過if let模式匹配後v的值為10000

這段程式中函數 f 是錯誤的根源,現在我們再寫一個傳遞錯誤的函數 g

fn g(i: i32) -> Result<i32, bool> {
    let t = f(i);
    return match t {
        Ok(i) => Ok(i),
        Err(b) => Err(b)
    };
}

函數 g 傳遞了函數 f 可能出現的錯誤,這樣寫有些冗長,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 在這裡已經推匯出是 i32 型別
}

fn main() {
    let r = g(10000);
    if let Ok(v) = r {
        println!("Ok: g(10000) = {}", v);
    } else {
        println!("Err");
    }
}//執行結果:Ok: g(10000) = 10000

? 符的實際作用是將 Result 類非異常的值直接取出,如果有異常就將異常 Result 返回出去。所以? 符僅用於返回值型別為 Result<T, E> 的函數,且其中 E 型別必須和 ? 所處理的 Result 的 E 型別一致。

4、結合kind方法處理異常

雖然前面提到Rust 異常不像其他語言這麼簡單,但這並不意味著 Rust 實現不了:我們完全可以把 try 塊在獨立的函數中實現,將所有的異常都傳遞出去解決。

實際上這才是一個分化良好的程式應當遵循的程式設計方法:應該注重獨立功能的完整性。

但是這樣需要判斷 Result 的 Err 型別,獲取 Err 型別的函數是 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");
                }
            }
        }
    }
}//這裡我沒有建立hello_rust.txt檔案,因此執行結果為:No such file

程式碼解釋:

  • 使用read_text_from_file()函數將檔案開啟的結果傳給了str_file變數
  • 這裡並不存在hello_rust.txt,因此File::open(path)?不會開啟檔案,異常會存到f
  • f.read_to_string(&mut s)?並不能讀出檔案內容,ok(s)無內容
  • 通過分析,分支會執行Err(e)的程式碼塊,使用e.kind()得到了錯誤型別並再次進行match分支
  • 如果是NotFound錯誤就會列印No such file
  • 其他情錯誤均提示Cannot read the file

到此這篇關於Rust指南錯誤的分類與傳遞|使用kind進行例外處理的文章就介紹到這了,更多相關Rust錯誤處理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com