bugfix> rust > 投稿

次の例のようないくつかの構造があり、 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 に関係していると疑っていますライフタイム(特に、NLLフラグがオンになっているエラーには、これらのメモがあります)が、ここで正確に何が間違っているのか理解できません。

回答 1 件
  • これは、現在の実装の制限です非字句寿命 これは、この縮小されたケースで表示できます。

    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ケース#3が防止されます。機能間の条件付き制御フロー

    コンパイラ開発者の用語では、現在の非字句の有効期間の実装は「場所に依存しない」です。ロケーションの感度はもともと利用可能でしたが、パフォーマンスの名前で無効にされました。

    Niko Matsakisにこのコードについて質問しました。

    In the context of your example: the value event  ライフタイム 'buf があればよい  条件付き—実行される場合と実行されない場合がある戻り点。しかし、「場所に依存しない」場合、 event のライフタイムを追跡するだけです。  その寿命がどこにあるべきかを考慮せずに、どこでも持っていなければなりません。この場合、それはすべての場所でそれを保持することを意味します。それがコンパイルの失敗を得る理由です。

         

    微妙な点の1つは、現在の分析が1つの点で場所に依存していることです-借用が行われる場所です。ボローの長さはそうではありません。

    幸いなことに、この場所の感度の概念を元に戻すことは、非字句的ライフタイムの実装の強化と見なされます。悪いニュース:

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

    (注:しましたじゃない Rust 2018の最初のリリースに含める)

    これは、パフォーマンスを向上させる非字句的ライフタイムの(さらに新しい!)基礎となる実装に依存します。 -Z polonius を使用して、この半分実装されたバージョンにオプトインできます。 :

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

    これは機能全体、関数をインライン化することでこれを回避できる場合があります。

あなたの答え