代码之家  ›  专栏  ›  技术社区  ›  Vladimir Matveev

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

  •  9
  • Vladimir Matveev  · 技术社区  · 6 年前

    假设我有几个结构,如下面的示例和 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的情况下都不可能工作):

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

    在这里,我试图把借词限制在一个词汇块内,并且 没有什么 从这一块漏到外面。但是,我仍然得到一个错误:

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

    1 回复  |  直到 6 年前
        1
  •  7
  •   Shepmaster Lukas Kalbertodt    5 年前

    这是 a limitation of the current implementation 属于 non-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 在可能执行或可能不执行的返回点有条件地“_”。但当我们“位置不敏感”时,我们只会跟踪 事件 必须拥有任何地方,而不考虑生命的意义。在这种情况下,这意味着我们使它在任何地方都能保存,这就是为什么编译失败的原因。

    一个微妙的问题是,当前的分析在某一方面对借款地点敏感。借土的长度不是。

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

    这可能在[Rust 2018]版本之前。

    (注:的确如此 使之成为2018年首次发布的Rust)

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

    rustc +nightly -Zpolonius --edition=2018 example.rs
    
    RUSTFLAGS="-Zpolonius" cargo +nightly build
    

    因为这是 跨职能 ,您有时可以通过内嵌函数来解决这个问题。