目錄
1、用 Result 處理可恢復(fù)的錯誤
1.1?傳播錯誤的簡寫:? 運(yùn)算符
1.2 哪里可以使用 ? 運(yùn)算符
2、要不要 panic!
2.1?示例、代碼原型和測試都非常適合 panic
2.2?當(dāng)我們比編譯器知道更多的情況
2.3?錯誤處理指導(dǎo)原則
2.4?創(chuàng)建自定義類型進(jìn)行有效性驗(yàn)證
2.5 總結(jié)
1、用 Result 處理可恢復(fù)的錯誤
1.1?傳播錯誤的簡寫:? 運(yùn)算符
先看下如下示例:
fn main() {
fn read_file() -> Result<String, io::Error> {
let file_result = File::open("hello.txt");
let mut v = String::new();
file_result.unwrap().read_to_string(&mut v)?;
Ok(v)
}
let res = read_file();
print!("{:?}", res)
}
Result
?值之后的??
?被定義為之前的處理?Result
?值的?match
?表達(dá)式有著完全相同的工作方式。如果?Result
?的值是?Ok
,這個表達(dá)式將會返回?Ok
?中的值而程序?qū)⒗^續(xù)執(zhí)行。如果值是?Err
,Err
?將作為整個函數(shù)的返回值,就好像使用了?return
?關(guān)鍵字一樣,這樣錯誤值就被傳播給了調(diào)用者。
之前示例的?match
?表達(dá)式與??
?運(yùn)算符所做的有一點(diǎn)不同:?
?運(yùn)算符所使用的錯誤值被傳遞給了?from
?函數(shù),它定義于標(biāo)準(zhǔn)庫的?From
?trait 中,其用來將錯誤從一種類型轉(zhuǎn)換為另一種類型。當(dāng)??
?運(yùn)算符調(diào)用?from
?函數(shù)時,收到的錯誤類型被轉(zhuǎn)換為由當(dāng)前函數(shù)返回類型所指定的錯誤類型。這在當(dāng)函數(shù)返回單個錯誤類型來代表所有可能失敗的方式時很有用,即使其可能會因很多種原因失敗。
?
?運(yùn)算符消除了大量樣板代碼并使得函數(shù)的實(shí)現(xiàn)更簡單。我們甚至可以在??
?之后直接使用鏈?zhǔn)椒椒ㄕ{(diào)用來進(jìn)一步縮短代碼,如下所示:
fn main() {
fn read_file() -> Result<String, io::Error> {
let mut v = String::new();
let _ = File::open("hello.txt")?.read_to_string(&mut v)?;
Ok(v)
}
let res = read_file();
print!("{:?}", res)
}
以下代碼展示了一個使用?fs::read_to_string
?的更為簡短的寫法:
fn main() {
fn read_file() -> Result<String, io::Error> {
fs::read_to_string("hello1.txt")
}
let res = read_file();
print!("{:?}", res)
}
將文件讀取到一個字符串是相當(dāng)常見的操作,所以 Rust 提供了名為?fs::read_to_string
?的函數(shù),它會打開文件、新建一個?String
、讀取文件的內(nèi)容,并將內(nèi)容放入?String
,接著返回它。當(dāng)然,這樣做就沒有展示所有這些錯誤處理的機(jī)會了.
1.2 哪里可以使用 ? 運(yùn)算符
?
?運(yùn)算符只能被用于返回值與??
?作用的值相兼容的函數(shù)。因?yàn)??
?運(yùn)算符被定義為從函數(shù)中提早返回一個值,這與之前示例的?match
?表達(dá)式有著完全相同的工作方式。示例中?match
?作用于一個?Result
?值,提早返回的分支返回了一個?Err(e)
?值。函數(shù)的返回值必須是?Result
?才能與這個?return
?相兼容。
讓我們看看在返回值不兼容的?main
?函數(shù)中使用??
?運(yùn)算符會得到什么錯誤:
fn main() {
fs::read_to_string("hello1.txt")?;
}
這段代碼打開一個文件,這可能會失敗。?
?運(yùn)算符作用于?File::open
?返回的?Result
?值,不過?main
?函數(shù)的返回類型是?()
?而不是?Result
。當(dāng)編譯這些代碼,會得到如下錯誤信息:?
這個錯誤指出只能在返回?Result
?或者其它實(shí)現(xiàn)了?FromResidual
?的類型的函數(shù)中使用??
?運(yùn)算符。
為了修復(fù)這個錯誤,有兩個選擇。一個是,如果沒有限制的話將函數(shù)的返回值改為?Result<T, E>
。另一個是使用?match
?或?Result<T, E>
?的方法中合適的一個來處理?Result<T, E>
。
錯誤信息也提到??
?也可用于?Option<T>
?值。如同對?Result
?使用??
?一樣,只能在返回?Option
?的函數(shù)中對?Option
?使用??
。在?Option<T>
?上調(diào)用??
?運(yùn)算符的行為與?Result<T, E>
?類似:如果值是?None
,此時?None
?會從函數(shù)中提前返回。如果值是?Some
,Some
?中的值作為表達(dá)式的返回值同時函數(shù)繼續(xù)。
文本中返回第一行最后一個字符的函數(shù)的例子:
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
這個函數(shù)返回?Option<char>
?因?yàn)樗赡軙谶@個位置找到一個字符,也可能沒有字符。這段代碼獲取?text
?字符串 slice 作為參數(shù)并調(diào)用其?lines
?方法,這會返回一個字符串中每一行的迭代器。因?yàn)楹瘮?shù)希望檢查第一行,所以調(diào)用了迭代器?next
?來獲取迭代器中第一個值。如果?text
?是空字符串,next
?調(diào)用會返回?None
,此時我們可以使用??
?來停止并從?last_char_of_first_line
?返回?None
。如果?text
?不是空字符串,next
?會返回一個包含?text
?中第一行的字符串 slice 的?Some
?值。
?
?會提取這個字符串 slice,然后可以在字符串 slice 上調(diào)用?chars
?來獲取字符的迭代器。我們感興趣的是第一行的最后一個字符,所以可以調(diào)用?last
?來返回迭代器的最后一項。這是一個?Option
,因?yàn)橛锌赡艿谝恍惺且粋€空字符串,例如?text
?以一個空行開頭而后面的行有文本,像是?"\nhi"
。不過,如果第一行有最后一個字符,它會返回在一個?Some
?成員中。?
?運(yùn)算符作用于其中給了我們一個簡潔的表達(dá)這種邏輯的方式。如果我們不能在?Option
?上使用??
?運(yùn)算符,則不得不使用更多的方法調(diào)用或者?match
?表達(dá)式來實(shí)現(xiàn)這些邏輯。
注意你可以在返回?Result
?的函數(shù)中對?Result
?使用??
?運(yùn)算符,可以在返回?Option
?的函數(shù)中對?Option
?使用??
?運(yùn)算符,但是不可以混合搭配。?
?運(yùn)算符不會自動將?Result
?轉(zhuǎn)化為?Option
,反之亦然;在這些情況下,可以使用類似?Result
?的?ok
?方法或者?Option
?的?ok_or
?方法來顯式轉(zhuǎn)換。
目前為止,我們所使用的所有?main
?函數(shù)都返回?()
。main
?函數(shù)是特殊的因?yàn)樗强蓤?zhí)行程序的入口點(diǎn)和退出點(diǎn),為了使程序能正常工作,其可以返回的類型是有限制的。
幸運(yùn)的是?main
?函數(shù)也可以返回?Result<(), E>
,以下示例?main
?的返回值為?Result<(), Box<dyn Error>>
?并在結(jié)尾增加了一個?Ok(())
?作為返回值。這段代碼可以編譯:
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello1.txt")?;
Ok(())
}
Box<dyn Error>
?類型是一個?trait 對象(trait object)
目前可以將?Box<dyn Error>
?理解為 “任何類型的錯誤”。在返回?Box<dyn Error>
?錯誤類型?main
?函數(shù)中對?Result
?使用??
?是允許的,因?yàn)樗试S任何?Err
?值提前返回。即便?main
?函數(shù)體從來只會返回?std::io::Error
?錯誤類型,通過指定?Box<dyn Error>
,這個簽名也仍是正確的,甚至當(dāng)?main
?函數(shù)體中增加更多返回其他錯誤類型的代碼時也是如此。
當(dāng)?main
?函數(shù)返回?Result<(), E>
,如果?main
?返回?Ok(())
?可執(zhí)行程序會以?0
?值退出,而如果?main
?返回?Err
?值則會以非零值退出;成功退出的程序會返回整數(shù)?0
,運(yùn)行錯誤的程序會返回非?0
?的整數(shù)。Rust 也會從二進(jìn)制程序中返回與這個慣例相兼容的整數(shù)。
main
?函數(shù)也可以返回任何實(shí)現(xiàn)了?std::process::Termination?trait?的類型,它包含了一個返回?ExitCode
?的?report
?函數(shù)。
2、要不要 panic!
那么,該如何決定何時應(yīng)該?panic!
?以及何時應(yīng)該返回?Result
?呢?如果代碼 panic,就沒有恢復(fù)的可能。你可以選擇對任何錯誤場景都調(diào)用?panic!
,不管是否有可能恢復(fù),不過這樣就是你代替調(diào)用者決定了這是不可恢復(fù)的。選擇返回?Result
?值的話,就將選擇權(quán)交給了調(diào)用者,而不是代替他們做出決定。調(diào)用者可能會選擇以符合他們場景的方式嘗試恢復(fù),或者也可能干脆就認(rèn)為?Err
?是不可恢復(fù)的,所以他們也可能會調(diào)用?panic!
?并將可恢復(fù)的錯誤變成了不可恢復(fù)的錯誤。因此返回?Result
?是定義可能會失敗的函數(shù)的一個好的默認(rèn)選擇。
在一些類似示例、原型代碼(prototype code)和測試中,panic 比返回?Result
?更為合適,不過它們并不常見。讓我們討論一下為何在示例、代碼原型和測試中,以及那些人們認(rèn)為不會失敗而編譯器不這么看的情況下,panic 是合適的。
2.1?示例、代碼原型和測試都非常適合 panic
當(dāng)你編寫一個示例來展示一些概念時,在擁有健壯的錯誤處理代碼的同時也會使得例子不那么明確。例如,調(diào)用一個類似?unwrap
?這樣可能?panic!
?的方法可以被理解為一個你實(shí)際希望程序處理錯誤方式的占位符,它根據(jù)其余代碼運(yùn)行方式可能會各不相同。
類似地,在我們準(zhǔn)備好決定如何處理錯誤之前,unwrap
和expect
方法在原型設(shè)計時非常方便。當(dāng)我們準(zhǔn)備好讓程序更加健壯時,它們會在代碼中留下清晰的標(biāo)記。
如果方法調(diào)用在測試中失敗了,我們希望這個測試都失敗,即便這個方法并不是需要測試的功能。因?yàn)?panic!
?會將測試標(biāo)記為失敗,此時調(diào)用?unwrap
?或?expect
?是恰當(dāng)?shù)摹?/p>
2.2?當(dāng)我們比編譯器知道更多的情況
當(dāng)你有一些其他的邏輯來確保?Result
?會是?Ok
?值時,調(diào)用?unwrap
?或者?expect
?也是合適的,雖然編譯器無法理解這種邏輯。你仍然需要處理一個?Result
?值:即使在你的特定情況下邏輯上是不可能的,你所調(diào)用的任何操作仍然有可能失敗。如果通過人工檢查代碼來確保永遠(yuǎn)也不會出現(xiàn)?Err
?值,那么調(diào)用?unwrap
?也是完全可以接受的,這里是一個例子:
fn main() {
let home: IpAddr = "127.0.0.1111".parse().expect("IP address error......");
panic!("{:?}", home)
}
我們通過解析一個硬編碼的字符來創(chuàng)建一個?IpAddr
?實(shí)例??梢钥闯?127.0.0.1
?是一個有效的 IP 地址,所以這里使用?expect
?是可以接受的。然而,擁有一個硬編碼的有效的字符串也不能改變?parse
?方法的返回值類型:它仍然是一個?Result
?值,而編譯器仍然會要求我們處理這個?Result
,好像還是有可能出現(xiàn)?Err
?成員那樣。這是因?yàn)榫幾g器還沒有智能到可以識別出這個字符串總是一個有效的 IP 地址。如果 IP 地址字符串來源于用戶而不是硬編碼進(jìn)程序中的話,那么就?確實(shí)?有失敗的可能性,這時就絕對需要我們以一種更健壯的方式處理?Result
?了。提及這個 IP 地址是硬編碼的假設(shè)會促使我們將來把?expect
?替換為更好的錯誤處理,我們應(yīng)該從其它代碼獲取 IP 地址。
2.3?錯誤處理指導(dǎo)原則
在當(dāng)有可能會導(dǎo)致有害狀態(tài)的情況下建議使用?panic!
?—— 在這里,有害狀態(tài)是指當(dāng)一些假設(shè)、保證、協(xié)議或不可變性被打破的狀態(tài),例如無效的值、自相矛盾的值或者被傳遞了不存在的值 —— 外加如下幾種情況:
- 有害狀態(tài)是非預(yù)期的行為,與偶爾會發(fā)生的行為相對,比如用戶輸入了錯誤格式的數(shù)據(jù)。
- 在此之后代碼的運(yùn)行依賴于不處于這種有害狀態(tài),而不是在每一步都檢查是否有問題。
- 沒有可行的手段來將有害狀態(tài)信息編碼進(jìn)所使用的類型中的情況。
如果別人調(diào)用你的代碼并傳遞了一個沒有意義的值,盡最大可能返回一個錯誤,如此庫的用戶就可以決定在這種情況下該如何處理。然而在繼續(xù)執(zhí)行代碼是不安全或有害的情況下,最好的選擇可能是調(diào)用?panic!
?并警告庫的用戶他們的代碼中有 bug,這樣他們就會在開發(fā)時進(jìn)行修復(fù)。類似的,如果你正在調(diào)用不受你控制的外部代碼,并且它返回了一個你無法修復(fù)的無效狀態(tài),那么?panic!
?往往是合適的。
然而當(dāng)錯誤預(yù)期會出現(xiàn)時,返回?Result
?仍要比調(diào)用?panic!
?更為合適。這樣的例子包括解析器接收到格式錯誤的數(shù)據(jù),或者 HTTP 請求返回了一個表明觸發(fā)了限流的狀態(tài)。在這些例子中,應(yīng)該通過返回?Result
?來表明失敗預(yù)期是可能的,這樣將有害狀態(tài)向上傳播,調(diào)用者就可以決定該如何處理這個問題。使用?panic!
?來處理這些情況就不是最好的選擇。
當(dāng)你的代碼在進(jìn)行一個使用無效值進(jìn)行調(diào)用時可能將用戶置于風(fēng)險中的操作時,代碼應(yīng)該首先驗(yàn)證值是有效的,并在其無效時?panic!
。這主要是出于安全的原因:嘗試操作無效數(shù)據(jù)會暴露代碼漏洞,這就是標(biāo)準(zhǔn)庫在嘗試越界訪問數(shù)組時會?panic!
?的主要原因:嘗試訪問不屬于當(dāng)前數(shù)據(jù)結(jié)構(gòu)的內(nèi)存是一個常見的安全隱患。函數(shù)通常都遵循?契約(contracts):它們的行為只有在輸入滿足特定條件時才能得到保證。當(dāng)違反契約時 panic 是有道理的,因?yàn)檫@通常代表調(diào)用方的 bug,而且這也不是那種你希望所調(diào)用的代碼必須處理的錯誤。事實(shí)上所調(diào)用的代碼也沒有合理的方式來恢復(fù),而是需要調(diào)用方的?程序員?修復(fù)其代碼。函數(shù)的契約,尤其是當(dāng)違反它會造成 panic 的契約,應(yīng)該在函數(shù)的 API 文檔中得到解釋。
雖然在所有函數(shù)中都擁有許多錯誤檢查是冗長而煩人的。幸運(yùn)的是,可以利用 Rust 的類型系統(tǒng)(以及編譯器的類型檢查)為你進(jìn)行很多檢查。如果函數(shù)有一個特定類型的參數(shù),可以在知曉編譯器已經(jīng)確保其擁有一個有效值的前提下進(jìn)行你的代碼邏輯。例如,如果你使用了一個并不是?Option
?的類型,則程序期望它是?有值?的并且不是?空值。你的代碼無需處理?Some
?和?None
?這兩種情況,它只會有一種情況就是絕對會有一個值。嘗試向函數(shù)傳遞空值的代碼甚至根本不能編譯,所以你的函數(shù)在運(yùn)行時沒有必要判空。另外一個例子是使用像?u32
?這樣的無符號整型,也會確保它永遠(yuǎn)不為負(fù)。
2.4?創(chuàng)建自定義類型進(jìn)行有效性驗(yàn)證
讓我們使用 Rust 類型系統(tǒng)的思想來進(jìn)一步確保值的有效性,并嘗試創(chuàng)建一個自定義類型以進(jìn)行驗(yàn)證?;貞浺幌碌诙碌牟虏驴从螒颍覀兊拇a要求用戶猜測一個 1 到 100 之間的數(shù)字,在將其與秘密數(shù)字做比較之前我們從未驗(yàn)證用戶的猜測是位于這兩個數(shù)字之間的,我們只驗(yàn)證它是否為正。在這種情況下,其影響并不是很嚴(yán)重:“Too high” 或 “Too low” 的輸出仍然是正確的。但是這是一個很好的引導(dǎo)用戶得出有效猜測的輔助,例如當(dāng)用戶猜測一個超出范圍的數(shù)字或者輸入字母時采取不同的行為。
一種實(shí)現(xiàn)方式是將猜測解析成?i32
?而不僅僅是?u32
,來默許輸入負(fù)數(shù),接著檢查數(shù)字是否在范圍內(nèi):文章來源:http://www.zghlxwxcb.cn/news/detail-804343.html
loop {
// --snip--
let guess: i32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
if guess < 1 || guess > 100 {
println!("The secret number will be between 1 and 100.");
continue;
}
match guess.cmp(&secret_number) {
// --snip--
}
2.5 總結(jié)
Rust 的錯誤處理功能被設(shè)計為幫助你編寫更加健壯的代碼。panic!
?宏代表一個程序無法處理的狀態(tài),并停止執(zhí)行而不是使用無效或不正確的值繼續(xù)處理。Rust 類型系統(tǒng)的?Result
?枚舉代表操作可能會在一種可以恢復(fù)的情況下失敗。可以使用?Result
?來告訴代碼調(diào)用者他需要處理潛在的成功或失敗。在適當(dāng)?shù)膱鼍笆褂?panic!
?和?Result
?將會使你的代碼在面對不可避免的錯誤時顯得更加可靠。文章來源地址http://www.zghlxwxcb.cn/news/detail-804343.html
到了這里,關(guān)于Rust 錯誤處理(下)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!