• 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为&的情况

    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,对于val_some的类型,T为Foo,对于ref_some的类型T为Foo&。也就是说

    val_some: Option 

    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变量中的值,被转移到了匿名Option中,就是代码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来as说,这样可能更好的理解。

    as_ref和map

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

     就是将T变为 &T。

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

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

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

    如果我们将Option 传入,那么就原先的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(self, f: F) -> Option
    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变为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<&as Deref>::Target>
    2. Option (或 &Option) 转换为 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 as Deref>::Target>
    2. Option (或 &mut Option) 转换为 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>
    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>, list2: Option<Box>) -> Option<Box> {
    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
    47. //使用as_mut方法,-> Option<&mut T>
    48. //再使用unwrap方法得到的应当是一个&mut Box
    49. //根据隐式解引用转换规则,可实现Box -> &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

      (self, predicate: P) -> Option

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

    Box和Option

    todo

    总结

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

  • 相关阅读:
    共模电感与差模电感总结
    matlab 小数据法求liyapunov指数
    支付宝小程序关键词优化:引领数字商业的未来
    【Matplotlib绘制图像大全】(十九):Matplotlib绘制等高线
    36岁,大龄剩男,2024上半年总结......
    ThinkPHP6 输出二维码图片格式 解决与 Debug 的冲突
    网络爬虫:如何有效的检测分布式爬虫
    文件外发怎么保证安全
    七、使用kubeadm搭建生产环境单master多node节点的k8s集群
    Prometheus简介和安装教程
  • 原文地址:https://blog.csdn.net/JAN6055/article/details/125774473