學習Rust 錯誤處理

roc_guo發表於2020-12-25

學習Rust 錯誤處理學習Rust 錯誤處理
Rust 有一套獨特的處理異常情況的機制,它並不像其它語言中的 try 機制那樣簡單。
首先,程式中一般會出現兩種錯誤:可恢復錯誤和不可恢復錯誤。
可恢復錯誤的典型案例是檔案訪問錯誤,如果訪問一個檔案失敗,有可能是因為它正在被佔用,是正常的,我們可以透過等待來解決。
但還有一種錯誤是由程式設計中無法解決的邏輯錯誤導致的,例如訪問陣列末尾以外的位置。
大多數程式語言不區分這兩種錯誤,並用 Exception (異常)類來表示錯誤。在 Rust 中沒有 Exception。
對於可恢復錯誤用 Result 類來處理,對於不可恢復錯誤使用 panic! 宏來處理。

不可恢復錯誤

本章以前沒有專門介紹 Rust 宏的語法,但已經使用過了 println! 宏,因為這些宏的使用較為簡單,所以暫時不需要徹底掌握它,我們可以用同樣的方法先學會使用 panic! 宏的使用方法。

例項

fn main() {
    panic!("error occured");
    println!("Hello, Rust");
}

執行結果:

thread 'main' panicked at 'error occured', src\main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

很顯然,程式並不能如約執行到 println!("Hello, Rust") ,而是在 panic! 宏被呼叫時停止了執行。

不可恢復的錯誤一定會導致程式受到致命的打擊而終止執行。

讓我們注視錯誤輸出的兩行:

第一行輸出了 panic! 宏呼叫的位置以及其輸出的錯誤資訊。
第二行是一句提示,翻譯成中文就是"透過 `RUST_BACKTRACE=1` 環境變數執行以顯示回溯"。接下來我們將介紹回溯(backtrace)。
緊接著剛才的例子,我們在 VSCode 中新建一個終端:

在新建的終端裡設定環境變數(不同的終端方法不同,這裡介紹兩種主要的方法):

如果在 Windows 7 及以上的 Windows 系統版本中,預設使用的終端 行是 Power ,請使用以下 :

$env:RUST_BACKTRACE=1 ; cargo run

如果你使用的是   或 macOS 等 UNIX 系統,一般情況下預設使用的是 bash 命令列,請使用以下命令:

RUST_BACKTRACE=1 cargo run

然後,你會看到以下文字:

thread 'main' panicked at 'error occured', src\main.rs:3:5
stack backtrace:
  ...
  11: greeting::main
             at .\src\main.rs:3
  ...

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

可恢復的錯誤

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

enum Result{
    Ok(T),
    Err(E),
}

在 Rust 標準庫中可能產生異常的函式的返回值都是 Result 型別的。例如:當我們嘗試開啟一個檔案時:

例項

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => {
            println!("File opened successfully.");
        },
        Err(err) => {
            println!("Failed to open the file.");
        }
    }
}

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

當然,我們在列舉類章節講到的 if let 語法可以簡化 match 語法塊:

例項

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    if let Ok(file) = f {
        println!("File opened successfully.");
    } else {
        println!("Failed to open the file.");
    }
}

如果想使一個可恢復錯誤按不可恢復錯誤處理,Result 類提供了兩個辦法:unwrap() 和 expect(message: &str) :

例項

use std::fs::File;
fn main() {
    let f1 = File::open("hello.txt").unwrap();
    let f2 = File::open("hello.txt").expect("Failed to open.");
}

這段程式相當於在 Result 為 Err 時呼叫 panic! 宏。兩者的區別在於 expect 能夠向 panic! 宏傳送一段指定的錯誤資訊。

可恢復的錯誤的傳遞

之前所講的是接收到錯誤的處理方式,但是如果我們自己編寫一個函式在遇到錯誤時想傳遞出去怎麼辦呢?

例項

fn f(i: i32) -> Result{
    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

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

例項

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

函式 g 傳遞了函式 f 可能出現的錯誤(這裡的 g 只是一個簡單的例子,實際上傳遞錯誤的函式一般還包含很多其它操作)。

這樣寫有些冗長,Rust 中可以在 Result 物件後新增 ? 運算子將同類的 Err 直接傳遞出去:

例項

fn f(i: i32) -> Result{
    if i >= 0 { Ok(i) }
    else { Err(false) }
}
fn g(i: i32) -> Result{
    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的函式,其中 E 型別必須和 ? 所處理的 Result 的 E 型別一致。

kind 方法

到此為止,Rust 似乎沒有像 try 塊一樣可以令任何位置發生的同類異常都直接得到相同的解決的語法,但這樣並不意味著 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{
    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.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");
                }
            }
        }
    }
}

執行結果:

No such file

原文來自:

本文地址:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2745180/,如需轉載,請註明出處,否則將追究法律責任。

相關文章