当前位置:   article > 正文

Rust——关于Option详解_rust option

rust option

前言:

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


区分Option<T>中的T为&的情况

  1. fn work_1() {
  2. let foo1 = Foo;
  3. let foo2 = Foo;
  4. let val_some = Some(foo1);
  5. let ref_some = Some(&foo2);
  6. }

Option<T>,对于val_some的类型,T为Foo,对于ref_some的类型T为Foo&。也就是说

val_some: Option<Foo> 

ref_some: OPtion<&Foo>

对于后续的文章,我会将其两者分开说明。

Some包装遵守赋值操作符的规则

让我们回想一个规则。Rust中的某一个类型如果没有实现Copy trait,那么其赋值操作是所有权的转移,如果实现了,就是复制。如果对一个变量进行包装,同样遵循这个道理。

  1. #[allow(unused)]
  2. fn work_1() {
  3. let foo = Foo;
  4. let some = Some(foo);
  5. let ref_foo = &foo; //error
  6. let a = 10;
  7. let some = Some(a);
  8. let ref_a = &a;
  9. }
  1. jan@jan:~/code/rust/option_$ cargo run
  2. Compiling option_ v0.1.0 (/home/jan/code/rust/option_)
  3. error[E0382]: borrow of moved value: `foo`
  4. --> src/main.rs:11:19
  5. |
  6. 9 | let foo = Foo;
  7. | --- move occurs because `foo` has type `Foo`, which does not implement the `Copy` trait
  8. 10 | let some = Some(foo);
  9. | --- value moved here
  10. 11 | let ref_foo = &foo; //error
  11. | ^^^^ value borrowed here after move
  12. For more information about this error, try `rustc --explain E0382`.
  13. error: could not compile `option_` due to previous error

所有的内置类型都实现了Copy trait,所以上面的三行不能通过编译,下面的三行可以。

区别&mut; mut &; mut & mut

这里的问题就像C++中的const *, * const, const * const一样。你完全可以类比。

  1. #[allow(unused)]
  2. fn work_2() {
  3. let mut a = 10;
  4. {
  5. let b = 20;
  6. let ref_a = &a; // -> &i32
  7. ref_a = &b; //将引用指向b不可以
  8. let mut mut_ref_a = &a; //mut &i32
  9. *mut_ref_a = 20; //更改变量本身,不可以
  10. mut_ref_a = &b; //将引用指向b,可以
  11. }
  12. let ref_mut_a = &mut a; // -> &mut i32
  13. *ref_mut_a = 20; //更改变量本身的值,可以
  14. }
  • & mut代表着对一个变量的可变引用,引用的变量是可变的,但是引用本身是不可变的,也就是说当我确定引用一个变量的时候,就不能再引用其他变量了。
  • mut &代表着引用本身是可变的,即这个引用既可以引用a,又可以引用b,但是引用的变量是不可变的。
  • & mut & 即代表着上述两者的结合,引用本身是可变的,并且引用的变量也是可变的。
  1. jan@jan:~/code/rust/option_$ cargo run
  2. Compiling option_ v0.1.0 (/home/jan/code/rust/option_)
  3. error[E0384]: cannot assign twice to immutable variable `ref_a`
  4. --> src/main.rs:24:9
  5. |
  6. 23 | let ref_a = &a; // -> &i32
  7. | -----
  8. | |
  9. | first assignment to `ref_a`
  10. | help: consider making this binding mutable: `mut ref_a`
  11. 24 | ref_a = &b; //将引用指向b不可以
  12. | ^^^^^^^^^^ cannot assign twice to immutable variable
  13. error[E0594]: cannot assign to `*mut_ref_a`, which is behind a `&` reference
  14. --> src/main.rs:26:9
  15. |
  16. 25 | let mut mut_ref_a = &a; //mut &i32
  17. | -- help: consider changing this to be a mutable reference: `&mut a`
  18. 26 | *mut_ref_a = 20; //更改变量本身,不可以
  19. | ^^^^^^^^^^^^^^^ `mut_ref_a` is a `&` reference, so the data it refers to cannot be written
  20. Some errors have detailed explanations: E0384, E0594.
  21. For more information about an error, try `rustc --explain E0384`.
  22. error: could not compile `option_` due to 2 previous errors

match和Option

match匹配Option是开发中经常使用的组合。

  1. #[allow(unused)]
  2. fn match_ref_some() {
  3. let some = Some(String::from("hello"));
  4. let ref_some = &some;
  5. match ref_some {
  6. Some(s) => println!("{}",s),
  7. None => println!("no string"),
  8. }
  9. match some {
  10. Some(s) => println!("{}",s),
  11. None => println!("no string"),
  12. }
  13. println!("{}",some.unwrap()); //error
  14. }

对于引用来说,匹配出来的值依旧是引用,也就是&T,对于变量本身来说,匹配出来的值就是T本身 。

  1. jan@jan:~/code/rust/some__$ cargo check
  2. Checking some__ v0.1.0 (/home/jan/code/rust/some__)
  3. error[E0382]: use of partially moved value: `some`
  4. --> src/main.rs:188:19
  5. |
  6. 184 | Some(s) => println!("{}",s),
  7. | - value partially moved here
  8. ...
  9. 188 | println!("{}",some.unwrap());
  10. | ^^^^ value used here after partial move
  11. |
  12. = note: partial move occurs because value has type `String`, which does not implement the `Copy` trait
  13. help: borrow this field in the pattern to avoid moving `some.0`
  14. |
  15. 184 | Some(ref s) => println!("{}",s),
  16. | +++
  17. For more information about this error, try `rustc --explain E0382`.
  18. error: could not compile `some__` due to previous error

这里匹配的是一个&Option,所以s是一个&String,不会造成所有权的转移。

  1. match ref_some {
  2. Some(s) => println!("{}",s),
  3. None => println!("no string"),
  4. }

而这里匹配的是Option,所以s为String,会发生所有权的转移,就是原来的some:Option<String>变量中的值,被转移到了匿名Option<String>中,就是代码Some(s)中的s,编译给出了一个部分移动的警告,也就是所some本身并没有被移动,而是其中的值被移动了,但是Option枚举中的Some就那么一个值,所以看着像是整个some都移动了,实则不然。

  1. match some {
  2. Some(s) => println!("{}",s),
  3. None => println!("no string"),
  4. }

Option和迭代器 

就连Option上也有迭代器,真是不可思议。和其他的迭代器一样,只不过因为Option中只能是Some或者None,当是Some的时候,第一次调用next返回Some中的值,其余情况,包括None,均返回None。

  1. #[allow(unused)]
  2. #[test]
  3. fn work_4() {
  4. let some = Some(String::from("hello"));
  5. let mut i = some.into_iter();
  6. assert_eq!(i.next().as_deref(),Some("hello"));
  7. assert_eq!(i.next(),None);
  8. let none: Option<String> = None;
  9. assert_eq!(none.iter().next(),None);
  10. }

as系列方法

as系类方法提供在不结构的请款下改变T的类型,这些as方法十分的方便,但是却有些不好掌握。

在了解as系列方法前:请先记住一个规则:所谓的as_XXX,均对于T来说,而不是Option<T>来as说,这样可能更好的理解。

as_ref和map

  1. pub const fn as_ref(&self) -> Option<&T>
  2. 从 &Option<T> 转换为 Option<&T>。

 就是将T变为 &T。

我想你一定有个疑问,什么情况下需要这样转变。答案是你想使用Option中存放的值,但是却又不想失去其所有权的情况下,也就是平常所说的按照引用的方式传参。

例如标Option的impl中有一个名为map的方法,就和迭代器的map功能是一样的,但注意,Option的此方法非缓式评估,或者说非惰性求值,因为完全没有必要,我们看其函数原型。

  1. pub fn map<U, F>(self, f: F) -> Option<U>
  2. where
  3. F: FnOnce(T) -> U,

如果我们将Option<T> 传入,那么就原先的Some就会失去所有权,就像是这样。

  1. #[allow(unused)]
  2. #[test]
  3. fn work_5() {
  4. let some = Some(String::from("hello"));
  5. let size = some.map(|s| s.len());
  6. println!("{}",some.unwrap());
  7. }

  1. error[E0382]: use of moved value: `some`
  2. --> src/main.rs:64:19
  3. |
  4. 62 | let some = Some(String::from("hello"));
  5. | ---- move occurs because `some` has type `Option<String>`, which does not implement the `Copy` trait
  6. 63 | let size = some.map(|s| s.len());
  7. | ---------------- `some` moved due to this method call
  8. 64 | println!("{}",some.unwrap());
  9. | ^^^^ value used here after move
  10. |
  11. note: this function takes ownership of the receiver `self`, which moves `some`
  12. --> /home/jan/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:903:28
  13. |
  14. 903 | pub const fn map<U, F>(self, f: F) -> Option<U>
  15. | ^^^^
  16. help: consider calling `.as_ref()` to borrow the type's contents
  17. |
  18. 63 | let size = some.as_ref().map(|s| s.len());
  19. | +++++++++
  20. For more information about this error, try `rustc --explain E0382`.

但如果我们将Option<T>变为Option<&T>就是不一样的了,对于引用来说,仅仅就是一个指针而已,也无所谓移动不移动的了,就像是这样。

  1. #[allow(unused)]
  2. #[test]
  3. fn work_5() {
  4. let some = Some(String::from("hello"));
  5. let size = some.map(|s| s.len());
  6. // println!("{}",some.unwrap()); //error
  7. let some = Some(String::from("hello"));
  8. let size = some.as_ref().map(|s| s.len());
  9. println!("{}",some.unwrap());
  10. }

标准库的实现也是非常的简单。

  1. pub const fn as_ref(&self) -> Option<&T> {
  2. match *self {
  3. Some(ref x) => Some(x),
  4. None => None,
  5. }
  6. }

as_deref

  1. pub fn as_deref(&self) -> Option<&<T as Deref>::Target>
  2. Option<T> (或 &Option<T>) 转换为 Option<&T::Target>。
  3. 将原始 Option 保留在原位,创建一个带有对原始 Option 的引用的新 Option,并通过 Deref 强制执行其内容。

也就是说,as_deref相当于对T进行了一次解引用操作并加上引用。当然,T必须实现了Deref这个trait。

如果你对None调用这个方法,结果依旧是None。

  1. #[allow(unused)]
  2. #[test]
  3. fn work_3() {
  4. let s = String::from("hello");
  5. let some = Some(s);
  6. assert_eq!(some.as_deref(),Some("hello"));
  7. println!("{:?}",some);
  8. let some: Option<i32> = None;
  9. // some.as_deref(); //error
  10. let some: Option<String> = None;
  11. assert_eq!(some.as_deref(),None);
  12. }

 将原始 Option 保留在原位,创建一个带有对原始 Option 的引用的新 Option,并通过 Deref 强制执行其内容。这句话是不是让你很不解,我们一点点分析。

所谓的保留在原位,即是不发生move语义,也就是我们上面所说的as_ref的情形。我们进入源码,可看见这样简短的实现方法。

  1. pub fn as_deref(&self) -> Option<&T::Target> {
  2. self.as_ref().map(|t| t.deref())
  3. }

所说的并通过 Deref 强制执行其内容,就是调用deref方法而已。总结的来说,就是获得Option<&T>然后再进行解引用(注意,deref返回值为&T::Target,所以返回值并没有什么好疑惑的)。

或者说更加新版的标准库是这样实现的

  1. pub const fn as_deref(&self) -> Option<&T::Target>
  2. where
  3. T: ~const Deref,
  4. {
  5. match self.as_ref() {
  6. Some(t) => Some(t.deref()),
  7. None => None,
  8. }
  9. }

这里t的类型为&T。

as_deref_mut

和as_deref很像,就对在返回类型的可变性进行了更改。

  1. pub fn as_deref_mut(&mut self) -> Option<&mut <T as Deref>::Target>
  2. Option<T> (或 &mut Option<T>) 转换为 Option<&mut T::Target>。
  3. 在这里保留原始的 Option,创建一个包含对内部类型的 Deref::Target 类型的可变引用的新的 Option

实战演练

as系列方法能够帮助我们做什么呢, 难道仅仅是令人头疼的类型转换吗? 为了能够更好的理解,我们可以看一下这个题,合并链表。

21. 合并两个有序链表 - 力扣(LeetCode)

这是一份实现代码——你可以在题解中找到这份答案,这份代码并不知作者写的。

  1. // Definition for singly-linked list.
  2. // #[derive(PartialEq, Eq, Clone, Debug)]
  3. // pub struct ListNode {
  4. // pub val: i32,
  5. // pub next: Option<Box<ListNode>>
  6. // }
  7. // impl ListNode {
  8. // #[inline]
  9. // fn new(val: i32) -> Self {
  10. // ListNode {
  11. // next: None,
  12. // val
  13. // }
  14. // }
  15. // }
  16. impl Solution {
  17. pub fn merge_two_lists(list1: Option<Box<ListNode>>, list2: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
  18. //考虑使用转移所有权的方法,这样构造的新的链表效率会更高
  19. //因为要转移所有权,所以list应当更改mut属性
  20. let mut list1 = list1;
  21. let mut list2 = list2;
  22. //新的链表
  23. let mut ret = ListNode::new(0);
  24. //一个mut & mut ,当作指针
  25. let mut p = &mut ret;
  26. //我们不应当获得list的所有权,因为是在一个loop中
  27. while let (Some(n1), Some(n2)) = (list1.as_ref(),list2.as_ref()) {
  28. if n1.val < n2.val {
  29. //转移所有权
  30. p.next = list1;
  31. //p指向list1:就是指针向后移动,因为p值&mut,所以应当使用as_mut
  32. p = p.next.as_mut().unwrap();
  33. //list1领导list1剩余的尾部
  34. list1 = p.next.take();
  35. } else {
  36. //逻辑同上
  37. p.next = list2;
  38. p = p.next.as_mut().unwrap();
  39. list2 = p.next.take();
  40. }
  41. //这里这样写是因为隐式解引用规则,全部样貌应当是
  42. //p = p.next.as_mut().unwrap().as_mut();
  43. //或者是
  44. // &mut **p.next.as_mut().unwrap();
  45. // p = &mut **p.next.as_mut().unwrap();
  46. //首先next返回Option<T>,
  47. //使用as_mut方法,-> Option<&mut T>
  48. //再使用unwrap方法得到的应当是一个&mut Box<ListNode>
  49. //根据隐式解引用转换规则,可实现Box<T> -> &T
  50. p = p.next.as_mut().unwrap();
  51. }
  52. p.next = if list1.is_some() { list1 } else {list2 };
  53. ret.next
  54. }

常用方法

filter

一个过滤器

  1. pub fn filter<P>(self, predicate: P) -> Option<T>
  2. where
  3. P: FnOnce(&T) -> bool,
  4. 如果选项为 None,则返回 None; 否则,使用包装的值调用 predicate 并返回:

predicate指的是一个一元谓词。可以这样使用。

  1. #[allow(unused)]
  2. fn is_even(x: &i32) -> bool {
  3. x % 2 == 0
  4. }
  5. #[allow(unused)]
  6. #[test]
  7. fn work_6() {
  8. let some = Some(3);
  9. assert_eq!(some.filter(|x| is_even(x)),None);
  10. let some = Some(4);
  11. assert_eq!(some.filter(|x| is_even(x)),Some(4));
  12. assert_eq!(None.filter(|x| is_even(x)),None);
  13. }

or 

  1. pub fn or(self, optb: Option<T>) -> Option<T>
  2. 如果包含值,则返回选项,否则返回 optb。
  3. 传递给 or 的参数会被急切地评估; 如果要传递函数调用的结果,建议使用 or_else,它是延迟计算的。

Box和Option

todo

总结

关于Option的用法还有很多,不能一一列举,如果日后作者在开发过程中踩坑,还会来继续更新的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/291300
推荐阅读
相关标签
  

闽ICP备14008679号