Open9

Rust イテレータ分からん

koko_ukoko_u

構造体に対して、イテレータを返却するようなメソッドを作りたくなることは多い。
単純な例で考えていくぞ。

struct Items {
    data: [i32; 3],
}

この Items 構造体に、順に i32 の値を返却するようなイテレータを返却するメソッドを作りたい

fn main() {
    let items = Items {
        data: [3, 7, 11],
    };

    for item in items.iter() {
        println!("{item}");
    }
}

期待する結果

3
7
11
koko_ukoko_u

まずは Itemsiter メソッドを生やす

impl Items {
    fn iter(&self) -> impl Iterator<Item = i32> {
        todo!()
    }
}

戻り値は i32 を順に返却してくるイテレータ。

(ちなみに todo! と書いてもコンパイラに怒られるけど、話に関係ないので無視)

koko_ukoko_u

iter メソッドで返却される「順に i32 を返却するイテレータ」となる構造体を作る

struct ItemsIter {
    current: usize,
    target: &Items,
}

current イテレートしている要素のインデックス。
target として、要素を順に取り出す対象となる Items への参照を保持する。

...これではコンパイルできない

error[E0106]: missing lifetime specifier
  --> it/src/main.rs:24:13
   |
24 |     target: &Items,
   |             ^ expected named lifetime parameter
   |
help: consider introducing a named lifetime parameter
   |
22 ~ struct ItemsIt<'a> {
23 |     current: usize,
24 ~     target: &'a Items,
   |

コンパイラに言われるままに修正する。

struct ItemsIter<'a> {
    current: usize,
    target: &'a Items,
}
koko_ukoko_u

ItemsIter をイテレータにするべく、Iterator トレイトを実装する

impl<'a> Iterator for ItemsIter<'a> {
    type Item = i32;

    fn next(&mut self) -> Option<Self::Item> {
        match self.current {
            current @ (0 | 1 | 2) => {
                self.current += 1;
                Some(self.target[current])
            }
            _ => None
        }
    }
}
koko_ukoko_u

え?後置インクリメント演算子が欲しいだって!?

無いよ。

koko_ukoko_u

Itemsiter メソッドでこの ItemsIter を返却すれば完成!

impl Items {
    fn iter(&self) -> impl Iterator<Item = i32> {
        ItemsIter { current: 0, target: self }
    }
}

...ではない。

error[E0700]: hidden type for `impl Iterator<Item = i32>` captures lifetime that does not appear in bounds
  --> it/src/main.rs:15:9
   |
14 |       fn it(&self) -> impl Iterator<Item = i32> {
   |             -----     ------------------------- opaque type defined here
   |             |
   |             hidden type `ItemsIter<'_>` captures the anonymous lifetime defined here
15 | /         ItemsIter {
16 | |             current: 0,
17 | |             target: self,
18 | |         }
   | |_________^
   |
help: to declare that `impl Iterator<Item = i32>` captures `'_`, you can add an explicit `'_` lifetime bound
   |
14 |     fn it(&self) -> impl Iterator<Item = i32> + '_ {
   |                                               ++++

koko_ukoko_u

できた?わからん。

impl Items {
    fn iter(&self) -> impl Iterator<Item = i32> + '_ {
        ItemsIter { current: 0, target: self }
    }
}

エラーメッセージの語彙が難しい

  • hidden type
  • anonymous lifetime
  • opaque type
koko_ukoko_u

iter メソッドを impl Trait を使わずに、明示的に型を指定すると次のようになる。

impl Items {
    fn iter<'a>(&'a self) -> ItemsIter<'a> {
        ItemsIter<'a> { current: 0, target: self }
    }
}

ItemsIter の型制約は

impl<'a> Iterator for ItemsIter<'a> { ... }

なので、impl Iterator<Items =...> ではなくて impl Iterator<Items = ...> + 'a ということ(多分)

koko_ukoko_u

ライフタイムパラメータを 'a でなく、無名で'_ と書ける条件はよくわからん。

impl Items {
    fn iter(&self) -> ItemsIter<'_> {
        ItemsIter { current: 0, target: self }
    }
}
impl Items {
    fn iter(&self) -> Impl Iterator<Item=i32> + '_ {
        ItemsIter { current: 0, target: self }
    }
}