即使使用NLL,循环中也会发生双重可变借用错误

Vla*_*eev 9 rust borrow-checker

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

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!()
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,即使#![feature(nll)]启用了以下代码,此代码也会出现双重借用错误:

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
Run Code Online (Sandbox Code Playgroud)

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

无论如何,我的最终目标是在没有标志的情况下实现它,所以我也尝试这样做(它是递归的,这真的很不幸,但是我提出的所有非递归版本都不可能在没有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)
}
Run Code Online (Sandbox Code Playgroud)

在这里,我试图将借用限制在一个词块中,并且这个块中没有任何东西泄漏到外面.但是,我仍然收到一个错误:

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
Run Code Online (Sandbox Code Playgroud)

而且,NLL并没有解决它.

很长一段时间以来我遇到了一个我不理解的借用检查错误,所以我希望它实际上是一些简单的东西,我忽略了一些原因:)

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

She*_*ter 7

是非词汇生命周期的当前实现限制这可以通过这种减少的情况来显示:

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() {}
Run Code Online (Sandbox Code Playgroud)

此限制可防止NLL情况#3:跨功能的条件控制流

在编译器开发人员术语中,非词汇生命周期的当前实现是"位置不敏感".位置灵敏度最初可用,但已在性能名称中禁用.

我向Niko Matsakis询问了这段代码:

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

一个微妙的事情是,当前的分析在一个方面是位置敏感的 - 借款发生在那里.借款的长度不是.

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

这可能是也可能不是[Rust 2018]版本之前.

(注:它并没有使它成为锈病2018的初始版本)

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

rustc +nightly -Zpolonius --edition=2018 example.rs
Run Code Online (Sandbox Code Playgroud)
RUSTFLAGS="-Zpolonius" cargo +nightly build
Run Code Online (Sandbox Code Playgroud)

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


Gol*_*nks 5

我发布了一个问题(带有循环和非词法生命周期的借用检查器问题),该问题的答案已得到解答。

我将在这里记录一个也可以回答这个问题的解决方法。假设您有这样的代码,只能使用 Polonius 进行编译:

struct Inner;

enum State<'a> {
    One,
    Two(&'a ()),
}

fn get<'s>(_inner: &'s mut Inner) -> State<'s> {
    unimplemented!()
}

struct Outer {
    inner: Inner,
}

impl Outer {
    pub fn read<'s>(&'s mut self) -> &'s () {
        loop {
            match get(&mut self.inner) {
                State::One => (), // In this case nothing happens, the borrow should end and the loop should continue
                State::Two(a) => return a, // self.inner ought to be borrowed for 's, that's just to be expected
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如另一个答案中所说:

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

确实,在条件分支再次借用所需的引用使其编译!当然,这使得假设get是参考透明的,因此您的里程可能会有所不同,但再次借用似乎是一个足够简单的解决方法。

struct Inner;

enum State<'a> {
    One,
    Two(&'a ()),
}

fn get<'s>(_inner: &'s mut Inner) -> State<'s> {
    unimplemented!()
}

struct Outer {
    inner: Inner,
}

impl Outer {
    pub fn read<'s>(&'s mut self) -> &'s () {
        loop {
            match get(&mut self.inner) {
                State::One => (), // In this case nothing happens, the borrow should end and the loop should continue
                State::Two(a) => {
                    return match get(&mut self.inner) { // Borrowing again compiles!
                        State::Two(a) => a,
                        _ => unreachable!(),
                    }
                }, // self.inner ought to be borrowed for 's, that's just to be expected
            }
        }
    }
}

fn main() {
    println!("Hello, world!");
}
Run Code Online (Sandbox Code Playgroud)