示例代码:
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()
需要不可变借用
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
,我想尽快从函数返回。 我不想引入运行时开销。(例如,动态检查借款-> &'a i32
表示返回"data"这需要一生。在我的实际代码中,您可以想象我有一个复杂的对象,它包含一些引用(因此需要生命周期)。例如:struct ComplexObject<'a>
.
RefCell
)。当然,这是一个假的例子。只是为了演示。所以要注意这一点。例如:答案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
我会考虑以下三个选项:
- 如果移动是一个选项,将
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!()
}
}
- 将
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!()
}
}
- 如果你根本无法移动对象,使用
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!()
}
}