Option
Option
空值的问题在于当你尝试像一个非空值那样使用一个空值,会出现某种形式的错误。因为空和非空的属性无处不在,非常容易出现这类错误。然而,空值尝试表达的概念仍然是有意义的:空值是一个因为某种原因目前无效或缺失的值。问题不在于概念而在于具体的实现。为此,
enum Option<T> {
None,
Some(T),
}
当您拥有一个可能存在或不存在的值时,请使用Some(value)
,当值不存在时仅为
// ⚠️
fn take_fifth(value: Vec<i32>) -> i32 {
value[4]
}
fn main() {
let new_vec = vec![1, 2];
let index = take_fifth(new_vec);
}
// thread 'main' panicked at 'index out of bounds: the len is 2 but the index is 4', src\main.rs:34:5
Option<i32>
。这意味着“如果有,请给我
fn take_fifth(value: Vec<i32>) -> Option<i32> {
if value.len() < 5 { // .len() gives the length of the vec.
// It must be at least 5.
None
} else {
Some(value[4])
}
}
fn main() {
let new_vec = vec![1, 2];
let bigger_vec = vec![1, 2, 3, 4, 5];
println!("{:?}, {:?}", take_fifth(new_vec), take_fifth(bigger_vec));
}
None, Some(5)
一个完整的例子如下:
fn find(haystack: &str, needle: char) -> Option<usize> {
for (offset, c) in haystack.char_indices() {
if c == needle {
return Some(offset);
}
}
None
}
find
在字符串haystack
中查找needle
字符,事实上结果会出现两种可能,有(Some(usize)
None
fn main() {
let file_name = "foobar.rs";
match find(file_name, '.') {
None => println!("No file extension found."),
Some(i) => println!("File extension: {}", &file_name[i+1..]),
}
}
None
的情况。这往往是一个好的编程习惯,可以减少潜在的match
会使代码变得臃肿,这也是滋生
属性方法
unwrap
我们可以使用 .unwrap()
获取选项中的值,但使用 .unwrap()
时要小心。就像打开礼物一样:也许里面好东西,或者里面有一条愤怒的蛇。如果您确定的话,只想 .unwrap()
即可。如果解开的值是
// ⚠️
fn take_fifth(value: Vec<i32>) -> Option<i32> {
if value.len() < 4 {
None
} else {
Some(value[4])
}
}
fn main() {
let new_vec = vec![1, 2];
let bigger_vec = vec![1, 2, 3, 4, 5];
println!("{:?}, {:?}",
take_fifth(new_vec).unwrap(), // this one is None. .unwrap() will panic!
take_fifth(bigger_vec).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
当遇到 None
值时会
- 在例子和简单快速的编码中 有的时候你只是需要一个小例子或者一个简单的小程序,输入输出已经确定,你根本没必要花太多时间考虑错误处理,使用
unwrap
变得非常合适。 - 当程序遇到了致命的
bug ,panic 是最优选择
很多时候我们并不需要
fn take_fifth(value: Vec<i32>) -> Option<i32> {
if value.len() < 4 {
None
} else {
Some(value[4])
}
}
fn handle_option(my_option: Vec<Option<i32>>) {
for item in my_option {
match item {
Some(number) => println!("Found a {}!", number),
None => println!("Found a None!"),
}
}
}
fn main() {
let new_vec = vec![1, 2];
let bigger_vec = vec![1, 2, 3, 4, 5];
let mut option_vec = Vec::new(); // Make a new vec to hold our options
// The vec is type: Vec<Option<i32>>. That means a vec of Option<i32>.
option_vec.push(take_fifth(new_vec)); // This pushes "None" into the vec
option_vec.push(take_fifth(bigger_vec)); // This pushes "Some(5)" into the vec
handle_option(option_vec); // handle_option looks at every option in the vec.
// It prints the value if it is Some. It doesn't touch it if it is None.
}
Found a None!
Found a 5!
当然,我们也可以直接去判断
fn take_fifth(value: Vec<i32>) -> Option<i32> {
if value.len() < 4 {
None
} else {
Some(value[4])
}
}
fn main() {
let new_vec = vec![1, 2];
let bigger_vec = vec![1, 2, 3, 4, 5];
let vec_of_vecs = vec![new_vec, bigger_vec];
for vec in vec_of_vecs {
let inside_number = take_fifth(vec);
if inside_number.is_some() {
// .is_some() returns true if we get Some, false if we get None
println!("We got: {}", inside_number.unwrap()); // now it is safe to use .unwrap() because we already checked
} else {
println!("We got nothing.");
}
}
}
We got nothing.
We got: 5
unwrap_or
fn unwrap_or<T>(option: Option<T>, default: T) -> T {
match option {
None => default,
Some(value) => value,
}
}
unwrap_or
提供了一个默认值default
,当值为None
时返回default
:
fn main() {
assert_eq!(extension("foo.rs").unwrap_or("rs"), "rs");
assert_eq!(extension("foo").unwrap_or("rs"), "rs");
}
map
假如我们要在一个字符串中找到文件的扩展名,比如
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
简化:
// map 是标准库中的方法
fn map<F, T, A>(option: Option<T>, f: F) -> Option<A> where F: FnOnce(T) -> A {
match option {
None => None,
Some(value) => Some(f(value)),
}
}
// 使用 map 去掉 match
fn extension(file_name: &str) -> Option<&str> {
find(file_name, '.').map(|i| &file_name[i+1..])
}
map
如果有值Some(T)
会执行f
,反之直接返回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
差不多,不过 map
只是把值为 Some(t)
重新映射了一遍,and_then
则会返回另一个 Option
。如果我们在一个文件路径中找到它的扩展名,这时候就会变得尤为重要:
use std::path::Path;
fn file_name(file_path: &str) -> Option<&str> {
let path = Path::new(file_path);
path.file_name().to_str()
}
fn file_path_ext(file_path: &str) -> Option<&str> {
file_name(file_path).and_then(extension)
}