Rust学习笔记(9)——Option的几个方法及所有权问题

在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)

 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值