【问题标题】:Double mutable borrow error in a loop happens even with NLL on即使打开 NLL,也会在循环中发生双重可变借用错误
【发布时间】:2018-11-04 06:14:18
【问题描述】:

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

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 的情况下工作):

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 标志的错误有这些注释),但我不明白这里到底出了什么问题。

【问题讨论】:

  • 您使用哪个rustc 版本? temp_event 是/包含引用吗?我要的是rustc 版本,因为match_default_bindings 最近稳定了,这可能会在此处作为参考结束,而您没想到它会成为参考。
  • @Tim rustc 版本是 2018-05-20 nightly 或最新稳定版。是的, temp_event 确实包含对缓冲区的引用。我认为它应该从方法签名中可见,但显然它不是:(我不确定该特定功能在这里是如何相关的,因为匹配没有捕获任何东西,它只是验证数据的结构。跨度>
  • 这里有一个设计流程,因为XmlEvent 拥有buffer 的所有权,当您选择返回temp_event 时,您将其生命周期绑定到函数的生命周期。我认为没有办法解决这个问题。您可以在不构造 XmlEvent 的情况下进行检查。

标签: rust borrow-checker


【解决方案1】:

这是non-lexical lifetimes 中的a limitation of the current implementation 这可以用这种简化的情况显示:

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

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

【讨论】:

  • 感谢 Niko 的检查!我想现在我必须坚持使用unsafe 来克服这个问题。我想我知道如何在不诉诸unsafe 的情况下克服它,但这需要以我不喜欢的方式更改内部方法类型:(
【解决方案2】:

我发布了一个问题 (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!");
}

【讨论】:

    猜你喜欢
    • 2022-01-11
    • 2019-08-12
    • 1970-01-01
    • 1970-01-01
    • 2021-03-20
    • 2018-03-05
    • 1970-01-01
    • 2018-10-11
    • 2017-06-18
    相关资源
    最近更新 更多