理解Rust中的Result/Option/unwrap/?

我在學習Rust時,注意到有4個概念經常放到一起討論:Result、Option、unwapr和?操作符。本文記錄了我對這4個Rust概念的思考,這個思考過程幫助我理解並學會了如何寫出更地道的Rust代碼。

區塊鏈開發教程鏈接:以太坊 | 比特幣 | EOS | Tendermint | Hyperledger Fabric | Omni/USDT | Ripple

1、Option - 可空變量

雖然Rust中有null的概念,但是使用null並不是Rust中常見的模式。假設我們要寫一個函數,輸入一種手機操作系統的名稱,這個函數就會返回其應用商店的名稱。如果傳入字符串iOS,該函數將返回App Store;如果傳入字符串android,那麼該函數將返回Play Store。任何其他的輸入都被視爲無效。

在大多數開發語言中,我們可以選擇返回null或字符串invalid來表示無效的結果,不過這不是Rust的用法。

地道的Rust代碼應該讓該函數返回一個Option。Option或更確切的說Option<T>是一個泛型,可以是Some<T>None(爲了便於閱讀,後續文章中將省略類型參數T)。Rust將SomeNone稱爲變體(Variant) —— 這一概念在其他語言中並不存在,因此我也不
去定義到底什麼是變體了。

在我們的示例中,正常情況下函數將返回包裹在Some變體中的字符串常量App Store或Play Store。而在非正常情況下,函數將返回None。

fn find_store(mobile_os: &str) -> Option<&str> {
    match mobile_os {
        "iOS" => Some("App Store"),
        "android" => Some("Play Store"),
        _ => None
    }
}

要使用find_store(),我們可以用如下方式調用:

fn main() {
    println!("{}", match find_store("windows") {
        Some(s) => s,
        None => "Not a valid mobile OS"
    });
}

完整的代碼如下:

fn find_store(mobile_os: &str) -> Option<&str> {
    match mobile_os {
        "iOS" => Some("App Store"),
        "android" => Some("Play Store"),
        _ => None
    }
}

fn main() {
    println!("{}", match find_store("windows") {
        Some(s) => s,
        None => "Not a valid mobile OS"
    });
}

2、Result - 包含錯誤信息的結果

Result,或者更確切地說Result<T,E>,是和Rust中的Option相關的概念,它是一個加強版本的Option。

Result<T, E>可能有以下結果之一:

  • Ok(T):結果爲成員T
  • Err(E):結果爲故障成員E

與之前我們看到Option可以包含Some或None不同,Result中包含了錯誤相關信息,這是Option中所沒有的。

讓我們看一個函數實例,它返回一個Result。該函數摘自用於解析JSON字符串的serde_json庫,其簽名爲:

pub fn from_str<'a, T>(s: &'a str) -> Result<T, Error> 
where
    T: Deserialize<'a>, 

假設我們要解析如下的字符串:

let json_string = r#"
    {
        "name": "John Doe",
        "age": 43,
        "phones": [
            "+44 1234567",
            "+44 2345678"
        ]
    }"#;

目標是解析爲Rust的一個person結構對象:

#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}

解析過程的Rust代碼如下:

let p:Person = match serde_json::from_str(json_string) {
    Ok(p) => p,
    Err(e) => ... //we will discuss what goes here next 
};

正常情況下可以得到期望的結果。不過假設在輸入的json_string中有一個筆誤,這導致程序運行時將執行Err分支。

當碰到Err時,我們可以採取兩個動作:

  • panic!
  • 返回Err

3、unwrap - 故障時執行panic!

在上面的示例中,假設我們期望panic!:

let p: Person = match serde_json::from_str(data) {
        Ok(p) => p,
        Err(e) => panic!("cannot parse JSON {:?}, e"), //panic
    }

當碰到Err時,上面的代碼panic!就會崩掉整個程序,也許這不是你期望的。我們可以修改爲:

let p:Person = serde_json::from_str(data).unwrap();

如果我們可以確定輸入的json_string始終會是可解析的,那麼使用unwrap沒有問題。但是如果會出現Err,那麼程序就會崩潰,無法從故障中恢復。在開發過程中,當我們更關心程序的主流程時,unwrap也可以作爲快速
原型使用。

因此unwrap隱含了panic!。雖然與更顯式的版本沒有差異,但是危險在於其隱含特性,因爲有時這並不是你真正期望的行爲。

無論如何,如果我們需要調用panic!,代碼如下:

use serde::{Deserialize, Serialize};
use serde_json::Result;

#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}

fn typed_example() -> Result<()> {
    //age2 is error on purpose
    let data = r#"
        {
            "name": "John Doe",
            "age2": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }"#;

    let p:Person = serde_json::from_str(data).unwrap();

    println!("Please call {} at the number {}", p.name, p.phones[0]);

    Ok(())
}

fn main() {
    match typed_example() {
        Ok(_) => println!("program ran ok"),
        Err(_) => println!("program ran with error"),
    }
}

4、? - 故障時返回Err對象

當碰到Err時,我們不一定要panic!,也可以返回Err。不是每個Err都是不可恢復的,因此有時並不需要panic!。下面的代碼返回Err:

let p: Person = match serde_json::from_str(data) {
        Ok(p) => p,
        Err(e) => return Err(e.into()),
};

?操作符提供了一個更簡潔的方法來替換上面的代碼:

let p:Person = serde_json::from_str(data)?;

這時完整的Rust程序代碼如下:

use serde::{Deserialize, Serialize};
use serde_json::Result;

#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}

fn typed_example() -> Result<()> {
    //age2 is error on purpose
    let data = r#"
        {
            "name": "John Doe",
            "age2": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }"#;

    let p: Person = serde_json::from_str(data)?;

    println!("Please call {} at the number {}", p.name, p.phones[0]);

    Ok(())
}

fn main() {
    match typed_example() {
        Ok(_) => println!("program ran ok"),
        Err(e) => println!("program ran with error {:?}", e),
    }
}

5、使用unwrap和?解包Option

就像我們可以使用unwarp和?來處理Result,我們也可以使用unwrap和?來處理Option。

如果我們unwrap的Option的值是None,那麼程序就會panic!。示例如下:

fn next_birthday(current_age: Option<u8>) -> Option<String> {
	// If `current_age` is `None`, this returns `None`.
	// If `current_age` is `Some`, the inner `u8` gets assigned to `next_age` after 1 is added to it
    let next_age: u8 = current_age?;
    Some(format!("Next year I will be {}", next_age + 1))
}

fn main() {
  let s = next_birthday(None);
  match s {
      Some(a) => println!("{:#?}", a),
      None => println!("No next birthday")
  }
}

原文鏈接:Rust學習 - Result/Option/unwrap/? — 匯智網

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章