定制小程序开发Rust——关于Option详解

前言:

Option定制小程序开发是组成程序的基石,熟练使用Rust的Option定制小程序开发可以帮助我们进行程序的开发。但是Option定制小程序开发这里的知识和细节比较绕,定制小程序开发说白了就是各种套娃,定制小程序开发本篇文章意在梳理Option定制小程序开发的一些细节。

关于Option定制小程序开发的基本构成,定制小程序开发这里不讲了,定制小程序开发想必读者应当都会。

首先,提供Rust定制小程序开发标准库的官方文档供读者查阅。

目录


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

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

  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的用法还有很多,不能一一列举,如果日后作者在开发过程中踩坑,还会来继续更新的。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发