前言:
Option是组成Rust程序的基石,熟练使用Rust的Option可以帮助我们进行程序的开发。但是Option这里的知识和细节比较绕,说白了就是各种套娃,本篇文章意在梳理Option的一些细节。
关于Option的基本构成,这里不讲了,想必读者应当都会。
首先,提供Rust标准库的官方文档供读者查阅。
Option in std::option - Rust (rustwiki.org)
目录
区分Option中的T为&的情况
Some包装遵守赋值操作符的规则
区别&mut; mut &; mut & mut
Option和迭代器
as系列方法
as_ref和map
as_deref
as_deref_mut
fn work_1() {
let foo1 = Foo;
let foo2 = Foo;
let val_some = Some(foo1);
let ref_some = Some(&foo2);
}
Option
val_some: Option
ref_some: OPtion<&Foo>
对于后续的文章,我会将其两者分开说明。
让我们回想一个规则。Rust中的某一个类型如果没有实现Copy trait,那么其赋值操作是所有权的转移,如果实现了,就是复制。如果对一个变量进行包装,同样遵循这个道理。
#[allow(unused)]
fn work_1() {
let foo = Foo;
let some = Some(foo);
let ref_foo = &foo; //error
let a = 10;
let some = Some(a);
let ref_a = &a;
}
jan@jan:~/code/rust/option_$ cargo run
Compiling option_ v0.1.0 (/home/jan/code/rust/option_)
error[E0382]: borrow of moved value: `foo`
--> src/main.rs:11:19
|
9 | let foo = Foo;
| --- move occurs because `foo` has type `Foo`, which does not implement the `Copy` trait
10 | let some = Some(foo);
| --- value moved here
11 | let ref_foo = &foo; //error
| ^^^^ value borrowed here after move
For more information about this error, try `rustc --explain E0382`.
error: could not compile `option_` due to previous error
所有的内置类型都实现了Copy trait,所以上面的三行不能通过编译,下面的三行可以。
这里的问题就像C++中的const *, * const, const * const一样。你完全可以类比。
#[allow(unused)]
fn work_2() {
let mut a = 10;
{
let b = 20;
let ref_a = &a; // -> &i32
ref_a = &b; //将引用指向b不可以
let mut mut_ref_a = &a; //mut &i32
*mut_ref_a = 20; //更改变量本身,不可以
mut_ref_a = &b; //将引用指向b,可以
}
let ref_mut_a = &mut a; // -> &mut i32
*ref_mut_a = 20; //更改变量本身的值,可以
}
jan@jan:~/code/rust/option_$ cargo run
Compiling option_ v0.1.0 (/home/jan/code/rust/option_)
error[E0384]: cannot assign twice to immutable variable `ref_a`
--> src/main.rs:24:9
|
23 | let ref_a = &a; // -> &i32
| -----
| |
| first assignment to `ref_a`
| help: consider making this binding mutable: `mut ref_a`
24 | ref_a = &b; //将引用指向b不可以
| ^^^^^^^^^^ cannot assign twice to immutable variable
error[E0594]: cannot assign to `*mut_ref_a`, which is behind a `&` reference
--> src/main.rs:26:9
|
25 | let mut mut_ref_a = &a; //mut &i32
| -- help: consider changing this to be a mutable reference: `&mut a`
26 | *mut_ref_a = 20; //更改变量本身,不可以
| ^^^^^^^^^^^^^^^ `mut_ref_a` is a `&` reference, so the data it refers to cannot be written
Some errors have detailed explanations: E0384, E0594.
For more information about an error, try `rustc --explain E0384`.
error: could not compile `option_` due to 2 previous errors
match匹配Option是开发中经常使用的组合。
#[allow(unused)]
fn match_ref_some() {
let some = Some(String::from("hello"));
let ref_some = &some;
match ref_some {
Some(s) => println!("{}",s),
None => println!("no string"),
}
match some {
Some(s) => println!("{}",s),
None => println!("no string"),
}
println!("{}",some.unwrap()); //error
}
对于引用来说,匹配出来的值依旧是引用,也就是&T,对于变量本身来说,匹配出来的值就是T本身 。
jan@jan:~/code/rust/some__$ cargo check
Checking some__ v0.1.0 (/home/jan/code/rust/some__)
error[E0382]: use of partially moved value: `some`
--> src/main.rs:188:19
|
184 | Some(s) => println!("{}",s),
| - value partially moved here
...
188 | println!("{}",some.unwrap());
| ^^^^ value used here after partial move
|
= note: partial move occurs because value has type `String`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `some.0`
|
184 | Some(ref s) => println!("{}",s),
| +++
For more information about this error, try `rustc --explain E0382`.
error: could not compile `some__` due to previous error
这里匹配的是一个&Option,所以s是一个&String,不会造成所有权的转移。
match ref_some {
Some(s) => println!("{}",s),
None => println!("no string"),
}
而这里匹配的是Option,所以s为String,会发生所有权的转移,就是原来的some:Option
match some {
Some(s) => println!("{}",s),
None => println!("no string"),
}
就连Option上也有迭代器,真是不可思议。和其他的迭代器一样,只不过因为Option中只能是Some或者None,当是Some的时候,第一次调用next返回Some中的值,其余情况,包括None,均返回None。
#[allow(unused)]
#[test]
fn work_4() {
let some = Some(String::from("hello"));
let mut i = some.into_iter();
assert_eq!(i.next().as_deref(),Some("hello"));
assert_eq!(i.next(),None);
let none: Option = None;
assert_eq!(none.iter().next(),None);
}
as系类方法提供在不结构的请款下改变T的类型,这些as方法十分的方便,但是却有些不好掌握。
在了解as系列方法前:请先记住一个规则:所谓的as_XXX,均对于T来说,而不是Option
pub const fn as_ref(&self) -> Option<&T>
从 &Option 转换为 Option<&T>。
就是将T变为 &T。
我想你一定有个疑问,什么情况下需要这样转变。答案是你想使用Option中存放的值,但是却又不想失去其所有权的情况下,也就是平常所说的按照引用的方式传参。
例如标Option的impl中有一个名为map的方法,就和迭代器的map功能是一样的,但注意,Option的此方法非缓式评估,或者说非惰性求值,因为完全没有必要,我们看其函数原型。
pub fn map(self, f: F) -> Option
where
F: FnOnce(T) -> U,
如果我们将Option
#[allow(unused)]
#[test]
fn work_5() {
let some = Some(String::from("hello"));
let size = some.map(|s| s.len());
println!("{}",some.unwrap());
}
error[E0382]: use of moved value: `some`
--> src/main.rs:64:19
|
62 | let some = Some(String::from("hello"));
| ---- move occurs because `some` has type `Option`, which does not implement the `Copy` trait
63 | let size = some.map(|s| s.len());
| ---------------- `some` moved due to this method call
64 | println!("{}",some.unwrap());
| ^^^^ value used here after move
|
note: this function takes ownership of the receiver `self`, which moves `some`
--> /home/jan/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:903:28
|
903 | pub const fn map(self, f: F) -> Option
| ^^^^
help: consider calling `.as_ref()` to borrow the type's contents
|
63 | let size = some.as_ref().map(|s| s.len());
| +++++++++
For more information about this error, try `rustc --explain E0382`.
但如果我们将Option
#[allow(unused)]
#[test]
fn work_5() {
let some = Some(String::from("hello"));
let size = some.map(|s| s.len());
// println!("{}",some.unwrap()); //error
let some = Some(String::from("hello"));
let size = some.as_ref().map(|s| s.len());
println!("{}",some.unwrap());
}
标准库的实现也是非常的简单。
pub const fn as_ref(&self) -> Option<&T> {
match *self {
Some(ref x) => Some(x),
None => None,
}
}
pub fn as_deref(&self) -> Option<&::Target>
从 Option (或 &Option) 转换为 Option<&T::Target>。
将原始 Option 保留在原位,创建一个带有对原始 Option 的引用的新 Option,并通过 Deref 强制执行其内容。
也就是说,as_deref相当于对T进行了一次解引用操作并加上引用。当然,T必须实现了Deref这个trait。
如果你对None调用这个方法,结果依旧是None。
#[allow(unused)]
#[test]
fn work_3() {
let s = String::from("hello");
let some = Some(s);
assert_eq!(some.as_deref(),Some("hello"));
println!("{:?}",some);
let some: Option = None;
// some.as_deref(); //error
let some: Option = None;
assert_eq!(some.as_deref(),None);
}
将原始 Option 保留在原位,创建一个带有对原始 Option 的引用的新 Option,并通过 Deref 强制执行其内容。这句话是不是让你很不解,我们一点点分析。
所谓的保留在原位,即是不发生move语义,也就是我们上面所说的as_ref的情形。我们进入源码,可看见这样简短的实现方法。
pub fn as_deref(&self) -> Option<&T::Target> {
self.as_ref().map(|t| t.deref())
}
所说的并通过 Deref 强制执行其内容,就是调用deref方法而已。总结的来说,就是获得Option<&T>然后再进行解引用(注意,deref返回值为&T::Target,所以返回值并没有什么好疑惑的)。
或者说更加新版的标准库是这样实现的
pub const fn as_deref(&self) -> Option<&T::Target>
where
T: ~const Deref,
{
match self.as_ref() {
Some(t) => Some(t.deref()),
None => None,
}
}
这里t的类型为&T。
和as_deref很像,就对在返回类型的可变性进行了更改。
pub fn as_deref_mut(&mut self) -> Option<&mut ::Target>
从 Option (或 &mut Option) 转换为 Option<&mut T::Target>。
在这里保留原始的 Option,创建一个包含对内部类型的 Deref::Target 类型的可变引用的新的 Option。
as系列方法能够帮助我们做什么呢, 难道仅仅是令人头疼的类型转换吗? 为了能够更好的理解,我们可以看一下这个题,合并链表。
21. 合并两个有序链表 - 力扣(LeetCode)
这是一份实现代码——你可以在题解中找到这份答案,这份代码并不知作者写的。
// Definition for singly-linked list.
// #[derive(PartialEq, Eq, Clone, Debug)]
// pub struct ListNode {
// pub val: i32,
// pub next: Option>
// }
// impl ListNode {
// #[inline]
// fn new(val: i32) -> Self {
// ListNode {
// next: None,
// val
// }
// }
// }
impl Solution {
pub fn merge_two_lists(list1: Option>, list2: Option>) -> Option> {
//考虑使用转移所有权的方法,这样构造的新的链表效率会更高
//因为要转移所有权,所以list应当更改mut属性
let mut list1 = list1;
let mut list2 = list2;
//新的链表
let mut ret = ListNode::new(0);
//一个mut & mut ,当作指针
let mut p = &mut ret;
//我们不应当获得list的所有权,因为是在一个loop中
while let (Some(n1), Some(n2)) = (list1.as_ref(),list2.as_ref()) {
if n1.val < n2.val {
//转移所有权
p.next = list1;
//p指向list1:就是指针向后移动,因为p值&mut,所以应当使用as_mut
p = p.next.as_mut().unwrap();
//list1领导list1剩余的尾部
list1 = p.next.take();
} else {
//逻辑同上
p.next = list2;
p = p.next.as_mut().unwrap();
list2 = p.next.take();
}
//这里这样写是因为隐式解引用规则,全部样貌应当是
//p = p.next.as_mut().unwrap().as_mut();
//或者是
// &mut **p.next.as_mut().unwrap();
// p = &mut **p.next.as_mut().unwrap();
//首先next返回Option,
//使用as_mut方法,-> Option<&mut T>
//再使用unwrap方法得到的应当是一个&mut Box
//根据隐式解引用转换规则,可实现Box -> &T
p = p.next.as_mut().unwrap();
}
p.next = if list1.is_some() { list1 } else {list2 };
ret.next
}
一个过滤器
pub fn filter(self, predicate: P) -> Option
where
P: FnOnce(&T) -> bool,
如果选项为 None,则返回 None; 否则,使用包装的值调用 predicate 并返回:
predicate指的是一个一元谓词。可以这样使用。
#[allow(unused)]
fn is_even(x: &i32) -> bool {
x % 2 == 0
}
#[allow(unused)]
#[test]
fn work_6() {
let some = Some(3);
assert_eq!(some.filter(|x| is_even(x)),None);
let some = Some(4);
assert_eq!(some.filter(|x| is_even(x)),Some(4));
assert_eq!(None.filter(|x| is_even(x)),None);
}
pub fn or(self, optb: Option) -> Option
如果包含值,则返回选项,否则返回 optb。
传递给 or 的参数会被急切地评估; 如果要传递函数调用的结果,建议使用 or_else,它是延迟计算的。
todo
关于Option的用法还有很多,不能一一列举,如果日后作者在开发过程中踩坑,还会来继续更新的。