Rust 学习 - Result/Option/unwrap/?
发表于 2020-06-15 | 分类于 开发语言
我在学习 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
- 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>whereT: 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 purposelet 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 purposelet 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 itlet 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")}}
原文链接:Taking the Unhappy Path with Result, Option, unwrap and ? operator in Rust
汇智网翻译整理,转载请标明出处
http://blog.hubwiz.com/2020/06/15/rust-result-option-unwrap-question/
