亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享
原創

Rust入門(三) —— 錯誤處理(Option && Result)

2024-03-14 01:23:58
58
0

rust 的枚舉

在講述Result或Option之前,我們有必要先了解一下rust的枚舉;因為Result和Option都是枚舉類型。

概念上rust的枚舉與C語言的枚舉是一致:定義一個類型,可以窮舉所有可能的值。比如,定義一個IP地址類型:

enum IpAddrKind {
        IPV4,
        IPV6,
}

 

IP地址要么是IPV4, 要么是IPV6。 對于rust  ip :IpAddrKind , ip 的值也是IPV4 或IPV6之一。

枚舉類型在使用時,通常也需要和數據關聯。在C語言中,當我們需要將枚舉類型和和數據關聯時,通常是額外定義一個結構體,將類型標識和數據封裝在一起:

struct IpAddr {
    enum IpAddrKind kind;
    char data[16];
}

如此,當我們獲得一個IpAddr實例時,可以根據其kind值先確認其地址類型;然后再根據其地址類型,從data中解析出具體數地址數據。

注意:這里data使用了共享存儲的方法;原因是這里將IPv6 和IPv4的數據都定義為了字符數組,并且IPv6的長度可以覆蓋IPv4。假如,重新定義IP的數據類型為 IPv4為一個U32整形,IPv6為一個字符數組,那么IpAddr的結構體可能會設計為這樣:

 

 

struct IpAddr {
    enum IpAddrKind kind;
    union {
         unsigned int ipv4;
         char ipv6[16];
    } data;
}

rust 相對于C的枚舉,對枚舉類型做了大幅優化,允許我們直接將關聯數據類型直接嵌入到枚舉的變體中。比如,rust定義的IpAddr 可能是這樣:

enum IpAddr {
    IPV4 (String),
    IPV6 (String),
}

使用:
let loopback = IpAddr::IPV4("127.0.0.1".to_string()); // 定義了一個ipv4地址,其值“127.0.0.1”

簡單起見,可以理解為rust 的枚舉,融合了C枚舉和聯合體,實現了數據類型和關聯數據的定義和綁定。

一個稍微復雜一點的枚舉類型:

enum Message {
    Quit, // 無綁定數據
    Move {x: i32, y:i32}, // 綁定了一個匿名結構體 struct {x:i32, y:i32}
    Write(string), // 綁定了一個字符串數據
    ChangeColor(i32, i32, i32), // 綁定了一個元祖,由三個i32 組成
}

 

枚舉方法

在rust 里面您還可以為枚舉實現方法。這就像在面向對象編程時,為class (java)或結構體(rust, golang)綁定方法一樣。和rust 的struct 實現方法一樣,用impl關鍵字為指定的枚舉類型添加方法:

impl Message {
    fn call(&self) {
          // do_something()
    }
}
// example
let msg = Message::Write(String::from("notice: processing going down"));
msg.call();

枚舉與match(控制流運輸符號)

rust中有一個強大的控制流運算符:match,它允許將一個值與一些列模式進行匹配,并根據匹配的模式執行相關代碼(關于rust的模式匹配,本文不深入,讀者自行補充);而其中枚舉是模式匹配中最為常用的:

impl Message {
   fn call(&self) {
      // do_something()
   }

    fn to_string(&self) -> String {
         match self {
                 Message::Quit => String::from("quit"),
                 Message::Move { x, y } => format!("Move: <{},{}>", x, y),
                 Message::Write(s) => format!("String: {}", s),
                 Message::ChangeColor(x, y, z) => format!("ChangeColor: ({},{},{})", x, y, z),
          }
     }
}

上述示例中,為Message枚舉實現的to_string方法返回某個具體示例的字符串值;其中就使用了match模式匹配。match 和C中的switch關鍵字比較類似,但比switch更為強大。

與其他語言不一樣,rust在匹配枚舉時,要求務必窮盡所有可能(當然,可以用通配的方法忽略不在意的變體)。

簡單控制流 if let

前面已經提到,match 在遍歷枚舉時,要求務必窮盡所有可能。但有時候,我們確實只關注某一種匹配的情況,而忽略其他情況。當然,這種場景可以用match 的 '_' 通配的方式,來忽略其它不關心的變體,只是多寫了了幾行廢代碼而已。

幸運的是,rust 提供了一個if let 語法,可以簡化這種場景的表達:

impl Message {
    fn on_quit(&self) {
             if let Message::Quit = self {
                    std::process::exit(0);
               }
     }
}

 Option

在其他語言中,大部分都支持空值(Null,nil):本身是一個值,卻表示‘沒有值’。在支持空值的語言中,一個值可能處于兩種狀態:空值或非空值。

比如 c語言中,定義一個變量 char* ptr ,那么默認情況下ptr 就是空值(null)。

空值的問題在于,當你嘗試像使用非空值那樣去使用空值的時候,就會觸發某種程度的錯誤(通常可能導致程序崩潰,比如訪問空指針)。另一方面,因為這種存在雙狀態的值被廣泛應用于程序中時,你很難避免引發類似問題。

但是,不管怎樣,空值本身所嘗試表達的概念任然具有意義:它代表了因某種原因而無法獲取、或者變為無效的值。

rust語言中沒有空值,但卻提供了一個擁有類似概念的枚舉:`Option<T>`。我們可以用它來表示任意一個可能存在空值的值。

enum Option<T> {
    Some(T),
    None,
}

在對待空值上,rust和其他支持空值的語言上有所差異。一般支持空值的語言,對于數據是否為空值,由程序員自己保證,語言上并不限制。但在rust 中`Option<T>` 包裹的值,需要特別處理。例如:

let x = 5;
let y: Option<i32> = Some(8);
let sum = x+y;
println!(“{}”, sum);

上面代碼看上去沒有問題,但實際上卻無法通過編譯。編譯器指出,i32 和`Option<T>`,不支持相加行為,因為他們是不同類型。

rust中,對于一個 給定類型的變量(基礎類型或者結構體),例子中的x,編譯器保證它是有效的;但相反,一個`Option<T>`的變量,rust要求我們必須確認它是具有值的情況下,才可以使用。

換句話說,`Option<T> `中可能存在T,也可能是空值;我們必須確認它有值,并且將其轉換為T才能夠使用它。經過這個過程,就幫助我們甄別了值是否真實存在,從而避免了“使用了一個值,但它卻是空值”的陷阱!

let x = 5;
let y: Option<i32> = Some(8);
let sum: i32 = 0;

match y {
     Some(t) => {
              sum = x+t; // 確保有值,并使用該值
     },
     - => (),
}
println!("sum={}", sum)

使用模式匹配來處理返回值,調用者必須處理結果為None的情況。這往往是一個好的編程習慣,可以減少潛在的bug。Option 包含一些方法來簡化模式匹配,畢竟過多的match會使代碼變得臃腫,這也是滋生bug的原因之一。

unwrap

impl<T> Option<T> {
    fn unwrap(self) -> T {
             match self {
                    Option::Some(val) => val,
                    Option::None => {
                                panic!("called `Option::unwrap()` on a `None` value"),
                           }
            }
}

unwrap 是Option的一個工具函數。當遇到None值時會panic。

通常panic 并不是一個良好的工程實踐,不過有些時候卻非常有用:

  1. 在例子和簡單快速的編碼中 有的時候你只是需要一個小例子或者一個簡單的小程序,輸入輸出已經確定,你根本沒必要花太多時間考慮錯誤處理,使用unwrap變得非常合適。
  2. 當程序遇到了致命的bug,panic是最優選擇

 

map

pub fn map<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> U,
{
          match self {
             Some(x) => Some(f(x)),
              None => None,
         }
}

map 是Option的一個工具函數:對一個Option類型的值,如果其值非空,那么通過一個映射函數,映射為一個新類型;否則返回為None。

假如我們要在一個字符串中找到文件的擴展名,比如foo.rs中的rs, 我們可以這樣:

fn extension_explicit(file_name: &str) -> Option<&str> {
    match find(file_name, '.') {
        None => None,
        Some(i) => Some(&file_name[i+1..]),
    }
}

fn main() {
    match extension_explicit("foo.rs") {
        None => println!("no extension"),
        Some(ext) =>  assert_eq!(ext, "rs"),
    }
}

 

// 使用map去掉match

fn extension(file_name: &str) -> Option<&str> {
    find(file_name, '.').map(|i| &file_name[i+1..])
}

注意上面 “|i| &file_name[i+1..]” 的寫法是一個閉包函數。關于rust的閉包函數,請讀者自行了解學習。

 

 unwrap_or

fn unwrap_or<T>(option: Option<T>, default: T) -> T {
    match option {
        None => default,
        Some(value) => value,
    }
}

unwrap_or 提供了一個默認值default,當值為None時返該默認值。

and_then

fn and_then<F, T, A>(option: Option<T>, f: F) -> Option<A>
        where F: FnOnce(T) -> Option<A> {
    match option {
        None => None,
        Some(value) => f(value),
    }
}

看起來and_then和map差不多, 當Option 非空時調用f函數,對傳輸數據進行處理,否則返回None。

與map的差異一方面是語義上的差異,map側重于映射,而and_then表達豐富的后續處理;另一方面,在返回類型上and_then不限制,而map 保持輸入和輸出一致。可以認為,map 是and_then的一種特例。

 

Result

編程實踐中,對于程序中的錯誤,通常分為兩類: 不可恢復的錯誤 和可以恢復的錯誤。對于可恢復的錯誤,比如文件未找到,一般是報告給用戶,讓其重試;而不可恢復錯誤,比如數組訪問越界了,則會引起程序進入異常狀態。

在有異常處理的編程語言中,通常并不詳細區分這兩種錯誤,而是統一交由異常處理機制處理。rust沒有異常處理機制,通常對于不可恢復錯誤,會采用panic結束程序;而對于可恢復錯誤,則更傾向通過顯示的機制進行錯誤捕獲和傳遞。對于可恢復錯誤,rust采用Result類型來描述。

Result 是一個枚舉,有Ok 和 Err兩個變體:

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

其中,T和E均為泛型類型。

有了前兩節的知識鋪墊,理解這個枚舉并不困難,可以描述為:

1. 一個可能處理失敗的過程,其結果用Result來表示;

2. 如果處理成功,那么返回Result的Ok 變體,并且攜帶返回數據;

3. 如果處理失敗,那么返回Result的Err變體,并且攜帶錯誤信息。

 

示例:

let ret = File::open("test.txt");
let f = match ret {
    Ok(file) => file,
    Err(err) = {
        panic!("fail to open test.txt, error: {:?}", err );
    }
}

// f.XXXX()

 

工具函數

Result 和Option 非常相似,甚至可以理解為,Result是Option更為通用的版本,在異常的時候,返回了更多的錯誤信息;而Option 只是Result Err 為空的特例。

type Option<T> = Result<T, ()>;

和Option一樣,Result 也提供了 unwrap,unwrap_or, map,and_then 等系列工具方法。比如 unwarp實現:

impl<T, E: ::std::fmt::Debug> Result<T, E> {
    fn unwrap(self) -> T {
        match self {
            Result::Ok(val) => val,
            Result::Err(err) =>
              panic!("called `Result::unwrap()` on an `Err` value: {:?}", err),
        }
    }
}

沒錯和Option一樣,不同的是,Result包括了錯誤的詳細描述,這對于調試人員來說,這是友好的。

除此之外,相比于Option, Result也有一些特有的針對錯誤類型的方法map_err和or_else等。

其中:

map_err 處理一個Result,當前是某種錯誤類型時,通過傳入的op方法,轉換其錯誤類型; 如果是非錯誤類型,則不受影響。

 pub fn map_err<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, F> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(op(e)),
        }
    }

 

or_else 處理一個Result并返回一個Result,當前是某種錯誤時,通過傳入的op方法,處理錯誤;如果是非錯誤類型,則不受影響。

 pub fn or_else<F, O: FnOnce(E) -> Result<T, F>>(self, op: O) -> Result<T, F> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => op(e),
        }
    }

or_else 通常用于鏈式調用的流程控制。例如:

fn auto_fix(e: u32) -> Result<u32, u32> { Ok(e * e) }
fn keep(e: u32) -> Result<u32, u32> { Err(e) }

// 用例1和2,由于 原始Result值非 錯誤,所以不受or_else影響
assert_eq!(Ok(2).or_else(auto_fix).or_else(auto_fix), Ok(2));
assert_eq!(Ok(2).or_else(keep).or_else(auto_fix), Ok(2));

// 用例3, Err類型的Result 經過auto_fix 后已經轉為Ok(9);經過第二個or_else 不受影響
assert_eq!(Err(3).or_else(auto_fix).or_else(keep), Ok(9));

// 用例4, Err類型的Result 連續調用or_else 的keep,由于keep實現保留err返回為Err(3); 注意實際上Result實例時變化了的
assert_eq!(Err(3).or_else(keep).or_else(keep), Err(3));

 

 

Result別名

在Rust的標準庫中會經常出現Result的別名,用來默認確認其中Ok(T)或者Err(E)的類型,這能減少重復編碼。比如io::Result

use std::num::ParseIntError;
use std::result;

type Result<T> = result::Result<T, ParseIntError>;

fn double_number(number_str: &str) -> Result<i32> {
    unimplemented!();
}

 

組合Option和Result

Option的方法ok_or:

fn ok_or<T, E>(option: Option<T>, err: E) -> Result<T, E> {
    match option {
        Some(val) => Ok(val),
        None => Err(err),
    }
}

可以在值為None的時候返回一個Result::Err(E),值為Some(T)的時候返回Ok(T),利用它我們可以組合Option和Result:

use std::env;

fn double_arg(mut argv: env::Args) -> Result<i32, String> {
    argv.nth(1)
        .ok_or("Please give at least one argument".to_owned())
        .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
        .map(|n| 2 * n)
}

fn main() {
    match double_arg(env::args()) {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

double_arg將傳入的命令行參數轉化為數字并翻倍,ok_or將Option類型轉換成Result,map_err當值為Err(E)時調用作為參數的函數處理錯誤。

 

try! 宏

macro_rules! try {
    ($e:expr) => (match $e {
        Ok(val) => val,
        Err(err) => return Err(::std::convert::From::from(err)),
    });
}

 

try!事實上就是match Result的封裝,當遇到Err(E)時會提早返回, ::std::convert::From::from(err)可以將不同的錯誤類型返回成最終需要的錯誤類型,因為所有的錯誤都能通過From轉化成`Box<Error>`,所以下面的代碼是正確的:

use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, Box<Error>> {
    let mut file = try!(File::open(file_path));
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents));
    let n = try!(contents.trim().parse::<i32>());
    Ok(2 * n)
}

 

在新版本中 try!宏被進一步簡化為 一個?:

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, Error> {
    let mut file = File::open(file_path)?; // 注意這里的?, 和try功能一致,遇到錯誤,提前返回
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents));
    let n = try!(contents.trim().parse::<i32>());
    Ok(2 * n)
}

 

總結

rust的Option 和Result 為返回、檢測、處理錯誤,提供了系統支撐,這一點和golang的errors 設計比價類似。

熟練使用Option和Result是編寫 Rust 代碼的關鍵,Rust 優雅的錯誤處理離不開值返回的錯誤形式,編寫代碼時提供給使用者詳細的錯誤信息是值得推崇的。

0條評論
0 / 1000
huskar
20文章數
3粉絲數
huskar
20 文章 | 3 粉絲
原創

Rust入門(三) —— 錯誤處理(Option && Result)

2024-03-14 01:23:58
58
0

rust 的枚舉

在講述Result或Option之前,我們有必要先了解一下rust的枚舉;因為Result和Option都是枚舉類型。

概念上rust的枚舉與C語言的枚舉是一致:定義一個類型,可以窮舉所有可能的值。比如,定義一個IP地址類型:

enum IpAddrKind {
        IPV4,
        IPV6,
}

 

IP地址要么是IPV4, 要么是IPV6。 對于rust  ip :IpAddrKind , ip 的值也是IPV4 或IPV6之一。

枚舉類型在使用時,通常也需要和數據關聯。在C語言中,當我們需要將枚舉類型和和數據關聯時,通常是額外定義一個結構體,將類型標識和數據封裝在一起:

struct IpAddr {
    enum IpAddrKind kind;
    char data[16];
}

如此,當我們獲得一個IpAddr實例時,可以根據其kind值先確認其地址類型;然后再根據其地址類型,從data中解析出具體數地址數據。

注意:這里data使用了共享存儲的方法;原因是這里將IPv6 和IPv4的數據都定義為了字符數組,并且IPv6的長度可以覆蓋IPv4。假如,重新定義IP的數據類型為 IPv4為一個U32整形,IPv6為一個字符數組,那么IpAddr的結構體可能會設計為這樣:

 

 

struct IpAddr {
    enum IpAddrKind kind;
    union {
         unsigned int ipv4;
         char ipv6[16];
    } data;
}

rust 相對于C的枚舉,對枚舉類型做了大幅優化,允許我們直接將關聯數據類型直接嵌入到枚舉的變體中。比如,rust定義的IpAddr 可能是這樣:

enum IpAddr {
    IPV4 (String),
    IPV6 (String),
}

使用:
let loopback = IpAddr::IPV4("127.0.0.1".to_string()); // 定義了一個ipv4地址,其值“127.0.0.1”

簡單起見,可以理解為rust 的枚舉,融合了C枚舉和聯合體,實現了數據類型和關聯數據的定義和綁定。

一個稍微復雜一點的枚舉類型:

enum Message {
    Quit, // 無綁定數據
    Move {x: i32, y:i32}, // 綁定了一個匿名結構體 struct {x:i32, y:i32}
    Write(string), // 綁定了一個字符串數據
    ChangeColor(i32, i32, i32), // 綁定了一個元祖,由三個i32 組成
}

 

枚舉方法

在rust 里面您還可以為枚舉實現方法。這就像在面向對象編程時,為class (java)或結構體(rust, golang)綁定方法一樣。和rust 的struct 實現方法一樣,用impl關鍵字為指定的枚舉類型添加方法:

impl Message {
    fn call(&self) {
          // do_something()
    }
}
// example
let msg = Message::Write(String::from("notice: processing going down"));
msg.call();

枚舉與match(控制流運輸符號)

rust中有一個強大的控制流運算符:match,它允許將一個值與一些列模式進行匹配,并根據匹配的模式執行相關代碼(關于rust的模式匹配,本文不深入,讀者自行補充);而其中枚舉是模式匹配中最為常用的:

impl Message {
   fn call(&self) {
      // do_something()
   }

    fn to_string(&self) -> String {
         match self {
                 Message::Quit => String::from("quit"),
                 Message::Move { x, y } => format!("Move: <{},{}>", x, y),
                 Message::Write(s) => format!("String: {}", s),
                 Message::ChangeColor(x, y, z) => format!("ChangeColor: ({},{},{})", x, y, z),
          }
     }
}

上述示例中,為Message枚舉實現的to_string方法返回某個具體示例的字符串值;其中就使用了match模式匹配。match 和C中的switch關鍵字比較類似,但比switch更為強大。

與其他語言不一樣,rust在匹配枚舉時,要求務必窮盡所有可能(當然,可以用通配的方法忽略不在意的變體)。

簡單控制流 if let

前面已經提到,match 在遍歷枚舉時,要求務必窮盡所有可能。但有時候,我們確實只關注某一種匹配的情況,而忽略其他情況。當然,這種場景可以用match 的 '_' 通配的方式,來忽略其它不關心的變體,只是多寫了了幾行廢代碼而已。

幸運的是,rust 提供了一個if let 語法,可以簡化這種場景的表達:

impl Message {
    fn on_quit(&self) {
             if let Message::Quit = self {
                    std::process::exit(0);
               }
     }
}

 Option

在其他語言中,大部分都支持空值(Null,nil):本身是一個值,卻表示‘沒有值’。在支持空值的語言中,一個值可能處于兩種狀態:空值或非空值。

比如 c語言中,定義一個變量 char* ptr ,那么默認情況下ptr 就是空值(null)。

空值的問題在于,當你嘗試像使用非空值那樣去使用空值的時候,就會觸發某種程度的錯誤(通常可能導致程序崩潰,比如訪問空指針)。另一方面,因為這種存在雙狀態的值被廣泛應用于程序中時,你很難避免引發類似問題。

但是,不管怎樣,空值本身所嘗試表達的概念任然具有意義:它代表了因某種原因而無法獲取、或者變為無效的值。

rust語言中沒有空值,但卻提供了一個擁有類似概念的枚舉:`Option<T>`。我們可以用它來表示任意一個可能存在空值的值。

enum Option<T> {
    Some(T),
    None,
}

在對待空值上,rust和其他支持空值的語言上有所差異。一般支持空值的語言,對于數據是否為空值,由程序員自己保證,語言上并不限制。但在rust 中`Option<T>` 包裹的值,需要特別處理。例如:

let x = 5;
let y: Option<i32> = Some(8);
let sum = x+y;
println!(“{}”, sum);

上面代碼看上去沒有問題,但實際上卻無法通過編譯。編譯器指出,i32 和`Option<T>`,不支持相加行為,因為他們是不同類型。

rust中,對于一個 給定類型的變量(基礎類型或者結構體),例子中的x,編譯器保證它是有效的;但相反,一個`Option<T>`的變量,rust要求我們必須確認它是具有值的情況下,才可以使用。

換句話說,`Option<T> `中可能存在T,也可能是空值;我們必須確認它有值,并且將其轉換為T才能夠使用它。經過這個過程,就幫助我們甄別了值是否真實存在,從而避免了“使用了一個值,但它卻是空值”的陷阱!

let x = 5;
let y: Option<i32> = Some(8);
let sum: i32 = 0;

match y {
     Some(t) => {
              sum = x+t; // 確保有值,并使用該值
     },
     - => (),
}
println!("sum={}", sum)

使用模式匹配來處理返回值,調用者必須處理結果為None的情況。這往往是一個好的編程習慣,可以減少潛在的bug。Option 包含一些方法來簡化模式匹配,畢竟過多的match會使代碼變得臃腫,這也是滋生bug的原因之一。

unwrap

impl<T> Option<T> {
    fn unwrap(self) -> T {
             match self {
                    Option::Some(val) => val,
                    Option::None => {
                                panic!("called `Option::unwrap()` on a `None` value"),
                           }
            }
}

unwrap 是Option的一個工具函數。當遇到None值時會panic。

通常panic 并不是一個良好的工程實踐,不過有些時候卻非常有用:

  1. 在例子和簡單快速的編碼中 有的時候你只是需要一個小例子或者一個簡單的小程序,輸入輸出已經確定,你根本沒必要花太多時間考慮錯誤處理,使用unwrap變得非常合適。
  2. 當程序遇到了致命的bug,panic是最優選擇

 

map

pub fn map<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> U,
{
          match self {
             Some(x) => Some(f(x)),
              None => None,
         }
}

map 是Option的一個工具函數:對一個Option類型的值,如果其值非空,那么通過一個映射函數,映射為一個新類型;否則返回為None。

假如我們要在一個字符串中找到文件的擴展名,比如foo.rs中的rs, 我們可以這樣:

fn extension_explicit(file_name: &str) -> Option<&str> {
    match find(file_name, '.') {
        None => None,
        Some(i) => Some(&file_name[i+1..]),
    }
}

fn main() {
    match extension_explicit("foo.rs") {
        None => println!("no extension"),
        Some(ext) =>  assert_eq!(ext, "rs"),
    }
}

 

// 使用map去掉match

fn extension(file_name: &str) -> Option<&str> {
    find(file_name, '.').map(|i| &file_name[i+1..])
}

注意上面 “|i| &file_name[i+1..]” 的寫法是一個閉包函數。關于rust的閉包函數,請讀者自行了解學習。

 

 unwrap_or

fn unwrap_or<T>(option: Option<T>, default: T) -> T {
    match option {
        None => default,
        Some(value) => value,
    }
}

unwrap_or 提供了一個默認值default,當值為None時返該默認值。

and_then

fn and_then<F, T, A>(option: Option<T>, f: F) -> Option<A>
        where F: FnOnce(T) -> Option<A> {
    match option {
        None => None,
        Some(value) => f(value),
    }
}

看起來and_then和map差不多, 當Option 非空時調用f函數,對傳輸數據進行處理,否則返回None。

與map的差異一方面是語義上的差異,map側重于映射,而and_then表達豐富的后續處理;另一方面,在返回類型上and_then不限制,而map 保持輸入和輸出一致。可以認為,map 是and_then的一種特例。

 

Result

編程實踐中,對于程序中的錯誤,通常分為兩類: 不可恢復的錯誤 和可以恢復的錯誤。對于可恢復的錯誤,比如文件未找到,一般是報告給用戶,讓其重試;而不可恢復錯誤,比如數組訪問越界了,則會引起程序進入異常狀態。

在有異常處理的編程語言中,通常并不詳細區分這兩種錯誤,而是統一交由異常處理機制處理。rust沒有異常處理機制,通常對于不可恢復錯誤,會采用panic結束程序;而對于可恢復錯誤,則更傾向通過顯示的機制進行錯誤捕獲和傳遞。對于可恢復錯誤,rust采用Result類型來描述。

Result 是一個枚舉,有Ok 和 Err兩個變體:

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

其中,T和E均為泛型類型。

有了前兩節的知識鋪墊,理解這個枚舉并不困難,可以描述為:

1. 一個可能處理失敗的過程,其結果用Result來表示;

2. 如果處理成功,那么返回Result的Ok 變體,并且攜帶返回數據;

3. 如果處理失敗,那么返回Result的Err變體,并且攜帶錯誤信息。

 

示例:

let ret = File::open("test.txt");
let f = match ret {
    Ok(file) => file,
    Err(err) = {
        panic!("fail to open test.txt, error: {:?}", err );
    }
}

// f.XXXX()

 

工具函數

Result 和Option 非常相似,甚至可以理解為,Result是Option更為通用的版本,在異常的時候,返回了更多的錯誤信息;而Option 只是Result Err 為空的特例。

type Option<T> = Result<T, ()>;

和Option一樣,Result 也提供了 unwrap,unwrap_or, map,and_then 等系列工具方法。比如 unwarp實現:

impl<T, E: ::std::fmt::Debug> Result<T, E> {
    fn unwrap(self) -> T {
        match self {
            Result::Ok(val) => val,
            Result::Err(err) =>
              panic!("called `Result::unwrap()` on an `Err` value: {:?}", err),
        }
    }
}

沒錯和Option一樣,不同的是,Result包括了錯誤的詳細描述,這對于調試人員來說,這是友好的。

除此之外,相比于Option, Result也有一些特有的針對錯誤類型的方法map_err和or_else等。

其中:

map_err 處理一個Result,當前是某種錯誤類型時,通過傳入的op方法,轉換其錯誤類型; 如果是非錯誤類型,則不受影響。

 pub fn map_err<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, F> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(op(e)),
        }
    }

 

or_else 處理一個Result并返回一個Result,當前是某種錯誤時,通過傳入的op方法,處理錯誤;如果是非錯誤類型,則不受影響。

 pub fn or_else<F, O: FnOnce(E) -> Result<T, F>>(self, op: O) -> Result<T, F> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => op(e),
        }
    }

or_else 通常用于鏈式調用的流程控制。例如:

fn auto_fix(e: u32) -> Result<u32, u32> { Ok(e * e) }
fn keep(e: u32) -> Result<u32, u32> { Err(e) }

// 用例1和2,由于 原始Result值非 錯誤,所以不受or_else影響
assert_eq!(Ok(2).or_else(auto_fix).or_else(auto_fix), Ok(2));
assert_eq!(Ok(2).or_else(keep).or_else(auto_fix), Ok(2));

// 用例3, Err類型的Result 經過auto_fix 后已經轉為Ok(9);經過第二個or_else 不受影響
assert_eq!(Err(3).or_else(auto_fix).or_else(keep), Ok(9));

// 用例4, Err類型的Result 連續調用or_else 的keep,由于keep實現保留err返回為Err(3); 注意實際上Result實例時變化了的
assert_eq!(Err(3).or_else(keep).or_else(keep), Err(3));

 

 

Result別名

在Rust的標準庫中會經常出現Result的別名,用來默認確認其中Ok(T)或者Err(E)的類型,這能減少重復編碼。比如io::Result

use std::num::ParseIntError;
use std::result;

type Result<T> = result::Result<T, ParseIntError>;

fn double_number(number_str: &str) -> Result<i32> {
    unimplemented!();
}

 

組合Option和Result

Option的方法ok_or:

fn ok_or<T, E>(option: Option<T>, err: E) -> Result<T, E> {
    match option {
        Some(val) => Ok(val),
        None => Err(err),
    }
}

可以在值為None的時候返回一個Result::Err(E),值為Some(T)的時候返回Ok(T),利用它我們可以組合Option和Result:

use std::env;

fn double_arg(mut argv: env::Args) -> Result<i32, String> {
    argv.nth(1)
        .ok_or("Please give at least one argument".to_owned())
        .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
        .map(|n| 2 * n)
}

fn main() {
    match double_arg(env::args()) {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

double_arg將傳入的命令行參數轉化為數字并翻倍,ok_or將Option類型轉換成Result,map_err當值為Err(E)時調用作為參數的函數處理錯誤。

 

try! 宏

macro_rules! try {
    ($e:expr) => (match $e {
        Ok(val) => val,
        Err(err) => return Err(::std::convert::From::from(err)),
    });
}

 

try!事實上就是match Result的封裝,當遇到Err(E)時會提早返回, ::std::convert::From::from(err)可以將不同的錯誤類型返回成最終需要的錯誤類型,因為所有的錯誤都能通過From轉化成`Box<Error>`,所以下面的代碼是正確的:

use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, Box<Error>> {
    let mut file = try!(File::open(file_path));
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents));
    let n = try!(contents.trim().parse::<i32>());
    Ok(2 * n)
}

 

在新版本中 try!宏被進一步簡化為 一個?:

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, Error> {
    let mut file = File::open(file_path)?; // 注意這里的?, 和try功能一致,遇到錯誤,提前返回
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents));
    let n = try!(contents.trim().parse::<i32>());
    Ok(2 * n)
}

 

總結

rust的Option 和Result 為返回、檢測、處理錯誤,提供了系統支撐,這一點和golang的errors 設計比價類似。

熟練使用Option和Result是編寫 Rust 代碼的關鍵,Rust 優雅的錯誤處理離不開值返回的錯誤形式,編寫代碼時提供給使用者詳細的錯誤信息是值得推崇的。

文章來自個人專欄
文章 | 訂閱
0條評論
0 / 1000
請輸入你的評論
2
2