Rust 錯(cuò)誤處理

Rust 有一套獨(dú)特的處理異常情況的機(jī)制,它并不像其它語(yǔ)言中的 try 機(jī)制那樣簡(jiǎn)單。

首先,程序中一般會(huì)出現(xiàn)兩種錯(cuò)誤:可恢復(fù)錯(cuò)誤和不可恢復(fù)錯(cuò)誤。

可恢復(fù)錯(cuò)誤的典型案例是文件訪問(wèn)錯(cuò)誤,如果訪問(wèn)一個(gè)文件失敗,有可能是因?yàn)樗诒徽加?,是正常的,我們可以通過(guò)等待來(lái)解決。

但還有一種錯(cuò)誤是由編程中無(wú)法解決的邏輯錯(cuò)誤導(dǎo)致的,例如訪問(wèn)數(shù)組末尾以外的位置。

大多數(shù)編程語(yǔ)言不區(qū)分這兩種錯(cuò)誤,并用 Exception (異常)類來(lái)表示錯(cuò)誤。在 Rust 中沒(méi)有 Exception。

對(duì)于可恢復(fù)錯(cuò)誤用 Result<T, E> 類來(lái)處理,對(duì)于不可恢復(fù)錯(cuò)誤使用 panic! 宏來(lái)處理。

不可恢復(fù)錯(cuò)誤

本章以前沒(méi)有專門介紹 Rust 宏的語(yǔ)法,但已經(jīng)使用過(guò)了 println! 宏,因?yàn)檫@些宏的使用較為簡(jiǎn)單,所以暫時(shí)不需要徹底掌握它,我們可以用同樣的方法先學(xué)會(huì)使用 panic! 宏的使用方法。

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

運(yùn)行結(jié)果:

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

很顯然,程序并不能如約運(yùn)行到 println!("Hello, Rust") ,而是在 panic! 宏被調(diào)用時(shí)停止了運(yùn)行。

不可恢復(fù)的錯(cuò)誤一定會(huì)導(dǎo)致程序受到致命的打擊而終止運(yùn)行。

讓我們注視錯(cuò)誤輸出的兩行:

  • 第一行輸出了 panic! 宏調(diào)用的位置以及其輸出的錯(cuò)誤信息。

  • 第二行是一句提示,翻譯成中文就是"通過(guò) `RUST_BACKTRACE=1` 環(huán)境變量運(yùn)行以顯示回溯"。接下來(lái)我們將介紹回溯(backtrace)。

緊接著剛才的實(shí)例,我們?cè)?VSCode 中新建一個(gè)終端:

圖片.png

在新建的終端里設(shè)置環(huán)境變量(不同的終端方法不同,這里介紹兩種主要的方法):

如果在 Windows 7 及以上的 Windows 系統(tǒng)版本中,默認(rèn)使用的終端命令行是 Powershell,請(qǐng)使用以下命令:

$env:RUST_BACKTRACE=1 ; cargo run

如果你使用的是 Linux 或 macOS 等 UNIX 系統(tǒng),一般情況下默認(rèn)使用的是 bash 命令行,請(qǐng)使用以下命令:

RUST_BACKTRACE=1 cargo run

然后,你會(huì)看到以下文字:

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

回溯是不可恢復(fù)錯(cuò)誤的另一種處理方式,它會(huì)展開(kāi)運(yùn)行的棧并輸出所有的信息,然后程序依然會(huì)退出。上面的省略號(hào)省略了大量的輸出信息,我們可以找到我們編寫(xiě)的 panic! 宏觸發(fā)的錯(cuò)誤。

可恢復(fù)的錯(cuò)誤

此概念十分類似于 Java 編程語(yǔ)言中的異常。實(shí)際上在 C 語(yǔ)言中我們就常常將函數(shù)返回值設(shè)置成整數(shù)來(lái)表達(dá)函數(shù)遇到的錯(cuò)誤,在 Rust 中通過(guò) Result<T, E> 枚舉類作返回值來(lái)進(jìn)行異常表達(dá):

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

在 Rust 標(biāo)準(zhǔn)庫(kù)中可能產(chǎn)生異常的函數(shù)的返回值都是 Result 類型的。例如:當(dāng)我們嘗試打開(kāi)一個(gè)文件時(shí):

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 文件不存在,會(huì)打印 "Failed to open the file."。

當(dāng)然,我們?cè)诿杜e類章節(jié)講到的 if let 語(yǔ)法可以簡(jiǎn)化 match 語(yǔ)法塊:

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.");
    }
}

如果想使一個(gè)可恢復(fù)錯(cuò)誤按不可恢復(fù)錯(cuò)誤處理,Result 類提供了兩個(gè)辦法: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.");
}

這段程序相當(dāng)于在 Result 為 Err 時(shí)調(diào)用 panic! 宏。兩者的區(qū)別在于 expect 能夠向 panic! 宏發(fā)送一段指定的錯(cuò)誤信息。

可恢復(fù)的錯(cuò)誤的傳遞

之前所講的是接收到錯(cuò)誤的處理方式,但是如果我們自己編寫(xiě)一個(gè)函數(shù)在遇到錯(cuò)誤時(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");
    }
}

運(yùn)行結(jié)果:

Ok: f(-1) = 10000

這段程序中函數(shù) f 是錯(cuò)誤的根源,現(xiàn)在我們?cè)賹?xiě)一個(gè)傳遞錯(cuò)誤的函數(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)的錯(cuò)誤(這里的 g 只是一個(gè)簡(jiǎn)單的實(shí)例,實(shí)際上傳遞錯(cuò)誤的函數(shù)一般還包含很多其它操作)。

這樣寫(xiě)有些冗長(zhǎng),Rust 中可以在 Result 對(duì)象后添加 ? 操作符將同類的 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) // 因?yàn)榇_定 t 不是 Err, t 在這里已經(jīng)是 i32 類型
}

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

運(yùn)行結(jié)果:

Ok: g(10000) = 10000

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

kind 方法

到此為止,Rust 似乎沒(méi)有像 try 塊一樣可以令任何位置發(fā)生的同類異常都直接得到相同的解決的語(yǔ)法,但這樣并不意味著 Rust 實(shí)現(xiàn)不了:我們完全可以把 try 塊在獨(dú)立的函數(shù)中實(shí)現(xiàn),將所有的異常都傳遞出去解決。實(shí)際上這才是一個(gè)分化良好的程序應(yīng)當(dāng)遵循的編程方法:應(yīng)該注重獨(dú)立功能的完整性。

但是這樣需要判斷 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.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");
                }
            }
        }
    }
}

運(yùn)行結(jié)果:

No such file
丰满人妻一级特黄a大片,午夜无码免费福利一级,欧美亚洲精品在线,国产婷婷成人久久Av免费高清