Iterator パターンの変種、Lending Iterator パターンについて。

概要

Iterator と Lending Iterator の違いについて。

Iterator

標準機能にトレイト std::iter::Iterator がある。

アイテムはイテレータとライフタイムを共有しない。

そのため、イテレータを破棄してもアイテムの使用を続けられる。

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

汎用用法 (GAT)

イテレートの対象を参照型に限定しない実装について。

この場合、参照を含みうるアイテムが必要なため、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 Polyfill)

、GAT にはまだ問題点が多い。

これらは GAT の代わりに nougat のような Polyfill を使うと解決できる。ただしその場合、API は通常の GAT のそれと似て非なるものになる。これらは通常の使用ではあまり問題にならないが、エラーメッセージやドキュメントをかなり難解にする欠点がある。

(サンプルは通常の GAT とあまり変わらないので割愛。)

クレート紹介

以下、代表的なクレートとその実装方式。これらを使う事で、Iterator トレイトにおける filter メソッドのような数多くのメソッドを自前で実装せずに済むようになる。

streaming-iterator
限定用法
gat-lending-iterator
汎用用法 (GAT)
lending-iterator
汎用用法 (GAT Polyfill)