即使 NLL 打开,循环中的双重可变借用错误也会发生 [英] Double mutable borrow error in a loop happens even with NLL on

查看:41
本文介绍了即使 NLL 打开,循环中的双重可变借用错误也会发生的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有几个结构,如下例所示,在 next() 方法中,我需要使用用户提供的缓冲区拉取下一个事件,但如果此事件是注释,并且忽略评论标志设置为true,我需要再次拉下一个事件:

Suppose I have several structures like in the following example, and in the next() method I need to pull the next event using a user-provided buffer, but if this event is a comment, and ignore comments flag is set to true, I need to pull the next event again:

struct Parser {
    ignore_comments: bool,
}

enum XmlEvent<'buf> {
    Comment(&'buf str),
    Other(&'buf str),
}

impl Parser {
    fn next<'buf>(&mut self, buffer: &'buf mut String) -> XmlEvent<'buf> {
        let result = loop {
            buffer.clear();

            let temp_event = self.parse_outside_tag(buffer);

            match temp_event {
                XmlEvent::Comment(_) if self.ignore_comments => {}
                _ => break temp_event,
            }
        };
        result
    }

    fn parse_outside_tag<'buf>(&mut self, _buffer: &'buf mut String) -> XmlEvent<'buf> {
        unimplemented!()
    }
}

然而,即使我启用了 #![feature(nll​​)],这段代码也会出现双重借用错误:

This code, however, gives a double borrow error, even when I have #![feature(nll)] enabled:

error[E0499]: cannot borrow `*buffer` as mutable more than once at a time
  --> src/main.rs:14:13
   |
14 |             buffer.clear();
   |             ^^^^^^ second mutable borrow occurs here
15 |             
16 |             let temp_event = self.parse_outside_tag(buffer);
   |                                                     ------ first mutable borrow occurs here
   |
note: borrowed value must be valid for the lifetime 'buf as defined on the method body at 12:5...
  --> src/main.rs:12:5
   |
12 |     fn next<'buf>(&mut self, buffer: &'buf mut String) -> XmlEvent<'buf> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0499]: cannot borrow `*buffer` as mutable more than once at a time
  --> src/main.rs:16:53
   |
16 |             let temp_event = self.parse_outside_tag(buffer);
   |                                                     ^^^^^^ mutable borrow starts here in previous iteration of loop
   |
note: borrowed value must be valid for the lifetime 'buf as defined on the method body at 12:5...
  --> src/main.rs:12:5
   |
12 |     fn next<'buf>(&mut self, buffer: &'buf mut String) -> XmlEvent<'buf> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

我可以(至少大约)理解为什么在关闭 NLL 功能的情况下会发生错误,但我不明白为什么 NLL 会发生错误.

I can (approximately at least) understand why an error could happen here with the NLL feature turned off, but I don't understand why it happens with NLL.

无论如何,我的最终目标是在没有标志的情况下实现这一点,所以我也尝试这样做(它是递归的,这真的很不幸,但我想出的所有非递归版本都无法在没有 NLL 的情况下工作):

Anyway, my end goal is to implement this without flags, so I also tried doing this (it is recursive, which is really unfortunate, but all non-recursive versions I came up with could not possibly work without NLL):

fn next<'buf>(&mut self, buffer: &'buf mut String) -> XmlEvent<'buf> {
    buffer.clear();

    {
        let temp_event = self.parse_outside_tag(buffer);

        match temp_event {
            XmlEvent::Comment(_) if self.ignore_comments => {}
            _ => return temp_event,
        }
    }

    self.next(buffer)
}

在这里,我试图将借用限制在一个词法块内,并且该块中没有泄漏到外部.但是,我仍然收到错误消息:

Here I tried to confine the borrow inside a lexical block, and nothing from this block leaks to the outside. However, I'm still getting an error:

error[E0499]: cannot borrow `*buffer` as mutable more than once at a time
  --> src/main.rs:23:19
   |
15 |             let temp_event = self.parse_outside_tag(buffer);
   |                                                     ------ first mutable borrow occurs here
...
23 |         self.next(buffer)
   |                   ^^^^^^ second mutable borrow occurs here
24 |     }
   |     - first borrow ends here

error: aborting due to previous error

再说一次,NLL 没有修复它.

And again, NLL does not fix it.

我已经很久没有遇到我不明白的借用检查错误了,所以我希望它实际上是一些简单的事情,我出于某种原因忽略了它:)

It has been a long time since I encountered a borrow checking error which I don't understand, so I'm hoping it is actually something simple which I'm overlooking for some reason :)

我真的怀疑根本原因与显式的 'buf 生命周期有关(特别是打开 NLL 标志的错误有这些注释),但我无法理解这里到底有什么问题.

I really suspect that the root cause is somehow connected with the explicit 'buf lifetime (in particular, errors with the NLL flag turned on have these notes about it), but I can't understand what exactly is wrong here.

推荐答案

这是a非词法生存期的当前实现的限制 这可以显示有了这个简化的案例:

This is a limitation of the current implementation of non-lexical lifetimes This can be shown with this reduced case:

fn next<'buf>(buffer: &'buf mut String) -> &'buf str {
    loop {
        let event = parse(buffer);

        if true {
            return event;
        }
    }
}

fn parse<'buf>(_buffer: &'buf mut String) -> &'buf str {
    unimplemented!()
}

fn main() {}

<小时>

这个限制阻止了NLL case #3:跨函数的条件控制流

在编译器开发人员的术语中,非词法生命周期的当前实现是位置不敏感".位置敏感原本是可用的,但以性能为名被禁用.

In compiler developer terms, the current implementation of non-lexical lifetimes is "location insensitive". Location sensitivity was originally available but it was disabled in the name of performance.

我问过Niko Matsakis 关于这段代码:

在您的示例的上下文中:值 event 只需有条件地具有生命周期 'buf - 在可能执行或可能不执行的返回点.但是当我们位置不敏感"时,我们只是跟踪 event 在任何地方必须具有的生命周期,而不考虑该生命周期必须保持在哪里.在这种情况下,这意味着我们让它无处不在,这就是编译失败的原因.

In the context of your example: the value event only has to have the lifetime 'buf conditionally — at the return point which may or may not execute. But when we are "location insensitive", we just track the lifetime that event must have anywhere, without considering where that lifetime must hold. In this case, that means we make it hold everywhere, which is why you get a compilation failure.

一件微妙的事情是,当前的分析在某一方面对位置敏感——借用发生的位置.借用的长度不是.

One subtle thing is that the current analysis is location sensitive in one respect — where the borrow takes place. The length of the borrow is not.

好消息是,重新添加这种位置敏感概念被视为对非词法生命周期实现的增强.坏消息:

The good news is that adding this concept of location sensitivity back is seen as an enhancement to the implementation of non-lexical lifetimes. The bad news:

这可能在 [Rust 2018] 版之前,也可能不在.

That may or may not be before the [Rust 2018] edition.

(注意:它确实没有进入 Rust 2018 的初始版本)

(Note: it did not make it into the initial release of Rust 2018)

这取决于(甚至更新!)非词法生命周期的底层实现,它提高了性能.您可以使用 -Z polonius 选择加入这个半实现的版本:

This hinges on a (even newer!) underlying implementation of non-lexical lifetimes that improves the performance. You can opt-in to this half-implemented version using -Z polonius:

rustc +nightly -Zpolonius --edition=2018 example.rs

RUSTFLAGS="-Zpolonius" cargo +nightly build

<小时>

因为这是跨函数,您有时可以通过内联函数来解决此问题.


Because this is across functions, you can sometimes work around this by inlining the function.

这篇关于即使 NLL 打开,循环中的双重可变借用错误也会发生的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆