rust學習十、異常處理(錯誤處理)

正在战斗中發表於2024-11-19

在書籍中,中文譯者翻譯為錯誤,這是因為原文是"Error"。

但在很多語言中,都是書寫為異常。

一、概述

rust的錯誤處理與眾不同,前文已經提及:大家稱為異常,它稱為錯誤。

不可恢復的錯誤,可以大體稱為panic(恐慌)! 太率性了....

那麼我們的問題是:除了一些的確不可處理的異常,rust是否也和大部分語言那樣使用try catch來捕獲和跳過?

所謂的不可恢復錯誤,其實也可以被try catch處理?

二、用panic!宏處理錯誤

不可恢復錯誤發生的時候的通常表現:會列印出一個錯誤資訊,展開並清理棧資料,然後退出

透過一個環境變數,可以讓 Rust 在 panic 發生時列印呼叫堆疊(call stack)以便於定位 panic 的原因.

兩種退出方式

  1. abort- 粗暴退出,讓作業系統收拾殘局
  2. 回溯棧並清理它遇到的每一個函式的資料。如果需要檢視堆疊,可以把環境變數RUST_BACKTRACE設定為不是0即可

設定退出方式

Cargo.toml中設定,如下:

[profile.release]
panic = 'abort'

上文中,panic還可以是設定為unwind,這是預設的

上例是release,如果想在除錯的時候,那麼可以新增profile.dev

更多的配置,可以參考 https://www.rustwiki.org.cn/zh-CN/cargo/index.html

觸發方式

  1. 直接呼叫panic!宏
  2. 一些可能導致不可恢復的錯誤,例如除以0,越絕訪問向量

示例一、直接觸發

輸出一大堆!!!

示例二、越界訪問

輸出太多,擷取了比較又意義的部分:已經足夠明白那裡發生錯誤的。

毫無疑問,對於第二種情況,在其它語言種,一般簡單try catch就可以處理了!

三、用Result處理可恢復錯誤

Resutt<T,E>是什麼

一個列舉型別,類似Option。

有兩個成員Ok,Err

和Option的Some,None一樣,Ok,Err也是可以直接使用,不需要書寫字首,因為它們都在prelude中匯入了。

注意:prelude是序章的意思,可以理解為rust為每一個應用自動匯入的部分。

處理Result,處理錯誤型別

由於Result是Option,所以,可以這樣使用:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {e:?}"),
            },
            other_error => {
                panic!("Problem opening the file: {other_error:?}");
            }
        },
    };
}

或者

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            })
        } else {
            panic!("Problem opening the file: {:?}", error);
        }
    });
}

失敗的兩種處理:unwrap,expect

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap();
    let greeting_file2 = File::open("hello.txt")
        .expect("hello.txt should be included in this project");
}

unwrap和expect的區別在於:後者會在觸發錯誤的時候,直接列印引數值。

expect方法內部大體是這樣的:

    #[inline]
    #[track_caller]
    #[stable(feature = "result_expect", since = "1.4.0")]
    pub fn expect(self, msg: &str) -> T
    where
        E: fmt::Debug,
    {
        match self {
            Ok(t) => t,
            Err(e) => unwrap_failed(msg, &e),
        }
    }

邏輯:正常就返回值、不正常的則返回一個異常(不可恢復的錯誤,退出)。

3.1傳播錯誤-如何處理錯誤

在大部分的語言中,有兩種方式:繼續丟擲或者捕獲之後做其它處理,例如java就是這樣處理的。

這個內容比較多,所以單獨一個小章節說明:

  1. 常規處理-根據result結果返回需要的內容
  2. 使用?簡化處理
  3. 不是任何地方都可以用?
  4. 讓main()函式可以支援?
  5. Box<dyn Error>

常規處理-這個沒有什麼好說的。

使用?簡化處理

1.正常程式碼

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let username_file_result = File::open("hello.txt");

    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut username = String::new();

    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(e) => Err(e),
    }
}

2.簡化程式碼-1

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;
    let mut username = String::new();
    username_file.read_to_string(&mut username)?;
    Ok(username)
}

?這裡表示 match xxxx 一段內容。如果出現異常,已經退出了。

3. 繼續簡化的程式碼_2

use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();
    File::open("hello.txt")?.read_to_string(&mut username)?;
    Ok(username)
}

這裡主要使用鏈式寫法簡化了。毫無疑問,這個鏈式的還是比較受到歡迎的!!!

哪裡不能用?

函式的返回值不同於?

最典型例子:

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")?;
}

這是因為main的返回型別是() -- rust的什麼都奇怪的很,雖然最核心大家都差不多。

讓main支援?

有什麼驚人之舉嗎?沒有,就是修改返回型別。

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let greeting_file = File::open("hello.txt")?;

    Ok(())
}

四、要不要panic!宏

原文說了一堆口水話,對於有經驗的工程師而言,沒有什麼價值。略!

五、小結

  1. rust的錯誤處理和其它部分一樣,力圖做到與眾不同- Error,panic!、Ok、Err、?、Box 這是憑空多出來的一些新概念(老東西換新的名稱)
  2. 暫時沒有介紹如何讓所謂的不可恢復錯誤程式設計可以忽略的異常 --類似越界訪問,在其它語言再正常不過了
  3. 利用Resut和?某種程度上,會讓程式碼看起來比一些語言好看一些(僅僅是好看而已)
  4. 注意異常處理原則。 或者注意團隊中的統一處理原則
  5. 一個最重要的內容本章並沒有提到:發生致命錯誤的時候,如何避免退出。這種要求十分常見,例如越界訪問。但這個應該是一定有的,只是本章並沒有提到。

相關文章