鍍金池/ 教程/ Java/ 錯(cuò)誤處理
While 循環(huán)
宏命令
模式
Rust 嵌入到其他語(yǔ)言
變量綁定
if
發(fā)布通道
Lang 項(xiàng)目
匹配
文檔
棧和堆
不依賴 stdlib
原始指針
條件編譯
type 別名
關(guān)聯(lián)類型
全類型
詞匯表
基本類型
Hello, world!
測(cè)試
箱和模塊
字符串
向量
引用與借用
所有權(quán)
內(nèi)斂函數(shù)
基準(zhǔn)測(cè)試
Nightly Rust
for 循環(huán)
特征
特征的對(duì)象
鏈接參數(shù)
介紹
'Deref'強(qiáng)制轉(zhuǎn)換
枚舉
內(nèi)聯(lián)匯編
泛型
方法語(yǔ)法
函數(shù)
外部函數(shù)接口
盒語(yǔ)法和模式
安裝 Rust
unsafe    
生存期
切片模式
Borrow 和 AsRef
If let
學(xué)習(xí) Rust
“常量”和“靜態(tài)”
語(yǔ)法和語(yǔ)義
迭代器
相關(guān)學(xué)術(shù)研究
通用函數(shù)調(diào)用語(yǔ)法
哲學(xué)家就餐問題
類型轉(zhuǎn)換
閉包
并發(fā)性
Hello, Cargo!
屬性
注釋
結(jié)構(gòu)體
編譯器插件
高效 Rust
相關(guān)常量
猜謎游戲
可變性
錯(cuò)誤處理
新手入門
操作符和重載

錯(cuò)誤處理

不管是人是鼠,即使最如意的安排設(shè)計(jì),結(jié)局也往往會(huì)出其不意。 《致老鼠》 羅伯特·彭斯

有時(shí)候,事情會(huì)出乎意料的發(fā)生錯(cuò)誤。重要的是要提前想好應(yīng)對(duì)錯(cuò)誤的方法。Rust 有豐富的支持錯(cuò)誤處理方法來應(yīng)對(duì)可能(老實(shí)說:將會(huì))發(fā)生在您的程序中的錯(cuò)誤。

主要有兩種類型的錯(cuò)誤可能發(fā)生在你的程序中:故障和異常。讓我們談?wù)剝烧咧g的區(qū)別,然后討論如何處理它們。接著,將討論如何將故障升級(jí)為異常。

故障 VS 異常

Rust 使用兩個(gè)術(shù)語(yǔ)來區(qū)分兩種形式的錯(cuò)誤:故障和異常。故障是可以用某種方式中恢復(fù)的錯(cuò)誤。異常是一種不能恢復(fù)的錯(cuò)誤。

我們說的 ”恢復(fù)“ 是什么意思?嗯,在大多數(shù)情況下,指的是預(yù)計(jì)一個(gè)錯(cuò)誤的可能性。例如,考慮 parse 函數(shù):

"5".parse();

這個(gè)方法將一個(gè)字符串轉(zhuǎn)換成另一種類型。但因?yàn)樗且粋€(gè)字符串,你不能確保轉(zhuǎn)換工作正常執(zhí)行。例如,執(zhí)行如下的轉(zhuǎn)換會(huì)得到什么?

"hello5world".parse();

這是行不通的。所以我們知道,這個(gè)函數(shù)只會(huì)對(duì)一些特定的輸入才能正常工作。這是預(yù)期行為。我們稱這種錯(cuò)誤為故障。

另一方面,有時(shí),有意想不到的錯(cuò)誤,或者我們不能恢復(fù)它。一個(gè)典型的例子是一個(gè)斷言:

assert!(x == 5);

我們使用 assert! 說明參數(shù)是正確的。如果這不是正確的,那么這個(gè)斷言就是錯(cuò)誤的。錯(cuò)誤的話,我們不就能繼續(xù)在當(dāng)前狀態(tài)往下執(zhí)行了。另一個(gè)例子是使用 unreachable!() 宏:

enum Event {
    NewRelease;
}

fn probability(_: &Event) -> f64 {
    // real implementation would be more complex, of course
    0.95
}

fn descriptive_probability(event: Event) -> &'static str {
    match probability(&event) {
        1.00 => "certain",
        0.00 => "impossible",
        0.00 ... 0.25 => "very unlikely",
        0.25 ... 0.50 => "unlikely",
        0.50 ... 0.75 => "likely",
        0.75 ... 1.00 => "very likely",
    }
}

fn main() {
    std::io::println(descriptive_probability(NewRelease));
}

它將會(huì)輸出如下的錯(cuò)誤:

error: non-exhaustive patterns: `_` not covered [E0004]

盡管我們已經(jīng)涵蓋所有我們知道的可能情況情況,但是 Rust 不清楚。Rust 不知道概率是 0.0 和 1.0 之間。所以我們添加另一個(gè)例子:

use Event::NewRelease;

enum Event {
    NewRelease,
}

fn probability(_: &Event) -> f64 {
    // real implementation would be more complex, of course
    0.95
}

fn descriptive_probability(event: Event) -> &'static str {
    match probability(&event) {
        1.00 => "certain",
        0.00 => "impossible",
        0.00 ... 0.25 => "very unlikely",
        0.25 ... 0.50 => "unlikely",
        0.50 ... 0.75 => "likely",
        0.75 ... 1.00 => "very likely",
        _ => unreachable!()
    }
}

fn main() {
    println!("{}", descriptive_probability(NewRelease));
}

我們不應(yīng)該得到 _ 情況,所以我們使用 unreachable!() 宏來說明這個(gè)。unreachable!() 比結(jié)果給出了不同于 Result 類型的錯(cuò)誤。Rust 稱這些類型的錯(cuò)誤為異常。

利用 Option 和 Result 處理錯(cuò)誤

表明一個(gè)函數(shù)可能會(huì)失敗的最簡(jiǎn)單方法是使用 option< T >類型。例如,字符串 find 方法試圖找到字符串的一個(gè)模式串,并返回 Option:

let s = "foo";

assert_eq!(s.find('f'), Some(0));
assert_eq!(s.find('z'), None);

對(duì)這些簡(jiǎn)單的情況下是可以的,但是在故障的情況下并不會(huì)給我們提供很多的信息。如果我們想知道為什么函數(shù)發(fā)生了故障,怎么辦?為此,我們可以使用 Result<T, E>類型。它看起來像這樣:

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

這個(gè)枚舉類型由 Rust 本身提供,所以你不需要在你的代碼中定義就可以使用它。Ok(T) 變量代表著成功執(zhí)行,Err(E) 變量代表著執(zhí)行失敗。推薦在大多數(shù)情況下返回一個(gè) Result而不是一個(gè) Option 變量。

如下是一個(gè)使用 Result 的例子:

#[derive(Debug)]
enum Version { Version1, Version2 }

#[derive(Debug)]
enum ParseError { InvalidHeaderLength, InvalidVersion }

fn parse_version(header: &[u8]) -> Result<Version, ParseError> {
    if header.len() < 1 {
        return Err(ParseError::InvalidHeaderLength);
    }
    match header[0] {
        1 => Ok(Version::Version1),
        2 => Ok(Version::Version2),
        _ => Err(ParseError::InvalidVersion)
    }
}

let version = parse_version(&[1, 2, 3, 4]);
match version {
    Ok(v) => {
        println!("working with version: {:?}", v);
    }
    Err(e) => {
        println!("error parsing header: {:?}", e);
    }
}

這個(gè)函數(shù)使用枚舉類型變量 ParseError 列舉各種可能發(fā)生的錯(cuò)誤。

調(diào)試特點(diǎn)就是讓我們使用 {:?} 格式來打印該枚舉變量的值。

遇到 panic!類型的不可恢復(fù)錯(cuò)誤

遇到為意料的和不可恢復(fù)的的錯(cuò)誤時(shí),宏 panic! 會(huì)引起異常。這將崩潰當(dāng)前線程,并給出一個(gè)錯(cuò)誤:

panic!("boom");

當(dāng)你運(yùn)行時(shí)會(huì)輸出:

thred '<main>' panicked at 'boom', hello.rs:2

因?yàn)檫@些類型的情況相對(duì)較少見,很少使用恐慌。

升級(jí)故障為異常

在某些情況下,即使一個(gè)函數(shù)可能發(fā)生故障,我們?nèi)韵胍阉?dāng)作一個(gè)異常對(duì)待。例如,io::stdin().read_line(&mut buff) 函數(shù)在讀取某一行時(shí)出現(xiàn)錯(cuò)誤會(huì)返回 Result< usize > 變量。這使我們能夠處理它并可能從錯(cuò)誤中恢復(fù)。

如果我們不想處理這個(gè)錯(cuò)誤,而寧愿只是中止程序,那么我們可以使用 unwrap() 方法:

io::stdin().read_line(&mut buffer).unwrap();

如果 Result 變量值是 Err,unwrap() 方法將會(huì)產(chǎn)生調(diào)用 panic!,輸出異常。這基本上是說“給我變量的值,如果出現(xiàn)錯(cuò)誤,就讓程序崩潰?!斑@相對(duì)于匹配錯(cuò)誤并試圖恢復(fù)的方式可靠性較低,但也大大縮短執(zhí)行時(shí)間。有時(shí),只是崩潰程序是合理的。

有另一種方式比 unwrap() 方法好一點(diǎn):

let mut buffer = String::new();
let input = io::stdin().read_line(&mut buffer)
                       .ok()
                       .expect("Failed to read line");

ok() 方法將 Result 轉(zhuǎn)換成一個(gè) Option,并 expect() 和 unwrap() 方法做的事是一樣的,不同點(diǎn)在于它需要一個(gè)參數(shù),用來輸出提示信息。這個(gè)消息被傳遞到底層的 panic!,如果代碼出現(xiàn)錯(cuò)誤,它提供一個(gè)較好的錯(cuò)誤消息展示方式。

使用 try!

當(dāng)編寫的代碼調(diào)用很多的返回 Result 類型的函數(shù)時(shí),錯(cuò)誤處理就變得比較冗長(zhǎng)。try! 宏利用堆棧對(duì)產(chǎn)生的錯(cuò)誤進(jìn)行引用從而隱藏具體的細(xì)節(jié)。

將如下的代碼:

use std::fs::File;
use std::io;
use std::io::prelude::*;

struct Info {
    name: String,
    age: i32,
    rating: i32,
}

fn write_info(info: &Info) -> io::Result<()> {
    let mut file = File::create("my_best_friends.txt").unwrap();

    if let Err(e) = writeln!(&mut file, "name: {}", info.name) {
        return Err(e)
    }
    if let Err(e) = writeln!(&mut file, "age: {}", info.age) {
        return Err(e)
    }
    if let Err(e) = writeln!(&mut file, "rating: {}", info.rating) {
        return Err(e)
    }

    return Ok(());
}

替換成:

use std::fs::File;
use std::io;
use std::io::prelude::*;

struct Info {
    name: String,
    age: i32,
    rating: i32,
}

fn write_info(info: &Info) -> io::Result<()> {
    let mut file = try!(File::create("my_best_friends.txt"));

    try!(writeln!(&mut file, "name: {}", info.name));
    try!(writeln!(&mut file, "age: {}", info.age));
    try!(writeln!(&mut file, "rating: {}", info.rating));

    return Ok(());
}

用 try! 封裝一個(gè)表達(dá)式,在成功執(zhí)行時(shí)給 Result 賦值為 Ok,否則賦值為 Err,在這種情況下,在函數(shù)未執(zhí)行完成之前就會(huì)返回 Err 值。

值得注意的是,你只能在返回 Result 的函數(shù)中使用 try!,這意味著您不能在 main() 中使用 try!,因?yàn)?main() 不返回任何值。

try! 利用 From 來確定在錯(cuò)誤的情況下返回的值。

上一篇:變量綁定下一篇:for 循環(huán)