Iterator パターンの変種、Lending Iterator パターンについて。
Iterator と Lending Iterator の違いについて。
標準機能にトレイト std::iter::Iterator
がある。
アイテムはイテレータとライフタイムを共有しない。
そのため、イテレータを破棄してもアイテムの使用を続けられる。
標準機能にないため、外部クレートか自前実装が必要になる。
アイテムはイテレータとライフタイムを共有する。
そのため、イテレータを破棄するとアイテムの使用を続けられない。
Lending Iterator 系のトレイトを利用する型について。
WindowsMut
このイテレータのアイテムは配列への可変スライスで、スライスの開始位置はアイテムの取得ごとに一歩ずつ前進し、スライスの長さは初期化時に指定された固定長になる。
なお、このイテレータは通常のイテレータと異なり、同じイテレータ由来の複数のアイテムが同時に存在できない。なぜなら、各アイテムは同じイテレータを可変参照しているため、借用チェッカーによる排他制御下にある。これにより、複数の可変スライスの範囲が重複して編集が競合するような事態を予防できる。
汎用性と GAT の取扱が焦点になる。
イテレートの対象を参照型に限定した実装について。
この場合、next
メソッドの戻り値の型は、&Item
や &mut Item
になる。
以下では、トレイト MutAddressIterator
が Lending Iterator になっている。
fn main() {
let mut arr = [1, 2, 3];
let mut iter = WindowsMut::new(&mut arr, 2);
assert_eq!(iter.next(), Some([1, 2].as_mut_slice()));
assert_eq!(iter.next(), Some([2, 3].as_mut_slice()));
assert_eq!(iter.next(), None);
}
trait MutAddressIterator {
type Item: ?Sized;
fn next(&mut self) -> Option<&mut Self::Item>;
}
struct WindowsMut<'arr, T> {
slice: &'arr mut [T],
size: usize,
pos: usize,
}
impl<'arr, T> WindowsMut<'arr, T> {
fn new(slice: &'arr mut [T], size: usize) -> Self {
assert_ne!(size, 0);
Self { slice, size, pos: 0 }
}
}
impl<'arr, T> MutAddressIterator for WindowsMut<'arr, T> {
type Item = [T];
fn next(&mut self) -> Option<&mut Self::Item> {
let result = self.slice[self.pos..].get_mut(..self.size)?;
self.pos += 1;
Some(result)
}
}
イテレートの対象を参照型に限定しない実装について。
この場合、参照を含みうるアイテムが必要なため、Item
型が GAT になる。
以下では、トレイト LendingIterator
が Lending Iterator になっている。
fn main() {
let mut arr = [1, 2, 3];
let mut iter = WindowsMut::new(&mut arr, 2);
assert_eq!(iter.next(), Some([1, 2].as_mut_slice()));
assert_eq!(iter.next(), Some([2, 3].as_mut_slice()));
assert_eq!(iter.next(), None);
}
trait LendingIterator {
type Item<'a> where Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>>;
}
struct WindowsMut<'arr, T> {
slice: &'arr mut [T],
size: usize,
pos: usize,
}
impl<'arr, T> WindowsMut<'arr, T> {
fn new(slice: &'arr mut [T], size: usize) -> Self {
assert_ne!(size, 0);
Self { slice, size, pos: 0 }
}
}
impl<'arr, T> LendingIterator for WindowsMut<'arr, T> {
type Item<'a> = &'a mut [T] where Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>> {
let result = self.slice[self.pos..].get_mut(..self.size)?;
self.pos += 1;
Some(result)
}
}
、GAT にはまだ問題点が多い。
これらは GAT の代わりに nougat
のような Polyfill を使うと解決できる。ただしその場合、API は通常の GAT のそれと似て非なるものになる。これらは通常の使用ではあまり問題にならないが、エラーメッセージやドキュメントをかなり難解にする欠点がある。
(サンプルは通常の GAT とあまり変わらないので割愛。)
以下、代表的なクレートとその実装方式。これらを使う事で、Iterator
トレイトにおける filter
メソッドのような数多くのメソッドを自前で実装せずに済むようになる。
streaming-iterator
gat-lending-iterator
lending-iterator