我在學習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將Some
和None
稱爲變體(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")
}
}