在rust的设计中,Option的设计非常巧妙,避免了其它语言中常见的Null引起的各种错误和异常。但Option与所有权的结合,尤其是在一些特定的数据结构,如链表、图等设计中,加上引用的各种约束,就使得Option的使用变得复杂起来。
1、unwrap方法
在确认Option不为None的情况下,可以用unwrap方法拆解出其中的值,并获取值的所有权。这里要强调的是,unwrap会消费Option本身的值,后面就不能再用了。
let a = Some(Box::new(5));
let d = a.unwrap();
// println!("{:?}", a); // cannot use 'a' again.
println!("{:?}", d);
unwrap开头的还有一系列方法,分别对应各种情况,可以参考官方标准库的API文档。
但这里有一个所有权的限制,因为涉及到其内部值的所有权的转移,所以只有Option原始变量本身可以调用unwrap方法,其引用(包括可变引用)均不可调用。这和unwrap的实现方法有关系,因为其消费了原始变量。下方代码不可编译:
let mut a = Some(Box::new(5));
let b = &mut a;
let c = b.unwrap(); // Error! cannot move out of `*b` which is behind a mutable reference
这种设计让链表的实现变得困难,结合前面学习到的struct的引用情况下的所有权的限制,形成了一个所有权引用借用的“迷宫”。
unwrap的实现源代码,供参考学习:
#[inline]
#[track_caller]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_option", issue = "67441")]
pub const fn unwrap(self) -> T {
match self {
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
}
}
2、take方法
take方法可以从Option中取出值,为原来的Option变量留下None值,但原来的变量还有效(但实际上没什么用处了,因为只剩下None了)。take方法的原始值或者引用必须为mut类型。强调一下,借用情况下可以调用take方法。或者说指向Option的可变引用(指针)可以调用take方法。
fn main() {
let mut a = Some(Box::new(5));
let mut b = &mut a;
let c = &mut b;
let d = c.take();
println!("{:?}", c);
println!("{:?}", d);
println!("{:?}", a);
}
输出:
None
Some(5)
None
但上面这两个方法,都改变了Option变量本身。如果不改变或受到限制无法改变Option本身时,只想借用或可变借用其中wrap的值应该怎么办呢?这也是在实现链表时遇到的让人掉坑的问题。幸好Option提供了 as_ref 和 as_mut方法。
3、as_ref 方法
as_ref方法的签名是:
fn as_ref(&self) -> Option<&T>
该方法将对Option的引用变为对Option所包含对象的不可变引用,并且返回一个新的Option。对这个新的Option进行unwrap操作,可以获得原Option所包含的对象的不可变引用。原始Option变量及其引用,均可调用as_ref方法。例如:
let a = Some(Box::new(5));
let b = a.as_ref();
let c = b.unwrap();
println!("{:?}", a);
println!("{:?}", b);
println!("{:?}", c);
输出为:
Some(5)
Some(5)
5
强调几点:
1、在以上代码中没有改变原始变量a,故a仍然可用;
2、b为通过as_ref生成的一个新的Option;对b进行unwrap,不影响a;
3、c为a/b包含的Box的不可变引用。
疑惑的一点:在实验过程中,不像在第一点中提到的unwrap方法,对变量b进行unwrap后,b仍然可用,这点和在第一点中遇到的不一样,不知道为什么。可能还是对rust学习的不够深入吧。
下面稍微复杂一点的代码也可以运行,但也有疑惑的一点:e为c的引用,按照第一点学的,引用不能调用unwrap方法,但这里竟然也可以调用,也和之前学的不一致,需要以后对rust更熟悉了再来看。
fn main() {
let a = Some(Point { x: 5, y: 5 });
let b = &a;
let c = b.as_ref(); // c is a new Option;
let e = &c;
let d = e.unwrap();
println!("{:?}", d.x);
println!("{:?}", c);
println!("{:?}", e); // ?
println!("{:?}", b);
println!("{:?}", a);
}
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
4、as_mut方法
与as_ref类似,as_mut只是返回了Option包含的数据的可变引用,但在实际实验时,发现其和as_ref还是有一些细微的区别,也让人有些不明白,如下方代码:
let mut a = Some(Point { x: 5, y: 5 });
let b = &mut a;
let c = b.as_mut(); // c is a new Option;
let e = &c;
let d = e.unwrap(); // Error! cannot move out of `*e` which is behind a shared reference
e为c的引用,最后一行又报错了,又不允许通过引用调用unwrap方法了,又和第一点学的保持一致了!同时,对于新生成的Option值,在调用unwrap方法后,也变得不再可用了。下面代码可以编译运行(Point与上面相同):
fn main() {
let mut a = Some(Point { x: 5, y: 5 });
// let b = a.as_mut();
let b = &mut a;
let c = b.as_mut(); // c is a new Option;
let d = c.unwrap();
d.x += 10;
let e = &mut d.y;
*e += 20;
println!("{:?}", d.x);
// println!("{:?}", c); // c is not available because of method call of "unwrap".
println!("{:?}", b);
println!("{:?}", a);
}
输出:
15
Some(Point { x: 15, y: 25 })
Some(Point { x: 15, y: 25 })
通过以上代码可以看出,在未改变Option变量a的情况下,通过as_mut方法,改变了其包含的数据的值。这个能力对于编写链表时,尤其是节点的插入、删除时,可以灵活的操作指向下一个节点的指针。
说明一点,调用as_mut方法时,Option变量本身或其引用,必须为可变的,即mut类型。
简单类型的一个例子:
fn main() {
let mut a = Some(5);
let b = a.as_mut().unwrap();
*b += 10;
println!("b = {:?}", b);
println!("a = {:?}", a);
}
输出:
b = 15
a = Some(15)