如何在有条件地修改外部选项时 return 引用选项内部的值

How to return reference to value inside option while conditionaly modifying outer option

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


#[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 块中,如果我 return Err 而不是 Ok,它编译得很好。为什么会发生这种情况,我该如何解决这个问题?

首先,如果我们将 *v = None 替换为 let _ = 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 guards:

    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_)
    }

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

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

行中使用它

一旦内部借用结束,外部借用 v 是唯一剩下的参考,可以触摸匹配臂中的外部值。

该匹配项可变地借用了 v(对于 v_),因此您不能再使用它了。您需要改用 v_ - 至少在它被删除之前。

为了便于阅读,我还将名称更改为 v_optv

    match v_opt {
        None => Err("No Token".into()),
        Some(v) if v.exp > 10 => {
            drop(v);
            *v_opt = None;
            Err("Token Expired".into())
        }
        Some(v_) => Ok(v_)
    }