在None选项中不可变借用



示例代码:

struct Foo;
impl Foo {
fn foo(&mut self) -> Result<&i32, String> {
match self.bar() {
Some(d) => Ok(d),
None => {
if self.check() {
Err(String::from("Error type 1"))
} else {
Err(String::from("Error type 2"))
}
}
}
}

fn bar(&mut self) -> Option<&i32> {
todo!()
}

fn check(&self) -> bool {
todo!()
}
}

Rust playground Code


  • Foo::foo函数中:
    • self.bar()需要可变借用
    • self.check()需要不可变借用

以上代码编译失败。生命周期检查器抱怨两个借用(可变的和不可变的)。

error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
--> src/lib.rs:8:20
|
4 |     fn foo(&mut self) -> Result<&i32, String> {
|            - let's call the lifetime of this reference `'1`
5 |         match self.bar() {
|               ---------- mutable borrow occurs here
6 |             Some(d) => Ok(d),
|                        ----- returning this value requires that `*self` is borrowed for `'1`
7 |             None => {
8 |                 if self.check() {
|                    ^^^^^^^^^^^^ immutable borrow occurs here

问题

问题1

为什么编译器阻止我编译这段代码?

显然我在这里遗漏了一些东西,但是…

不可变借用(即self.check())只发生在None分支中。之前的可变借用(即self.bar())应该不会给那里带来任何影响,对吗?

同样的代码可以写成:

if let Some(d) = self.bar() {
return Ok(d);
}
// What lifetime of `self.bar` is needed at this point?
if self.check() {
// ...
} else {
// ...
}

问题2

如何解决这个问题?

请注意:

  • 我不想在match之前移动检查(即self.check())。仅仅因为性能:self.check可能是昂贵的,并且处于"冷"状态。路径。如果self.bar()返回Some,我想尽快从函数返回。
  • 我不想引入运行时开销。(例如,动态检查借款->RefCell)。当然,这是一个假的例子。只是为了演示。所以要注意这一点。例如:
    • &'a i32表示返回"data"这需要一生。在我的实际代码中,您可以想象我有一个复杂的对象,它包含一些引用(因此需要生命周期)。例如:struct ComplexObject<'a>.

答案1

问题在于,在Some(d) => Ok(d)中,您仍然持有与self.bar()调用的&mut self具有相同生存期的引用&i32。生命周期省略隐藏了这一点,但是代码将被糖化成这样:

fn bar<'a>(&'a mut self) -> Option<&'a i32> {
todo!()
}

self.bar()结果的生存期为'a,Some(d)中的d也是如此。由于您将从函数返回d,因此self.bar()需要保持可变借用状态,直到函数结束。这使得self.check()不可能被借用。

为了更好地可视化问题:

use std::rc::Rc;
struct Foo;
impl Foo {
fn foo<'b>(&'b mut self) -> Result<&'b i32, String> {
match self.bar() {        // --+- "&'a mut self" borrowed
Some(d) => Ok(d),     // --+- "'a" becomes "'b"
None => {             //   |
if self.check() { // --+- "&'c self" borrow failed attempt
Err(String::from("Error type 1"))
} else {          //   |
Err(String::from("Error type 2"))
}                 //   |
}                     //   |
}                         //   |
}                             // --+- "&'a mut self" valid until here

fn bar<'a>(&'a mut self) -> Option<&'a i32> {
todo!()
}

fn check<'c>(&'c self) -> bool {
todo!()
}
}

self.foo的返回值为&'b i32,其生存期需要与bar的返回值相同,因此'a的生存期至少需要与'b一样长,因此self.bar中借用的&'a mut self一直借用到self.foo结束。

回答2

我会考虑以下三个选项:

  1. 如果移动是一个选项,将bar的返回值更改为Option<i32>:
struct Foo;
impl Foo {
fn foo(&mut self) -> Result<i32, String> {
match self.bar() {
Some(d) => Ok(d),
None => {
if self.check() {
Err(String::from("Error type 1"))
} else {
Err(String::from("Error type 2"))
}
}
}
}

fn bar(&mut self) -> Option<i32> {
todo!()
}

fn check(&self) -> bool {
todo!()
}
}
  1. self.bar()拆分为两个独立的函数,一个执行可变操作并将所有权从函数移动到被调用方,另一个返回Option<&i32>但不可变地借用:
struct Foo;
impl Foo {
fn foo(&mut self) -> Result<&i32, String> {
let bar = self.bar_mut();
match self.bar(bar) {
Some(d) => Ok(d),
None => {
if self.check() {
Err(String::from("Error type 1"))
} else {
Err(String::from("Error type 2"))
}
}
}
}

fn bar_mut(&mut self) -> Option<i32> {
todo!()
}

fn bar(&self, bar: Option<i32>) -> Option<&i32> {
todo!()
}

fn check(&self) -> bool {
todo!()
}
}
  1. 如果你根本无法移动对象,使用Rc作为多重所有权:
use std::rc::Rc;
struct Foo;
impl Foo {
fn foo(&mut self) -> Result<Rc<i32>, String> {
match self.bar() {
Some(d) => Ok(d),
None => {
if self.check() {
Err(String::from("Error type 1"))
} else {
Err(String::from("Error type 2"))
}
}
}
}

fn bar(&mut self) -> Option<Rc<i32>> {
// Rc::clone the value here
todo!()
}

fn check(&self) -> bool {
todo!()
}
}

最新更新