欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

探索?Rust?中實(shí)用的錯(cuò)誤處理技巧

 更新時(shí)間:2024年01月25日 08:22:05   作者:和你一起去月球  
探索Rust中實(shí)用的錯(cuò)誤處理技巧!Rust是一門靜態(tài)類型系統(tǒng)安全且高效的編程語言,但使用過程中難免會遇到各種錯(cuò)誤,學(xué)會如何正確處理這些錯(cuò)誤至關(guān)重要,本指南將為您提供一些實(shí)用的錯(cuò)誤處理技巧,幫助您更好地編寫健壯的代碼,需要的朋友可以參考下

錯(cuò)誤是軟件中不可否認(rèn)的事實(shí),所以 Rust 有一些處理出錯(cuò)情況的特性。在許多情況下,Rust 要求你承認(rèn)錯(cuò)誤的可能性,并在你的代碼編譯前采取一些行動。這一要求使你的程序更加健壯,因?yàn)樗梢源_保你在將代碼部署到生產(chǎn)環(huán)境之前就能發(fā)現(xiàn)錯(cuò)誤并進(jìn)行適當(dāng)?shù)奶幚怼?/p>

Rust 將錯(cuò)誤分為兩大類:可恢復(fù)的(recoverable)和 不可恢復(fù)的(unrecoverable)錯(cuò)誤。對于一個(gè)可恢復(fù)的錯(cuò)誤,比如文件未找到的錯(cuò)誤,我們很可能只想向用戶報(bào)告問題并重試操作。不可恢復(fù)的錯(cuò)誤總是 bug 出現(xiàn)的征兆,比如試圖訪問一個(gè)超過數(shù)組末端的位置,因此我們要立即停止程序。

大多數(shù)語言并不區(qū)分這兩種錯(cuò)誤,并采用類似異常這樣方式統(tǒng)一處理它們。Rust 沒有異常。相反,它有 Result<T, E> 類型,用于處理可恢復(fù)的錯(cuò)誤,還有 panic! 宏,在程序遇到不可恢復(fù)的錯(cuò)誤時(shí)停止執(zhí)行。本章首先介紹 panic! 調(diào)用,接著會講到如何返回 Result<T, E>。此外,我們將探討在決定是嘗試從錯(cuò)誤中恢復(fù)還是停止執(zhí)行時(shí)的注意事項(xiàng)。

1、用 panic! 處理不可恢復(fù)的錯(cuò)誤

突然有一天,代碼出問題了,而你對此束手無策。對于這種情況,Rust 有 panic!宏。在實(shí)踐中有兩種方法造成 panic:執(zhí)行會造成代碼 panic 的操作(比如訪問超過數(shù)組結(jié)尾的內(nèi)容)或者顯式調(diào)用 panic! 宏。這兩種情況都會使程序 panic。通常情況下這些 panic 會打印出一個(gè)錯(cuò)誤信息,展開并清理?xiàng)?shù)據(jù),然后退出。通過一個(gè)環(huán)境變量,你也可以讓 Rust 在 panic 發(fā)生時(shí)打印調(diào)用堆棧(call stack)以便于定位 panic 的原因。

對應(yīng) panic 時(shí)的棧展開或終止

當(dāng)出現(xiàn) panic 時(shí),程序默認(rèn)會開始 展開(unwinding),這意味著 Rust 會回溯棧并清理它遇到的每一個(gè)函數(shù)的數(shù)據(jù),不過這個(gè)回溯并清理的過程有很多工作。另一種選擇是直接 終止(abort),這會不清理數(shù)據(jù)就退出程序。

那么程序所使用的內(nèi)存需要由操作系統(tǒng)來清理。如果你需要項(xiàng)目的最終二進(jìn)制文件越小越好,panic 時(shí)通過在 Cargo.toml 的 [profile] 部分增加 panic = 'abort',可以由展開切換為終止。例如,如果你想要在 release 模式中 panic 時(shí)直接終止:

[profile.release] panic = 'abort'

我們可以再程序中主動拋出一個(gè)錯(cuò)誤,如下圖所示:

fn main() {
    panic!("error error error ...")
}

 運(yùn)行一下程序,會打印如下信息:

通過上圖可以知道:

第一行顯示的是程序代碼發(fā)生錯(cuò)誤的位置,main.rs 的第二行第五列開始的。

第二行顯示的是panic!里面,我們自定義的錯(cuò)誤內(nèi)容。

第三行告訴我們可以使用 panic! 被調(diào)用的函數(shù)的 backtrace 來尋找代碼中出問題的地方。

1.1 使用 panic! 的 backtrace

讓我們來看看另一個(gè)因?yàn)槲覀兇a中的 bug 引起的別的庫中 panic! 的例子,而不是直接的宏調(diào)用。示例如下所示:

fn main() {
    let arr = [10, 20, 30, 40, 50];
    arr[100];
}

這里嘗試訪問 vector 的第一百個(gè)元素(這里的索引是 99 因?yàn)樗饕龔?0 開始),不過它只有三個(gè)元素。這種情況下 Rust 會 panic。[] 應(yīng)當(dāng)返回一個(gè)元素,不過如果傳遞了一個(gè)無效索引,就沒有可供 Rust 返回的正確的元素。

C 語言中,嘗試讀取數(shù)據(jù)結(jié)構(gòu)之后的值是未定義行為(undefined behavior)。你會得到任何對應(yīng)數(shù)據(jù)結(jié)構(gòu)中這個(gè)元素的內(nèi)存位置的值,甚至是這些內(nèi)存并不屬于這個(gè)數(shù)據(jù)結(jié)構(gòu)的情況。這被稱為 緩沖區(qū)溢出(buffer overread),并可能會導(dǎo)致安全漏洞,比如攻擊者可以像這樣操作索引來讀取儲存在數(shù)據(jù)結(jié)構(gòu)之后不被允許的數(shù)據(jù)。

為了保護(hù)程序遠(yuǎn)離這類漏洞,如果嘗試讀取一個(gè)索引不存在的元素,Rust 會停止執(zhí)行并拒絕繼續(xù)。嘗試運(yùn)行上面的程序會出現(xiàn)如下:

報(bào)錯(cuò):運(yùn)行時(shí)遇到panic錯(cuò)誤,在main.rs第三行第五列開始,索引超過邊界,長度為5,而索引值確實(shí)100。

讓我們將 RUST_BACKTRACE 環(huán)境變量設(shè)置為1 的值來獲取 backtrace 看看。

這是數(shù)組,報(bào)錯(cuò)比較簡單,如果其他數(shù)據(jù)結(jié)構(gòu)我們可以看一下結(jié)果,例如:string

fn main() {
    let arr = String::from("hello");
    arr[100];
}

 打印結(jié)果如下所示:

這里有大量的輸出!你實(shí)際看到的輸出可能因不同的操作系統(tǒng)和 Rust 版本而有所不同。為了獲取帶有這些信息的 backtrace,必須啟用 debug 標(biāo)識。當(dāng)不使用 --release 參數(shù)運(yùn)行 cargo build 或 cargo run 時(shí) debug 標(biāo)識會默認(rèn)啟用,就像這里一樣。

在上圖中,我們可以看到報(bào)錯(cuò)具體的文件以及對應(yīng)的行號,下面還有rust程序報(bào)錯(cuò)更加詳細(xì)的原因,這樣可以更快的為我們解決問題,提升自己的效率。

2、用 Result 處理可恢復(fù)的錯(cuò)誤

大部分錯(cuò)誤并沒有嚴(yán)重到需要程序完全停止執(zhí)行。有時(shí)候,一個(gè)函數(shù)失敗,僅僅就是因?yàn)橐粋€(gè)容易理解和響應(yīng)的原因。例如,如果因?yàn)榇蜷_一個(gè)并不存在的文件而失敗,此時(shí)我們可能想要創(chuàng)建這個(gè)文件,而不是終止進(jìn)程。

Result 枚舉,它定義有如下兩個(gè)成員,Ok 和 Err

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

T 和 E 是泛型類型參數(shù);現(xiàn)在你需要知道的就是 T 代表成功時(shí)返回的 Ok 成員中的數(shù)據(jù)的類型,而 E 代表失敗時(shí)返回的 Err 成員中的錯(cuò)誤的類型。因?yàn)?nbsp;Result 有這些泛型類型參數(shù),我們可以將 Result 類型和標(biāo)準(zhǔn)庫中為其定義的函數(shù)用于很多不同的場景,這些情況中需要返回的成功值和失敗值可能會各不相同。

讓我們調(diào)用一個(gè)返回 Result 的函數(shù),因?yàn)樗赡軙。嚎匆幌氯缦率纠?/p>

use std::fs::File;
fn main() {
    let file_result = File::open("hello.txt");
}

File::open 的返回值是 Result<T, E>。泛型參數(shù) T 會被 File::open 的實(shí)現(xiàn)放入成功返回值的類型 std::fs::File,這是一個(gè)文件句柄。錯(cuò)誤返回值使用的 E 的類型是 std::io::Error。這些返回類型意味著 File::open 調(diào)用可能成功并返回一個(gè)可以讀寫的文件句柄。這個(gè)函數(shù)調(diào)用也可能會失?。豪纾苍S文件不存在,或者可能沒有權(quán)限訪問這個(gè)文件。File::open 函數(shù)需要一個(gè)方法在告訴我們成功與否的同時(shí)返回文件句柄或者錯(cuò)誤信息。這些信息正好是 Result 枚舉所代表的。

當(dāng) File::open 成功時(shí),greeting_file_result 變量將會是一個(gè)包含文件句柄的 Ok 實(shí)例。當(dāng)失敗時(shí),greeting_file_result 變量將會是一個(gè)包含了更多關(guān)于發(fā)生了何種錯(cuò)誤的信息的 Err 實(shí)例。

這里使用match表達(dá)式來處理結(jié)果:

 
fn main() {
    let file_result = File::open("hello.txt");
    let res = match file_result {
        Ok(file) => file,
        Err(err) => panic!("打開文件發(fā)生錯(cuò)誤...{:?}", err),
    };
}

當(dāng)我們運(yùn)行以上代碼時(shí),看一下輸出結(jié)果如何:

2.1 匹配不同的錯(cuò)誤 

上面的代碼不管 File::open 是因?yàn)槭裁丛蚴《紩?nbsp;panic!。我們真正希望的是對不同的錯(cuò)誤原因采取不同的行為:如果 File::open 因?yàn)槲募淮嬖诙?,我們希望?chuàng)建這個(gè)文件并返回新文件的句柄。如果 File::open 因?yàn)槿魏纹渌蚴?,例如沒有打開文件的權(quán)限,我們可以通過不同分支把錯(cuò)誤提示的更加詳細(xì)。

fn main() {
    let file_result = File::open("hello.txt");
    match file_result {
        Ok(file) => file,
        Err(err) => match err.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(file) => file,
                Err(err) => panic!("創(chuàng)建文件失敗 {:?}", err),
            },
            other_error => panic!("其他錯(cuò)誤 {:?}", other_error),
        },
    };
}

File::open 返回的 Err 成員中的值類型 io::Error,它是一個(gè)標(biāo)準(zhǔn)庫中提供的結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體有一個(gè)返回 io::ErrorKind 值的 kind 方法可供調(diào)用。io::ErrorKind 是一個(gè)標(biāo)準(zhǔn)庫提供的枚舉,它的成員對應(yīng) io 操作可能導(dǎo)致的不同錯(cuò)誤類型。我們感興趣的成員是 ErrorKind::NotFound,它代表嘗試打開的文件并不存在。這樣,match 就匹配完 greeting_file_result 了,不過對于 error.kind() 還有一個(gè)內(nèi)層 match。

我們希望在內(nèi)層 match 中檢查的條件是 error.kind() 的返回值是否為 ErrorKind的 NotFound 成員。如果是,則嘗試通過 File::create 創(chuàng)建文件。然而因?yàn)?nbsp;File::create 也可能會失敗,還需要增加一個(gè)內(nèi)層 match 語句。當(dāng)文件不能被打開,會打印出一個(gè)不同的錯(cuò)誤信息。外層 match 的最后一個(gè)分支保持不變,這樣對任何除了文件不存在的錯(cuò)誤會使程序 panic。

2.2 失敗時(shí) panic 的簡寫:unwrap 和 expect

match 能夠勝任它的工作,不過它可能有點(diǎn)冗長并且不總是能很好的表明其意圖。Result<T, E> 類型定義了很多輔助方法來處理各種情況。其中之一叫做 unwrap,它的實(shí)現(xiàn)就像上個(gè)示例中的 match 語句。如果 Result 值是成員 Ok,unwrap 會返回 Ok 中的值。如果 Result 是成員 Err,unwrap 會為我們調(diào)用 panic!。這里是一個(gè)實(shí)踐 unwrap 的例子:

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

運(yùn)行一下這個(gè)程序,看下對應(yīng)的輸出:

還有另一個(gè)類似于 unwrap 的方法它還允許我們選擇 panic! 的錯(cuò)誤信息:expect。使用 expect 而不是 unwrap 并提供一個(gè)好的錯(cuò)誤信息可以表明你的意圖并更易于追蹤 panic 的根源。expect 的語法看起來像這樣:

fn main() {
    let file_result = File::open("hello.txt").expect("沒有讀取到文件");
}

expect 與 unwrap 的使用方式一樣:返回文件句柄或調(diào)用 panic! 宏。expect 在調(diào)用 panic! 時(shí)使用的錯(cuò)誤信息將是我們傳遞給 expect 的參數(shù),而不像 unwrap 那樣使用默認(rèn)的 panic! 信息。它看起來像這樣:

在生產(chǎn)級別的代碼中,大部分 Rustaceans 選擇 expect 而不是 unwrap 并提供更多關(guān)于為何操作期望是一直成功的上下文。

2.3 傳播錯(cuò)誤

當(dāng)編寫一個(gè)其實(shí)先會調(diào)用一些可能會失敗的操作的函數(shù)時(shí),除了在這個(gè)函數(shù)中處理錯(cuò)誤外,還可以選擇讓調(diào)用者知道這個(gè)錯(cuò)誤并決定該如何處理。這被稱為 傳播(propagating)錯(cuò)誤,這樣能更好的控制代碼調(diào)用,因?yàn)楸绕鹉愦a所擁有的上下文,調(diào)用者可能擁有更多信息或邏輯來決定應(yīng)該如何處理錯(cuò)誤。

例如,示例 9-6 展示了一個(gè)從文件中讀取用戶名的函數(shù)。如果文件不存在或不能讀取,這個(gè)函數(shù)會將這些錯(cuò)誤返回給調(diào)用它的代碼:

fn main() {
    fn read_file() -> Result<String, io::Error> {
        let file_result = File::open("hello.txt");
        let v = String::from("open file success ...");
        match file_result {
            Ok(_) => Ok(v),
            Err(err) => Err(err),
        }
    }
    let res = read_file();
    print!("{:?}", res)
}

這個(gè)函數(shù)可以編寫成更加簡短的形式,不過我們以大量手動處理開始以便探索錯(cuò)誤處理;在最后我們會展示更短的形式。讓我們看看函數(shù)的返回值:Result<String, io::Error>。這意味著函數(shù)返回一個(gè) Result<T, E> 類型的值,其中泛型參數(shù) T 的具體類型是 String,而 E 的具體類型是 io::Error

如果這個(gè)函數(shù)沒有出任何錯(cuò)誤成功返回,函數(shù)的調(diào)用者會收到一個(gè)包含 String 的 Ok 值 —— 函數(shù)從文件中讀取到的用戶名。如果函數(shù)遇到任何錯(cuò)誤,函數(shù)的調(diào)用者會收到一個(gè) Err 值,它儲存了一個(gè)包含更多這個(gè)問題相關(guān)信息的 io::Error 實(shí)例。

到此這篇關(guān)于探索 Rust 中實(shí)用的錯(cuò)誤處理技巧的文章就介紹到這了,更多相關(guān)Rust 錯(cuò)誤處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Rust利用tauri制作個(gè)效率小工具

    Rust利用tauri制作個(gè)效率小工具

    日常使用電腦中經(jīng)常會用到一個(gè)quicke工具中的輪盤菜單工具。但quicke免費(fèi)版很多功能不支持,且它的觸發(fā)邏輯用的不舒服,經(jīng)常誤觸。所以本文就來用tauri自制一個(gè)小工具,希望對大家有所幫助
    2023-02-02
  • Rust?所有權(quán)機(jī)制原理深入剖析

    Rust?所有權(quán)機(jī)制原理深入剖析

    這篇文章主要為大家介紹了Rust?所有權(quán)機(jī)制原理深入剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 在Rust中編寫自定義Error的詳細(xì)代碼

    在Rust中編寫自定義Error的詳細(xì)代碼

    Result<T, E> 類型可以方便地用于錯(cuò)誤傳導(dǎo),Result<T, E>是模板類型,實(shí)例化后可以是各種類型,但 Rust 要求傳導(dǎo)的 Result 中的 E 是相同類型的,所以我們需要編寫自己的 Error 類型,本文給大家介紹了在Rust中編寫自定義Error的詳細(xì)代碼,需要的朋友可以參考下
    2024-01-01
  • Rust使用csv crate構(gòu)建CSV文件讀取器的全過程

    Rust使用csv crate構(gòu)建CSV文件讀取器的全過程

    這篇文章主要學(xué)習(xí)如何基于Rust使用csv這個(gè)crate構(gòu)建一個(gè)CSV文件讀取器的過程,學(xué)習(xí)了csv相關(guān)的用法以及一些往期學(xué)過的crate的復(fù)習(xí),兼顧了實(shí)用性和Rust的學(xué)習(xí),需要的朋友可以參考下
    2024-05-05
  • Rust如何使用線程同時(shí)運(yùn)行代碼

    Rust如何使用線程同時(shí)運(yùn)行代碼

    Rust使用1:1線程模型,通過std::thread::spawn創(chuàng)建線程,返回JoinHandle用于等待線程完成,閉包默認(rèn)借用外部變量,使用move關(guān)鍵字轉(zhuǎn)移所有權(quán),多線程共享數(shù)據(jù)時(shí)需使用并發(fā)原語,如Mutex、RwLock、Arc等,以避免競態(tài)條件
    2025-02-02
  • 使用Rust語言管理Node.js版本

    使用Rust語言管理Node.js版本

    這篇文章主要介紹一個(gè)使用?Rust?進(jìn)行編寫的一體化版本管理工具?Rtx,比如使用它來管理?Node.js?版本,它很簡單易用,使用了它,就可以拋棄掉?nvm?了,文中通過代碼示例給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • rust生命周期詳解

    rust生命周期詳解

    生命周期是rust中用來規(guī)定引用的有效作用域,在大多數(shù)時(shí)候,無需手動聲明,因?yàn)榫幾g器能夠自動推導(dǎo),這篇文章主要介紹了rust生命周期相關(guān)知識,需要的朋友可以參考下
    2023-03-03
  • 深入了解Rust中函數(shù)與閉包的使用

    深入了解Rust中函數(shù)與閉包的使用

    本文主要介紹一下Rust函數(shù)相關(guān)的內(nèi)容,首先函數(shù)我們其實(shí)一直都在用,所以函數(shù)本身沒什么可說的,我們的重點(diǎn)是與函數(shù)相關(guān)的閉包、高階函數(shù)、發(fā)散函數(shù),感興趣的可以學(xué)習(xí)一下
    2022-11-11
  • 深入了解Rust的切片使用

    深入了解Rust的切片使用

    除了引用,Rust?還有另外一種不持有所有權(quán)的數(shù)據(jù)類型:切片(slice),切片允許我們引用集合中某一段連續(xù)的元素序列,而不是整個(gè)集合。本文讓我們來深入了解Rust的切片
    2022-11-11
  • Rust實(shí)現(xiàn)AES加解密詳解

    Rust實(shí)現(xiàn)AES加解密詳解

    這篇文章主要為大家詳細(xì)介紹了如何利用Rust語言實(shí)現(xiàn)AES加解密算法,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下
    2022-10-10

最新評論