即使在

时间:2018-05-24 22:38:19

标签: rust borrow-checker

假设我有几个结构,如下例所示,在next()方法中,我需要使用用户提供的缓冲区来提取下一个事件,但如果此事件是注释,则忽略注释标记为设置为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!()
    }
}

但是,即使我启用了#![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

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

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

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)
}

在这里,我尝试将借用限制在词法块中,并且来自此块的 nothing 泄漏到外部。但是,我仍然收到错误:

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没有修复它。

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

我真的怀疑根本原因与显式'buf生命周期有某种联系(特别是,启用了NLL标志的错误有关于它的这些注释),但我无法理解究竟是什么错误这里。

2 个答案:

答案 0 :(得分:7)

这是a limitation of the current implementationnon-lexical lifetimes这可以通过以下简化案例来显示:

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跨功能的条件控制流

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

I asked Niko Matsakis about this code

  

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

     

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

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

  

可能会或可能不会出现在[Rust 2018]版本之前。

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

这取决于非词汇生命周期的(甚至是更新的!)底层实现,可以提高性能。您可以使用-Z polonius

选择加入此半实施版本
rustc +nightly -Zpolonius --edition=2018 example.rs
RUSTFLAGS="-Zpolonius" cargo +nightly build

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

答案 1 :(得分:3)

我发布了一个问题(A borrow checker problem with a loop and non-lexical lifetimes),该问题的答案已回答该问题。

我将在此处记录一个替代方法,该替代方法也可以回答该问题。假设您有这样的代码,只能用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
            }
        }
    }
}

如另一个答案中所述:

  

一件微妙的事情是,当前的分析在某一方面对位置敏感,即发生借贷的位置。借用的期限不是。

实际上,再次在条件分支内借用了所需的引用 使其可以编译!当然,这会假设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!");
}