前言
在 教程8 我们介绍HashMap的时候提到过Option,当时我们讲到查询HashMap的key值对应数据时,不能百分百保证对应的数据一定存在,所以返回结果必然会存在“空”的情况。Rust中没有类似JavaScript中undefined或null的概念,为了满足安全表示“空”,就需要Option来解决这个问题了
“空”就像是一个预期错误,这种情况一般要么是结果正确,要么结果为“空”意味着失败,为应对预期中的错误处理,Result应运而生。Result 和 Option 常常是一块出现,处理方式也相似,而且必要时可以相互转换
正文
相关阅读
- The Rust Book: ch 06 - Enums
- Rust by Example: Enums
- The Rust Reference: Enumerations
- Rust by Example: match
- Rust by Example: if let
- Rust docs: Result
- Rust docs: Option
Option
如果你还没仔细学习过enums,那么好好读完上面列出的相关文章
Rust的enums与其它很多语言的实现不同,其中Option enums是这么定义的:
pub enum Option<T> {
/// No value
None,
/// Some value `T`
Some(T),
}
取值要么是Option::None表示“空”,要么是Option::Some(T)表示某个值,我们之前接触过泛型,你在 教程10 的阅读列表 里可以看到更多有关文章
在Rust中创建和使用Option十分简单:
fn main() {
let some = returns_some();
println!("{:?}", some);
let none = returns_none();
println!("{:?}", none);
}
fn returns_some() -> Option<String> {
Some("my string".to_owned())
}
fn returns_none() -> Option<String> {
None
}
在返回None的时候,我们也需要指定Option<T>中的T
注意:我们可以用
Some和None替代Option::Some()和Option::None是因为它们都被Rust预先引入了,相关信息可以阅读 std::prelude.
Result
Result和Option除了失败也会有个数值以外,其它地方都很相似,Result::Err变量中的值通常遵循一些约定,不过查看其实现的话会发现实际上也没有什么约束
pub enum Result<T, E> {
Ok(T),
Err(E),
}
Ok()和Err()的创建没有什么特殊之处
fn main() {
let value = returns_ok();
println!("{:?}", value);
let value = returns_err();
println!("{:?}", value);
}
fn returns_ok() -> Result<String, MyError> {
Ok("This turned out great!".to_owned())
}
fn returns_err() -> Result<String, MyError> {
Err(MyError("This failed horribly.".to_owned()))
}
#[derive(Debug)]
struct MyError(String);
这里之所以用到struct是为了表示Err可以包含任何值,比如struct、String、HashMap或其它
.unwrap()
Option和Result看上去简单易懂,但实际上真的是这样么?
容易困惑之处来自于如何获取其值,在教程中你已经遇到过.unwrap(),如果你对None或Err使用.unwrap()会报错。在Rust中,只要对警告足够重视,你会减少99%的异常,实现更快、更稳定的目标
如何获取值
.unwrap()
只在你确保Some()或Ok()上使用.unwrap(),已之前IP地址的示例为例:
let ip_address = std::net::Ipv4Addr::from_str("127.0.0.1").unwrap();
能放心的使用.unwrap()是基于编码者对代码的了解,在使用前需要能够确认其值。例如我们可以使用 .contains_key() 来确认HashMaps是否包含指定的key,如果是的话我们就可以放心的使用.unwrap()了
.unwrap_or()
.unwrap_or()会给失败提供一个默认值,默认值的类型需要和Ok(T)、Some(T)的T保持一致:
fn main() {
let value = returns_ok();
println!("{:?}", value.unwrap());
let value = returns_err();
// 这里必须是String类型
println!("{:?}", value.unwrap_or("真的失败了".to_owned()));
}
fn returns_ok() -> Result<String, String> {
Ok("成功!".to_owned())
}
fn returns_err() -> Result<String, String> {
Err("失败!".to_owned())
}
.unwrap_or_else(|| {})
.unwrap_or_else() 接受一个函数,当是None或Err的情况时将会采纳函数的返回值,如果默认值的计算代价很高且提前计算它没有任何价值,那么就可以使用这种方法
和.unwrap_or()一样,函数的返回值类型需要和T保持一致
let unwrap_or_else = returns_none()
.unwrap_or_else(|| format!("Default value from a function at time {:?}", Instant::now()));
println!(
"returns_none().unwrap_or_else(|| {{...}}): {:?}",
unwrap_or_else
);
|| ...是Rust中的闭包语法(closure syntax),后面会深入介绍
unwrap_or_default()
unwrap_or_default() 会遵循指定类型的Default值如果不存在的话,Default是一个类似Debug、Display之类的trait。
在TypeScript中你可能会写这样的代码:
et my_string = maybe_undefined || "";
在Rust中则是这样的:
let my_string = maybe_none.unwrap_or_default(); // Assuming `T` is `String`.
你可以像这样来实现Default:
enum Kind {
A,
B,
C,
}
impl Default for Kind {
fn default() -> Self { Kind::A }
}
match表达式
你可以使用match表达式来获取到数值:
let match_value = match returns_some() {
Some(val) => val,
None => "My default value".to_owned(),
};
println!("match {{...}}: {:?}", match_value);
if let表达式
你可以对枚举的指定变体采用一个条件判断:
if let Some(val) = returns_some() {
println!("if let : {:?}", val);
}
如果returns_some()返回的Option是Some(),那么它的内部值将被绑定到标识符val,这是一个比较奇怪的语法,但很有用处
? 运算符
下面的代码展示了?运算符的使用技巧:
use std::fs::read_to_string;
fn main() -> Result<(), std::io::Error> {
let html = render_markdown("../README.md")?;
println!("{}", html);
Ok(())
}
fn render_markdown(file: &str) -> Result<String, std::io::Error> {
let source = read_to_string(file)?;
Ok(markdown::to_html(&source))
}
- 首先我们我们在第3用
-> Result<(), std::io::Error>将main()返回值类型改为Result,注意()是unit type,这是另一种表达“空”的方式。-> Result<(), ...>意味着返回“空”或失败 - 其次,在第10行我们用
std::fs::read_to_string(),它会接受一个路径并返回Result<String, std::io::Error>,这意味着它会返回String类型的文件内容或std::io::Error类型的错误 - 接着在第10行用
?运算符自动进行unwrap,如果结果是失败,那么?运算符会直接将结果返回给调用者main()函数 - 在第4行我们继续使用
?运算符来做自动unwrap,由于main()函数没有调用者,所以如果存在失败,则会导致程序终止 - 由于
main()的返回类型是Result<(), ...>,所以我们在最后需要再写一个Ok(())
? 与 try!
在一些老的文章中,你会读到关于try!宏的内容,try!实际上就是?运算符的先驱,无论谁好谁坏,多了解下总归是好的,我们可以看下 try!的实现
宏不是本教程的重点,不过看下try!的实现源码,相信也不难理解:
macro_rules! r#try {
($expr:expr $(,)?) => {
match $expr {
$crate::result::Result::Ok(val) => val,
$crate::result::Result::Err(err) => {
return $crate::result::Result::Err($crate::convert::From::from(err));
}
}
};
}
try!宏接收一个表达式,并且在match语句中使用了该表达式,如果表达式是Ok则返回其值,如果是失败,则会提前返回且将返回值转化为Result Error类型
错误处理
最后让我们看下,如果文件路径是从环境变量中获取有什么区别:
use std::fs::read_to_string;
fn main() -> Result<(), std::io::Error> {
let html = render_markdown()?;
println!("{}", html);
Ok(())
}
fn render_markdown() -> Result<String, std::io::Error> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(markdown::to_html(&source))
}
我们增加了一行代码,从名为"MARKDOWN"的环境变量获取文件路径,如果没有这个环境变量则程序会报错,我们用一个?表达式来做短路处理,现在我们会遇到一个编译错误:? could not convert the error to std::io::Error …
你现在已经了解了Option和Result,这还不是最麻烦的,最麻烦的是如何处理不同的错误,这我们会在下一篇教程中讲到
总结
Option和Result在Rust中无处不在,你需要对它们有正确的理解。同样Enums也是无处不在,你经常会用到Enums而不是具体的某个字符串、数字或布尔值
在下一篇文章中,我们会介绍关于Err的知识
更多
- 写给前端看的Rust教程(1)从nvm到rust
- 写给前端看的Rust教程(2)从npm到cargo
- 写给前端看的Rust教程(3)配置Visual Studio Code
- 写给前端看的Rust教程(4)Hello World
- 写给前端看的Rust教程(5)借用&所有权
- 写给前端看的Rust教程(6)String 第一部分
- 写给前端看的Rust教程(7)语言篇[上]
- 写给前端看的Rust教程(8)语言篇[中]
- 写给前端看的Rust教程(9)语言篇[下]
- 写给前端看的Rust教程(10)从 Mixins 到 Traits
- 写给前端看的Rust教程(11)Module
- 写给前端看的Rust教程(12)String 第二部分
- 写给前端看的Rust教程(13)Results & Options