Rust:panic- VS error

每個開發人員都知道,錯誤往往發生在應用程序開發過程中。錯誤處理是大多數編程語言中的一個基本概念,它可以幫助開發人員在程序交付給最終用戶之前發現並解決程序中的錯誤,從而改善用戶體驗。

在本文中,我們將探索 Rust 如何使開發人員能夠發現並處理代碼中的錯誤,明確 panic! 和 error 之間的區別,讓我們開始吧!

爲了更好地理解 Rust 中的錯誤,我們需要了解 Rust 如何對錯誤進行分組。與大多數不區分錯誤類型的編程語言不同,Rust 將錯誤分爲兩大類:可恢復錯誤和不可恢復錯誤。此外,Rust 沒有異常。它的類型爲 Result<T,E> 用於可恢復錯誤和 panic! 宏,當程序遇到不可恢復錯誤時停止進一步執行。

Result 和可恢復錯誤

在 Rust 中,大多數錯誤都屬於可恢復的類別。可恢復錯誤不會嚴重到導致程序突然停止或失敗,因爲它們可以很容易地處理或糾正。對於可恢復的錯誤,應該向用戶報告錯誤以重試操作或爲用戶提供替代操作方案。

一個很好的例子是當用戶輸入一個無效的電子郵件時。錯誤消息可能是類似於 “請輸入有效的電子郵件地址並重試” 之類的內容。Result 類型通常用於 Rust 函數中返回一個 Ok 成功值或一個錯誤 Err 值:

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

T 和 E 是泛型類型參數。Ok(T) 表示執行成功時返回幷包含一個值,而 Err(E) 表示執行錯誤並返回一個錯誤值。值得注意的是,當錯誤是預期的並且是可恢復的時,我們從函數返回 Result。

因爲 Result 有這些泛型類型參數,我們可以在許多不同的情況下使用 Result 類型和定義在它上面的函數,這些情況下我們想要返回的成功值和錯誤值可能不同:

use std::fs::File; 
fn main() { 
    let f = File::open("main.jpg");  
    //this file does not exist 
    println!("{:?}",f); 
} 

Err(Error { repr: Os { code: 2, message: "No such file or directory" } })

上面的代碼是一個打開指定文件的簡單程序。如果文件已經存在,程序返回 OK(File),如果文件沒有找到,則返回 Err。返回值類型爲 Result<T,E>,對於 E 是一個可恢復的錯誤,它允許我們訪問錯誤值,而不必中斷整個程序。

匹配不同的 error

讓我們回顧一下前面的代碼片段,爲了更好地調試程序,我們可能需要根據失敗的原因採取不同的步驟。例如,如果由於一個不存在的文件而導致代碼失敗,我們可以創建該文件並返回新文件。

如果 File::open 由於其他原因失敗,例如用戶沒有權限打開指定的文件,我們希望代碼 panic!。爲了實現這個結果,我們添加了一個內部匹配表達式:

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

fn main() {
    let logo_image = File::open("main.jpg");

    let logo_file = match logo_image {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("main.jpg") {
                Ok(fc) => fc,
                Err(e) => panic!("We encountered a problem creating this file: {:?}", e),},
            other_error ={
                panic!("There was a problem opening the specified file: {:?}", other_error);
            }
        },
    };
}

在上面的代碼片段中,我們導入了 io::ErrorKind,其中包含了來自 Rust 標準庫的結構體 io::Error。io::Error 是 Err 變量中 File::open 返回的值類型,它有一個叫做 kind 的方法。它包含幾個變體,表示 io 操作可能導致的不同類型的錯誤。在我們的例子中,我們得到 ErrorKind::NotFound,這意味着我們試圖打開的文件不存在。

該函數在 error.kind() 上也有一個內部匹配表達式。我們正在檢查條件,這意味着 error.kind() 返回的值是否爲 ErrorKind 枚舉的 NotFound 變體。如果滿足這個條件,則使用 file::create 創建一個名爲 main.jpg 的新文件。File::create 也可能失敗,因此我們爲內部匹配表達式添加了另一個選項。

如果 File::create 失敗,則打印不同的錯誤消息。外部匹配的第二個選項保持不變,這意味着當遇到 ErrorKind 枚舉的 NotFound 變體以外的任何錯誤時,程序將 panic!。

Result<T,E> 類型的輔助方法

還有其他 Result<T, E> 類型的輔助方法,可以使用它們來更好地傳達意圖。

unwrap

unwarp 方法更適合於特定的任務。在前面的例子中,我們可以使用 unwrap。如果該值是一個 Ok 變量,unwrap 將返回 Ok 內部的值。如果 Result 是 Err 變量,unwrap 將調用 panic! 宏。我們使用上面的例子來展示 unwrap 的實際操作:

use std::fs::File;

fn main() {
    let logo_image = File::open("main.jpg").unwrap();
}

如果我們在沒有 main.jpg 文件的情況下運行這段代碼,就會遇到執行 unwrap 方法時的 panic! 錯誤消息:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os {
code: 2, kind: NotFound, message: "No such file or directory" }',
src/main.rs:4:49

expect

expect 是另一個輔助方法,它的工作方式類似於 unwrap,但可以自定義 panic! 錯誤消息。讓我們看看 expect 的作用:

use std::fs::File;

fn main() {
    let logo_file = File::open("main.jpg")
        .expect("main.jpg file should be included in the project for the program to run");
}

在上面的代碼中,我們使用了 expect 而不是 unwrap。它返回文件句柄或調用 panic! 宏,以防出現錯誤。不同之處在於可以將錯誤消息傳遞給 expect 的參數,而不是使用默認的 panic! 消息。

讓我們看看輸出,以防我們調用一個不存在的 main.jpg 文件:

thread 'main' panicked at 'main.jpg file should be included in the project for the 
program to run: Os {
code: 2, kind: NotFound, message: "No such file or directory" }',
src/main.rs:5:10

panic! 宏和不可恢復錯誤

不可恢復的錯誤,即無法處理的錯誤,通常是 bug 的結果。當這些錯誤發生時,不可恢復的錯誤會導致程序突然失敗。此外,程序不能恢復到正常狀態、重試失敗或不能撤消錯誤。不可恢復錯誤的一個例子是試圖訪問數組結尾以外的位置。

在 Rust 中,panic! 是一個宏,它在遇到不可恢復錯誤時終止程序。簡單來說,就是 panic! 宏允許程序立即終止,並向程序的調用者提供反饋。

如何觸發 panic!

有兩種方法可以引發 panic!,第一個方法是在 Rust 程序中手動觸發 panic!。第二種是通過執行操作導致代碼 panic!。

在 Rust 程序中要手動觸發 panic!,我們使用 panic! 宏。像觸發一個函數一樣調用它:

fn main() { 
    panic!("panic vs. error in rust"); // because of panic, the program will terminate 
    //immediately.  
    println!("I am the unreachable statement ");  
}

在上面的例子中,我們包含了 panic! 宏。一旦程序遇到它,它會立即終止。

下面是一個引起 panic! 的條件語句:

fn main() { 
  let no = 112;  
  //try with no above 100 and below 100  
  if no>=100 { 
      println!("Thank you for inserting your number, Registration is successful "); 
  } else { 
      panic!("NUMBER_TOO_SHORT");  
  } 
  println!("End of main"); 
}

下面的例子顯示了 panic! 的一個實例,panic! 錯誤是直接從標準庫中調用產生的,而不是手動調用。下面的代碼嘗試訪問 vector 中的索引,由於向量超出了有效下標的範圍而產生了 panic!:

fn main() { 
    let f = vec![1, 2, 3]; 
    v[40]; 
}

該程序試圖訪問向量的第 41 個元素 (索引爲 40 的向量),但該向量只有 3 個元素。在這種情況下,Rust 會 panic!:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 40', hello.rs:15:5 
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

恢復或停止執行

當你決定你的代碼是否應該 panic!,我們需要考慮代碼最終處於糟糕狀態的可能性。壞狀態是指某些假設、保證、契約或不變量被打破,例如無效值、矛盾值或缺失值被傳遞到你的代碼中。

壞狀態是指一些意料之外的事情,而不是偶爾可能發生的事情,比如用戶以錯誤的格式輸入數據。當用戶向代碼傳遞沒有意義的值時,建議在可能的情況下返回錯誤。這讓用戶可以選擇下一步應該採取什麼行動。

然而,在某些情況下,繼續下去可能是有害的或不安全的。在這種情況下,最好呼叫 panic! 並提醒用戶存在錯誤,提示他們在開發階段進行修復。

另一個適合呼叫 panic! 情況可能是當你調用外部代碼時,你無法控制它是否返回一個無效的狀態,而且你沒有辦法修復。

查看堆棧跟蹤或反向跟蹤

類似於其他編程語言,panic! 提供堆棧跟蹤或反向跟蹤一個錯誤。但是,除非 RUST_BACKTRACE 環境變量被設置爲不同於零的值,否則在終端上不會顯示反向跟蹤。因此,如果在前面的代碼示例中執行以下命令語句:

RUST_BACKTRACE=1 ./hello
RUST_BACKTRACE=1 cargo run 
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 40', hello.rs:15:5 
stack backtrace: 
0: rust_begin_unwind 
at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/std/src/panicking.rs:584:5 
1: core::panicking::panic_fmt 
at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/panicking.rs:142:14 
2: core::panicking::panic_bounds_check 
at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/panicking.rs:84:5 
3: <usize as core::slice::index::SliceIndex<[T]>>::index 
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index 
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index 
6: hello::main 
7: core::ops::function::FnOnce::call_once 
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

總結

總之,Rust 有兩種錯誤,一種是 Result 類型返回的錯誤值,另一種是觸發 panic! 宏產生的錯誤。Result 類型返回一個可恢復的錯誤,換句話說,一個不會導致程序終止的錯誤。

另一方面,panic! 宏會觸發一個不可恢復的錯誤,導致 Rust 程序立即終止。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/iLLDBtVw6PlY2SR4QUlc3g