首页 > 解决方案 > 如何在有条件地修改外部选项时返回对选项内部值的引用

问题描述

我在选项中有令牌。在每次操作之前,我需要检查令牌是否有效。它可能已过期。如果它已过期,我想将外部选项设置为 None 并返回错误,所以下次我只会得到“无令牌”错误。


#[derive(Debug)]
struct Token {
    exp : u64
    //other cool stuff
}

fn is_valid<'a>(v : &'a mut Option<Token> ) -> Result<&'a mut Token, String> {
    match v {
        None => Err("No Token".into()),
        Some(v_) => {
            if v_.exp > 10 {
                *v = None;
                Err("Token Expired".into())
            }
            else {
                Ok(v_)
                // Err("erlgnerlg".into()) //commenting this out and comenting upper line also work.
            }
        }
    }
}

fn main() {
    let mut v = Some(Token { exp : 69 });

    //expecting "Token Expired" error
    println!("{:?} ", is_valid(&mut v)); 

    //expecting "No Token" error
    println!("{:?} ", is_valid(&mut v)); 
}

但它无法编译。

error[E0506]: cannot assign to `*v` because it is borrowed
  --> src/main.rs:13:17
   |
8  | fn is_valid<'a>(v : &'a mut Option<Token> ) -> Result<&'a mut Token, String> {
   |             -- lifetime `'a` defined here
...
11 |         Some(v_) => {
   |              -- borrow of `*v` occurs here
12 |             if v_.exp > 10 {
13 |                 *v = None;
   |                 ^^^^^^^^^ assignment to borrowed `*v` occurs here
...
17 |                 Ok(v_)
   |                 ------ returning this value requires that `v.0` is borrowed for `'a

如果我不将 None 分配给外部选项,我的代码将编译。同样在 else 块中,如果我返回 Err 而不是 Ok,它编译得很好。为什么会发生这种情况,我该如何解决这个问题?

标签: rustborrow-checker

解决方案


首先,如果我们替换*v = Nonelet _ = v.take()(它做同样的事情),我们会得到另一个编译器错误。这产生了经典的“第二个可变借用发生在这里”:

10 |         Some(v_) => {
   |              -- first mutable borrow occurs here
11 |             if v_.exp > 10 {
12 |                 let _ = v.take();
   |                         ^ second mutable borrow occurs here
...
16 |                 Ok(v_)
   |                 ------ returning this value requires that `v.0` is borrowed for `'a`

这是有道理的:首先,我们解构了借来Option的东西来看看里面,然后我们想要改变它,同时仍然参考Option周围的内部。

但是,我们可以使用匹配守卫match通过一条语句捕获所有条件逻辑:

    match v {
        None => Err("No Token".into()),
        Some(v_) if v_.exp > 10 => {
            dbg!(v_.exp); // use v_ in _some_ way
            let _ = v.take(); // same as *v = None
            Err("Token Expired".into())
        }
        Some(v_) => Ok(v_)
    }

我不完全确定,为什么编译器会卡在您的版本上,但这是我认为发生的事情:您的匹配臂以某种方式Token返回对内部值的引用,因此编译器推断该引用必须保持活动状态手臂的整个范围。

使用匹配守卫,它可以推断出我们再也不会触及包含的值并缩短内部借用的生命周期(我们在匹配时隐式执行的那个Some(v_) if ...)。请注意,当我们删除先前包含的值时,此借用的生命周期恰好结束,因为我们仍然可以在行中使用它dbg!(v_.exp)

一旦这个内部借用结束,外部借用v是唯一剩下的引用,并且可以触及匹配臂中的外部值。


推荐阅读