为什么self的可变借用不能变成不可变借用?

13

这段代码未能通过令人头疼的借用检查器 (playground):

struct Data {
    a: i32,
    b: i32,
    c: i32,
}

impl Data {
    fn reference_to_a(&mut self) -> &i32 {
        self.c = 1;
        &self.a
    }
    fn get_b(&self) -> i32 {
        self.b
    }
}

fn main() {
    let mut dat = Data{ a: 1, b: 2, c: 3 };
    let aref = dat.reference_to_a();
    println!("{}", dat.get_b());
}

自从非词汇生命周期被实现以来,需要触发错误:

fn main() {
    let mut dat = Data { a: 1, b: 2, c: 3 };
    let aref = dat.reference_to_a();
    let b = dat.get_b();
    println!("{:?}, {}", aref, b);
}

错误:

error[E0502]: cannot borrow `dat` as immutable because it is also borrowed as mutable
  --> <anon>:19:20
   |
18 |     let aref = dat.reference_to_a();
   |                --- mutable borrow occurs here
19 |     println!("{}", dat.get_b());
   |                    ^^^ immutable borrow occurs here
20 | }
   | - mutable borrow ends here

为什么会这样呢?我原以为,当reference_to_a()返回时,对dat的可变借用将转换为不可变借用,因为该函数只返回一个不可变引用。难道借用检查器还不够聪明吗?是计划中的吗?有没有解决方法?


7
「生命周期的限制」,这个链接中所述情况与此完全相同。 - aSpex
@Stargateur:我认为“脱糖”代码并不打算编译;它只是为了说明。 - Timmmm
@Stargateur:不,这并没有帮助。有必要限制loan的生命周期。https://play.rust-lang.org/?gist=fc564b89ff4fa44ae5463d7f407e88ca&version=stable&backtrace=0 - aSpex
@aSpex:啊,是的,看起来是一样的。但是文档没有提供解决方案或变通方法吗? - Timmmm
1
哦,确实,“根据我们实际关心的参考语义,这个程序显然是正确的,但生命周期系统过于粗粒度,无法处理它。”。因此,唯一的解决方案是使用独立作用域。@aSpex,你应该发布一个答案。 - Stargateur
我不太擅长英语。所以如果有人想发布答案,请随便发表。 - aSpex
2个回答

5
寿命与引用是否可变是分开的。通过代码进行工作:
fn reference_to_a(&mut self) -> &i32

尽管生命周期已被省略,但这与以下内容等效:
fn reference_to_a<'a>(&'a mut self) -> &'a i32

即输入和输出的生命周期相同。这是为这样的函数分配生命周期的唯一方法(除非它返回对全局数据的&'static引用),因为你不能从无中创造出输出生命周期。
这意味着如果你通过保存变量来使返回值保持活动状态,那么你也会使&mut self保持活动状态。
另一种思考方式是&i32是&mut self的子借用,因此只在其过期之前有效。
正如@aSpex所指出的,这在nomicon中有讲述

1
谢谢,那么有解决方案或变通方法吗?非词汇生命周期是否能解决这个问题? - Timmmm
3
除了将这个变异操作拆分成单独的方法调用之外,我认为没有其他的解决方法。这不是一个词法生存期的问题;&i32 是从 &mut self 借用的,因此它们基本上是相互关联的。 - Chris Emerson
2
@Timmmm,启用NLL后,OP示例可以原样工作。 - mcarton
2
“example works as-is with NLL enabled” 只是因为 aref 没有被使用。如果你使用它,例如 println!("{} {}", aref, dat.get_b()); 那么 NLL 就没有任何帮助了,正如 @ChrisEmerson 所解释的那样。 - Nickolay
有没有什么理由阻止 Rust 扩展以允许这样做,或者甚至为什么允许这样做会很困难。例如,为什么 Rust 不能将 self 的可变借用降级为返回的 self 的不可变借用。这样,它就可以工作了(因为您可以拥有相同事物的多个不可变借用)。 - PersonWithName

2
为什么会出现错误:虽然2.5年前@Chris已经给出了更精确的解释,但您可以将fn reference_to_a(&mut self) -> &i32解读为以下声明:

“我想要独占地借用self,然后返回一个与原始独占借用一样长寿的共享/不可变引用” (source)

显然,这甚至可以防止我自己搞砸。
借用检查器还没有足够聪明吗?这是计划中的吗?
仍然没有一种方式可以表达“我想在通话期间独占使用自身,并返回一个具有单独生命周期的共享引用”。正如@aSpex指出的那样,这在nomicon中提到,并且从2018年末开始被列为 Rust 不允许你做的事情之一。
我找不到具体的计划来解决这个问题,因为之前其他借用检查器的改进被视为更高优先级。关于允许独立的读/写“生命周期角色”(Ref2<'r, 'w>)的想法在NLL RFC中提到过,但据我所知,没有人将其变成自己的RFC。 有没有绕过这个问题的方法?实际上没有,但根据你最初需要它的原因,可能适合使用其他方式来构建代码:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接