日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Rust使用kind進行異常處理(錯誤的分類與傳遞)_相關技巧

作者:微涼秋意 ? 更新時間: 2022-11-15 編程語言

前言

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', src\main.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/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

回溯是不可恢復錯誤的另一種處理方式,它會展開運行的棧并輸出所有的信息,然后程序依然會退出。通過大量的輸出信息,我們可以找到我們編寫的 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

原文鏈接:https://blog.csdn.net/m0_58618795/article/details/127034383

欄目分類
最近更新