前言:
Option是组成Rust程序的基石,熟练使用Rust的Option可以帮助我们进行程序的开发。但是Option这里的知识和细节比较绕,说白了就是各种套娃,本篇文章意在梳理Option的一些细节。
关于Option的基本构成,这里不讲了,想必读者应当都会。
首先,提供Rust标准库的官方文档供读者查阅。
Option in std::option - Rust (rustwiki.org)
目录
- 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<String> = 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<String>`, 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<&
as Deref>::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<i32> = None;
- // some.as_deref(); //error
-
- let some: Option<String> = 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
as Deref>::Target> - 从 Option
(或 &mut Option) 转换为 Option<&mut T::Target>。 -
- 在这里保留原始的 Option,创建一个包含对内部类型的 Deref::Target 类型的可变引用的新的 Option。
as系列方法能够帮助我们做什么呢, 难道仅仅是令人头疼的类型转换吗? 为了能够更好的理解,我们可以看一下这个题,合并链表。
这是一份实现代码——你可以在题解中找到这份答案,这份代码并不知作者写的。
- // 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<Box
>, list2: Option<Box>) -> Option<Box> { - //考虑使用转移所有权的方法,这样构造的新的链表效率会更高
- //因为要转移所有权,所以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的用法还有很多,不能一一列举,如果日后作者在开发过程中踩坑,还会来继续更新的。