【问题标题】:Nested method calls with existing mutable references具有现有可变引用的嵌套方法调用
【发布时间】:2020-12-28 11:48:18
【问题描述】:

以下代码编译成功:

let mut v = vec![1];
let r = &mut v;
r.push(r.len());

虽然这个失败了:

let mut v = vec![1];
let r = &mut v;
r.push(v.len());

有错误:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
    |
    |     let r = &mut v;
    |             ------ mutable borrow occurs here
    |     r.push(v.len());
    |            ^ immutable borrow occurs here
    |     r.push(r.len());
    |     - mutable borrow later used here
  • 为什么第一个示例编译正确?是因为在外部和内部调用中使用了相同的引用:r?还是因为它应用了RFC 2025, Two-Phase Borrows?还是别的什么?
  • 鉴于第一个示例成功,为什么第二个示例失败?为什么RFC 2025, Two-Phase Borrows 不适用于此处?

我怀疑在第一个示例中没有错误,因为编译器没有创建中间引用并且它使用相同的引用:r,因此没有多次借用。 但是,如果是这样,为什么下面的代码编译失败

let mut v = vec![1];
let r = &mut v;
r.push({r.push(0);1});

【问题讨论】:

    标签: rust reference borrow-checker borrowing


    【解决方案1】:

    在第二个示例中,v 在您尝试获取其长度时仍然是可变借用的。 Vec::len 需要 &self,因此获得它的长度意味着在它已经可变地借用时不可变地借用。

    通过r 访问len() 仍然可以,因为您是在通过相同的借阅,而不是再次借阅。


    请注意,即使第一个示例在 Rust 1.30 及更早版本(或 2015 版的 1.35)中也失败了,因为它依赖于 NLL(非词法生命周期)。 NLL 的问题在于,知道什么是允许的并不完全直观。从本质上讲,这意味着不超过数据词汇范围的借用是可以的,还有其他几个直观正确的情况。您的第三个示例是 NLL 仍然不允许的情况之一。

    【讨论】:

    • 我在这里发布了同样的问题:users.rust-lang.org/t/…
    • 我想知道为什么 NLL 不允许第三个示例。编译器如何评估代码以考虑第一个示例可以,而第三个示例不行?
    • 这就是我的意思。可以推断出词法的生命周期,但 NLL 只是意味着“允许更多”,并且很难准确地描述极限。
    • 使用 NLL 的借用检查器的实际实现(无论是 2 阶段模型、堆叠借用还是粉笔)很难考虑,因为它是根据 MIR,而不是您实际编写的代码.有可能想出一个好的心智模型来预测什么 Rust 代码会或不会满足当前的借用检查器实现,但我没有。总的来说,根据经验,我对自己对词汇借用和一些非词汇案例的理解很有信心。除了词汇案例之外,我无法写下规则,而且我不相信很多人可以。
    • 而且我不相信这些努力是否值得:我还期望未来会接受更多的程序,无论是对当前实施的改进还是从最终切换到粉笔。
    【解决方案2】:

    第一个例子:

    let mut v = vec![1];
    let r = &mut v;
    r.push(r.len());
    

    如果没有 2 阶段借用,代码将无法编译,因为外部调用创建了 r 的重借:&mut *r,而内部调用创建了相同值的新不可变重借:&*r

    对于两阶段借用,第一个重借将转换为&mut2 *r,然后在第二个重借超出范围时激活。

    第二个例子:

    let mut v = vec![1];
    let r = &mut v;
    r.push(v.len());
    

    即使使用 2-phase 借用,它也无法编译。

    内部调用导致 v 的重借:&mut vr 冲突。

    第三个例子:

    let mut v = vec![1];
    let r = &mut v;
    r.push({r.push(0);0});
    

    即使使用 2-phase 借用,它也无法编译。

    内部调用需要&mut2 重新借用*r,这是两阶段借用所不允许的,因为外部调用已经创建了&mut2*r 重新借用。

    参考文献

    【讨论】:

      猜你喜欢
      • 2016-09-15
      • 1970-01-01
      • 2020-01-16
      • 1970-01-01
      • 1970-01-01
      • 2016-01-05
      • 1970-01-01
      • 2014-10-16
      • 2011-09-07
      相关资源
      最近更新 更多