2017-07-25 7 views
3

私は同時に2つのイテレータを処理する関数を実装しようとしています。このコールバックは、(bool, bool)タプルを返すことで、各ステップでどのイテレータを進めるかを制御できます。イテレータは私の使用例でバッファを参照するので、ファンクション(Iterator::nextと同じですが、追加の有効期間パラメータが必要です)を使用してstdlibからIteratorの特性を実装することはできません。コールバックで `zip`のような関数で借用チェッカーの問題

// An iterator-like type, that returns references to itself 
// in next_ref 
struct RefIter { 
    value: u64 
} 

impl RefIter { 
    fn next_ref<'a>(&'a mut self) -> Option<&'a u64> { 
     self.value += 1; 
     Some(&self.value) 
    } 
} 

// Iterate over two RefIter simultaneously and call a callback 
// for each pair. The callback returns a tuple of bools 
// that indicate which iterators should be advanced. 
fn each_zipped<F>(mut iter1: RefIter, mut iter2: RefIter, callback: F) 
    where F: Fn(&Option<&u64>, &Option<&u64>) -> (bool, bool) 
{ 
    let mut current1 = iter1.next_ref(); 
    let mut current2 = iter2.next_ref(); 
    loop { 
     let advance_flags = callback(&current1, &current2); 
     match advance_flags { 
      (true, true) => { 
       current1 = iter1.next_ref(); 
       current2 = iter2.next_ref(); 
      }, 
      (true, false) => { 
       current1 = iter1.next_ref(); 
      }, 
      (false, true) => { 
       current2 = iter1.next_ref(); 
      }, 
      (false, false) => { 
       return 
      } 
     } 
    } 
} 

fn main() { 
    let mut iter1 = RefIter { value: 3 }; 
    let mut iter2 = RefIter { value: 4 }; 
    each_zipped(iter1, iter2, |val1, val2| { 
     let val1 = *val1.unwrap(); 
     let val2 = *val2.unwrap(); 
     println!("{}, {}", val1, val2); 
     (val1 < 10, val2 < 10) 
    }); 
} 
error[E0499]: cannot borrow `iter1` as mutable more than once at a time 
    --> src/main.rs:28:28 
    | 
22 |  let mut current1 = iter1.next_ref(); 
    |      ----- first mutable borrow occurs here 
... 
28 |     current1 = iter1.next_ref(); 
    |       ^^^^^ second mutable borrow occurs here 
... 
42 | } 
    | - first borrow ends here 

error[E0499]: cannot borrow `iter2` as mutable more than once at a time 
    --> src/main.rs:29:28 
    | 
23 |  let mut current2 = iter2.next_ref(); 
    |      ----- first mutable borrow occurs here 
... 
29 |     current2 = iter2.next_ref(); 
    |       ^^^^^ second mutable borrow occurs here 
... 
42 | } 
    | - first borrow ends here 

error[E0499]: cannot borrow `iter1` as mutable more than once at a time 
    --> src/main.rs:32:28 
    | 
22 |  let mut current1 = iter1.next_ref(); 
    |      ----- first mutable borrow occurs here 
... 
32 |     current1 = iter1.next_ref(); 
    |       ^^^^^ second mutable borrow occurs here 
... 
42 | } 
    | - first borrow ends here 

error[E0499]: cannot borrow `iter1` as mutable more than once at a time 
    --> src/main.rs:35:28 
    | 
22 |  let mut current1 = iter1.next_ref(); 
    |      ----- first mutable borrow occurs here 
... 
35 |     current2 = iter1.next_ref(); 
    |       ^^^^^ second mutable borrow occurs here 
... 
42 | } 
    | - first borrow ends here 

私はそれが不平を言う理由を理解、それを回避する方法を見つけることができません。私はその件に関する助けに感謝します。

playgroundのこのスニペットにリンクします。

答えて

5

イテレータは私のユースケースにおけるバッファへの参照を取るので、STDLIBからIterator形質を実装することができないが、その代わりにIterator::nextと同一であるnext_ref関数も使用されるが、とり追加の生涯パラメータ。

ストリーミングイテレータについて説明しています。このための箱は、streaming_iteratorと呼ばれています。ドキュメントはあなたの問題(強調鉱山)説明:advanceget:標準Iterator特性の機能を next方法のオフに基づいていますが

を、StreamingIteratorの機能は、 メソッドのペアをベースにしています。これは本質的にnextの論理を半分に分割している(実際には、StreamingIteratornextの方法 は、advanceの後にgetと呼ぶ以外は何もしません)。

これは、Rustの語彙的な借用処理のために必要です(詳細は ).1つのエントリがない、複数の出口借用) StreamingIteratorが、必要な nextメソッドを使用してIteratorのように定義された場合、filterのような操作は定義できません。

クレートには現在、zip機能がありません。また、あなたが記述した変種ではありません。しかし、実装するのは簡単です。

extern crate streaming_iterator; 

use streaming_iterator::StreamingIterator; 

fn each_zipped<A, B, F>(mut iter1: A, mut iter2: B, callback: F) 
where 
    A: StreamingIterator, 
    B: StreamingIterator, 
    F: for<'a> Fn(Option<&'a A::Item>, Option<&'a B::Item>) -> (bool, bool), 
{ 
    iter1.advance(); 
    iter2.advance(); 

    loop { 
     let advance_flags = callback(iter1.get(), iter2.get()); 
     match advance_flags { 
      (true, true) => { 
       iter1.advance(); 
       iter2.advance(); 
      } 
      (true, false) => { 
       iter1.advance(); 
      } 
      (false, true) => { 
       iter1.advance(); 
      } 
      (false, false) => return, 
     } 
    } 
} 

struct RefIter { 
    value: u64 
} 

impl StreamingIterator for RefIter { 
    type Item = u64; 

    fn advance(&mut self) { 
     self.value += 1; 
    } 

    fn get(&self) -> Option<&Self::Item> { 
     Some(&self.value) 
    } 
} 

fn main() { 
    let iter1 = RefIter { value: 3 }; 
    let iter2 = RefIter { value: 4 }; 
    each_zipped(iter1, iter2, |val1, val2| { 
     let val1 = *val1.unwrap(); 
     let val2 = *val2.unwrap(); 
     println!("{}, {}", val1, val2); 
     (val1 < 10, val2 < 10) 
    }); 
} 
+0

最後の 'iter1.advance();'は 'iter2.advance();'ですか? – Douglas

+0

@Douglasそれは確かにそれのように見えるが、私はOPのロジックにちょうど従っていた。おそらく彼らは彼らの狂気への方法を持っていたでしょう。 – Shepmaster

+2

このような狂気には方法はありません。実際のコードから削除するときにコピー&ペーストエラーが発生します。その特定のバージョンの 'zip'の理由は、fastqレコード(DNAスニペットに関する情報)が2つの異なるファイルに分割されることが多いからです。しかし時にはそれらのファイルには、通常のジップが同期しなくなる不一致なレコードがいくつか含まれています。ところで、イテレータを分割することは素晴らしいアイデアです。ストリーミング・イテレーターというキーワードがなくなってしまったと思います。ありがとう。 – aseyboldt

1

このコードの問題は、RefIterが互いに対立し、基本的に二つの方法で使用されていることである。next_ref

  • 発信者に接続されて格納された値への参照を受け取りますそれは呼び出しごとに増分することができるようにRefIter
  • RefIterの値の有効期間は、変更可能であることが必要

これは完全に可変エイリアシングが(あなたが変更しようとしている説明それに対する参照が保持されている間は '値') - Rustが防ぐために明示的に設計したもの。

each_zippedを機能させるには、変更するデータへの参照を渡さないようにRefIterを変更する必要があります。 私はRefCellRcの組み合わせを使用して、以下の一つの可能​​性を実装しました:

use std::cell::RefCell; 
use std::rc::Rc; 

// An iterator-like type, that returns references to itself 
// in next_ref 
struct RefIter { 
    value: RefCell<Rc<u64>> 
} 


impl RefIter { 
    fn next_ref(&self) -> Option<Rc<u64>> { 
     let new_val = Rc::new(**self.value.borrow() + 1); 
     *self.value.borrow_mut() = new_val; 
     Some(Rc::clone(&*self.value.borrow())) 
    } 
} 


// Iterate over two RefIter simultaniously and call a callback 
// for each pair. The callback returns a tuple of bools 
// that indicate which iterators should be advanced. 
fn each_zipped<F>(iter1: RefIter, iter2: RefIter, callback: F) 
    where F: Fn(&Option<Rc<u64>>, &Option<Rc<u64>>) -> (bool, bool) 
{ 
    let mut current1 = iter1.next_ref(); 
    let mut current2 = iter2.next_ref(); 
    loop { 
     let advance_flags = callback(&current1, &current2); 
     match advance_flags { 
      (true, true) => { 
       current1 = iter1.next_ref(); 
       current2 = iter2.next_ref(); 
      }, 
      (true, false) => { 
       current1 = iter1.next_ref(); 
      }, 
      (false, true) => { 
       current2 = iter1.next_ref(); 
      }, 
      (false, false) => { 
       return 
      } 
     } 
    } 
} 

fn main() { 
    let iter1 = RefIter { value: RefCell::new(Rc::new(3)) }; 
    let iter2 = RefIter { value: RefCell::new(Rc::new(4)) }; 
    each_zipped(iter1, iter2, |val1, val2| { 
     // We can't use unwrap() directly, since we're only passed a reference to an Option 
     let val1 = **val1.iter().next().unwrap(); 
     let val2 = **val2.iter().next().unwrap(); 
     println!("{}, {}", val1, val2); 
     (val1 < 10, val2 < 10) 
    }); 
} 

RefIter手アウト消費者へRc秒、代わりの参照のこのバージョンを。これにより、可変エイリアシングの問題を回避します。valueの更新は、 を新しいRcを外側のRefCellに配置することによって行われます。これの副作用は、消費者がRefIterが進められた後でさえも、(返されたRcを通して)バッファへの「古い」参照を保持することができるということです。

+0

「RefIter」自体のデザインに欠陥があると想定するのは間違いです。イテレータを一度に1つずつ繰り返し簡単に繰り返すことができます:https://gist.github.com/anonymous/b43d24bbd5649b62d0b9612cfa9bc01a。 'Rc'を使うときは普通のイテレータを使うこともできますが、私の場合、同じバッファの異なるスライスへの参照を返したいと思います(新しいデータが読み込まれると変更されます)。 – aseyboldt

+0

問題は、語彙以外の生涯に関連しているようです。 'each_zipped'の始めに' current1'に変更可能なリファレンスを取ります。その後、この値を使用してコールバックを呼び出します。この時点では、参照はもう必要ありません。 * next_refをもう一度呼び出す必要がありますが、コンパイラはそれに気づいていないようです。 – aseyboldt

+0

私はあなたが非語彙の生涯について正しいと思います。「current1」と「current2」の生存期間は、コールバック後に使用されなくても、関数の終わりまで拡張されます。 –

関連する問題