Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rustのイテレーター完全制覇 / domination-of-the-rust-iterators

OPTiM
July 29, 2021

Rustのイテレーター完全制覇 / domination-of-the-rust-iterators

イテレーターの仕組みや使い方、そしてメニアックな話を紹介します。イテレーターを何となく敬遠している方、または雰囲気で使っている方、あるいはIteratorの知識をnextしたい方におすすめです。

OPTiM TECH NIGHT|Rustのイテレーター完全制覇! - connpass
https://optim.connpass.com/event/218495/

OPTiM

July 29, 2021
Tweet

More Decks by OPTiM

Other Decks in Programming

Transcript

  1. 齋藤( @aznhe21) HAL東京4年制卒 2017年度新卒入社 27歳 最近の仕事:OPTiM AI CameraのAI周り (右下) VRゲームのBeat

    Saberを布教したい(右上) 最近のTECH BLOGの投稿たち https://japanese.engadget.com/kddi-5-g-artificialintelligence-033658347.html 2
  2. イテレーターのおさらい 大雑把に言えばforループでのループ対象 for i in 0..10 { // ^^^^^ println!("{}",

    i); } let v = vec![1, 2, 3]; for x in &v { // ^^ println!("{}", i); } for entry in std::fs::read_dir(".")? { // ^^^^^^^^^^^^^^^^^^^^^^^ let entry = entry?; println!("{:?}", entry.file_name()); } Copyright © OPTiM Corp. All Rights Reserved. 7
  3. イテレーターのおさらい または関数型プログラミングスタイルで使うもの let v = vec![1, 2, 3]; v.iter() //

    Iterator<Item = &u32> .copied() // Iterator<Item = u32> .filter(|x| *x % 2 == 1) // Iterator<Item = u32> .map(|x| x * 2) // Iterator<Item = u32> .for_each(|x| println!("{}", x)); // -> 2, 6 イテレーターとは・・・ 値を1つずつ取り出して処理(=「反復」「イテレート(iterate)」)するためのもの 日本語ではイテレーターのことを反復子と呼ぶ(JIS規格より) Copyright © OPTiM Corp. All Rights Reserved. 8
  4. イテレーターとして扱える型 1. Range 系の型( 0..10 や 0.. など) 2. スライスや

    Vec 、 HashMap などのコレクション型 3. std::fs::read_dir() (の戻り値である std::fs::ReadDir 型) 4. std::sync::mpsc::Receiver 対応する std::sync::mpsc::Sender が生存している間、データを受信する度にイテレーターが進む 5. etc... Copyright © OPTiM Corp. All Rights Reserved. 9
  5. Rustのイテレーターの特徴 1. 回す時は手続き型(for文)でも関数型( for_each などでメソッドチェーン)でも使える 読みやすい方で書ける 2. 遅延評価 JavaScriptだと const

    v = [1, 2, 3]; v // -> Array<number> .filter(x => x % 2 == 1) // -> Array<number> .map(x => console.log(x)) // -> Array<void> ; // 1, 3 Rustだと let v = vec![1, 2, 3]; v.iter() // -> Iterator<Item = &u32> .copied() // -> Iterator<Item = u32> .filter(|x| *x % 2 == 1) // -> Iterator<Item = u32> .map(|x| println!("{}", x)) // -> Iterator<Item = ()> ; // 何も出力されない // warning: unused `Map` that must be used // = note: `#[warn(unused_must_use)]` on by default // = note: iterators are lazy and do nothing unless consumed Copyright © OPTiM Corp. All Rights Reserved. 10
  6. イテレーターは何者だ イテレーターとは Iterator トレイトを実装する型 Range は Iterator を実装するが、 Vec は実装しない

    つまり Vec そのものはイテレーターではない pub trait Iterator { // ループする値の型 type Item; // 次の値を取得する fn next(&mut self) -> Option<Self::Item>; // ... } impl<A: Step> Iterator for ops::Range<A> { /* ... */ } // VecへのIterator実装はない 値が尽きるまで next() を呼び続けることで全ての値を列挙・反復できる。 let mut iter = 1..3; // イテレーターは自身を変更するためmut println!("{:?}", iter.next()); // Some(1) println!("{:?}", iter.next()); // Some(2) println!("{:?}", iter.next()); // None Copyright © OPTiM Corp. All Rights Reserved. 11
  7. forループの正体 forループはコンパイラによって IntoIterator::into_iter() を使ったwhileループに変換される。 into_iter() は値をイテレーターに変換する。 for x in &v

    {} ↑と↓は同義 let mut iter = (&v).into_iter(); // ^^^^ while let Some(x) = iter.next() {} v.into_iter() と (&v).into_iter() は全く別の意味(使い方の章で説明) 実際のループ対象は Vec 型の値ではなく IntoIterator::into_iter() の戻り値 Vec 自体をループしているわけではない! Copyright © OPTiM Corp. All Rights Reserved. 12
  8. IntoIteratorを経由することの意義 Iterator が直接実装されている Range の挙動を見てみると意義が分かりやすい。 let mut range = 0..10;

    println!("{:?}", range); // 0..10 range.next(); // イテレーターを進める println!("{:?}", range); // 1..10 Range 自体がイテレーターのため Range そのものを変更してしまう Copyright © OPTiM Corp. All Rights Reserved. 13
  9. IntoIteratorを経由することの意義 Vec 自体がイテレーターだった場合は Vec を書き換えることになってしまう。 その場合は next() で先頭要素を削除することになり、2番目以降の要素を1つ前にずらすという処理が発生してしまう(図の左側)。 代わりに別オブジェクトを用意し、追加でインデックスを保持することでコピーが無くなる(図の右側)。 このオブジェクトに変換するのが

    IntoIterator::into_iter() の役割。 Vec自体がIteratorの場合 1 2 3 next() 2 3 別にイテレーター型を用意する場合 1 2 3 next() 1 2 3 使い分けるなら 値自身を変更しても良いなら直接 Iterator を実装 値を変更したらまずい場合は IntoIterator を実装 ←基本こっち Copyright © OPTiM Corp. All Rights Reserved. 14
  10. IntoIteratorの定義 pub trait IntoIterator { // Iteratorでループする値の型 type Item; //

    変換後のイテレーターの型 type IntoIter: Iterator<Item = Self::Item>; // 値をイテレーターに変換する fn into_iter(self) -> Self::IntoIter; } Iterator には IntoIterator が自動で実装される。 これによりイテレーター自体をforループの対象に出来る。 impl<I: Iterator> IntoIterator for I { type Item = I::Item; type IntoIter = I; // イテレーターからはイテレーター自身を返す fn into_iter(self) -> I { self } } let v = vec![1, 2, 3]; for x in v.iter() { println!("{}", x); } Copyright © OPTiM Corp. All Rights Reserved. 15
  11. 3パターンのイテレーター Vec を始め HashMap や BTreeMap などのコレクション型には IntoIterator が3パターン実装されており、 使い方によって所有権の扱いが異なる。

    また、それぞれの IntoIterator と対応するメソッドも用意されており、関数型スタイルで使うことも出来る。 Copyright © OPTiM Corp. All Rights Reserved. 17
  12. 3パターンのイテレーター:ムーブ 値をムーブするので中身の所有権を奪うことが出来る。 let v: Vec<u32> = vec![1, 2, 3]; //

    xの型はu32 for x in v { /* ... */ } for x in v.into_iter() { /* ... */ } // 関数型スタイル v.into_iter().for_each(|x| { /* ... */ }); impl<T, A: Allocator> IntoIterator for Vec<T, A> { type Item = T; type IntoIter = IntoIter<T, A>; fn into_iter(self) -> IntoIter<T, A> { /* ... */ } } Copyright © OPTiM Corp. All Rights Reserved. 18
  13. 3パターンのイテレーター:借用 借用するので値を再利用出来る。 大抵の場合 IntoIterator は iter() を呼び出しているだけ。 let v: Vec<u32>

    = vec![1, 2, 3]; // xの型は&u32 for x in &v { /* ... */ } for x in v.iter() { /* ... */ } // 関数型スタイル v.iter().for_each(|x| { /* ... */ }); impl<'a, T, A: Allocator> IntoIterator for &'a Vec<T, A> { type Item = &'a T; type IntoIter = slice::Iter<'a, T>; fn into_iter(self) -> slice::Iter<'a, T> { self.iter() } } Copyright © OPTiM Corp. All Rights Reserved. 19
  14. 3パターンのイテレーター:可変借用 可変借用するので値を変更出来る。 大抵の場合 IntoIterator は iter_mut() を呼び出しているだけ。 let mut v:

    Vec<u32> = vec![1, 2, 3]; // xの型は&mut u32 for x in &mut v { /* ... */ } for x in v.iter_mut() { /* ... */ } // 関数型スタイル v.iter_mut().for_each(|x| { /* ... */ }); impl<'a, T, A: Allocator> IntoIterator for &'a mut Vec<T, A> { type Item = &'a mut T; type IntoIter = slice::IterMut<'a, T>; fn into_iter(self) -> slice::IterMut<'a, T> { self.iter_mut() } } Copyright © OPTiM Corp. All Rights Reserved. 20
  15. iter()の有無による違い 良く疑問に思われがちなforループの .iter() の有無の違いはここまでくれば分かるはず。 ムーブ for x in v {}

    for x in v.into_iter() {} 借用 for x in &v {} for x in (&v).into_iter() {} for x in v.iter() {} 可変借用 for x in &mut v {} for x in (&mut v).into_iter() {} for x in v.iter_mut() {} 各セクション内のコードは同じ意味。 Copyright © OPTiM Corp. All Rights Reserved. 21
  16. Iteratorトレイトのメソッド Iterator を関数型スタイルで使う時は map や find などを使うことになる。 便利なメソッドは大量にあるので紹介記事は要チェック。 このような Iterator

    のメソッドは随時追加されている。 Iterator::copied :1.36で追加 Iterator::reduce :1.51で追加 今後追加が検討されている(安定化がまだな)メソッド Iterator::advance_by : next() を n 回呼ぶ Iterator::intersperse :末尾と要素の間に指定した値を追加する。 collect() と組み合わせると join() 的な挙動になる Iterator::try_find :クロージャーから Ok(Some(v)) または Err が返ったらそれを返す etc... https://qiita.com/lo48576/items/34887794c146042aebf1 Copyright © OPTiM Corp. All Rights Reserved. 22
  17. ExactSizeIterator pub trait Iterator { fn size_hint(&self) -> (usize, Option<usize>)

    { /* ... */ } } Iterator::size_hint はイテレーターがどれくらいループ出来るのかを示すメソッドで、 「保証される最小の要素数」と「保証される最大の要素数」を返す。 つまり、要素数が不明の場合は (0, None) を、要素数が1以上の場合は (1, None) を、 要素数が5以上10以下の場合は (5, Some(10)) を返す。 pub trait ExactSizeIterator: Iterator { fn len(&self) -> usize { /* ... */ } } ExactSizeIterator は正確な要素数が分かる場合に実装される。 主にスライスや Vec 、 HashMap のイテレーターなどに実装される。 size_hint との違いはまさしく要素数の保証があるかどうか。 let mut iter = [0, 1, 2].iter(); println!("{}", iter.len()); // 3 iter.next(); println!("{}", iter.len()); // 2 Copyright © OPTiM Corp. All Rights Reserved. 24
  18. DoubleEndedIterator pub trait DoubleEndedIterator: Iterator { fn next_back(&mut self) ->

    Option<Self::Item>; // ... } 双方向イテレーターである場合に実装される。 つまり最後の要素を1つ消費する(戻す)ことが出来るイテレーター( next() したあとに戻れるわけではない)。 let mut iter = [0, 1, 2, 3].iter(); println!("{:?}", iter.next_back()); // Some(3) println!("{:?}", iter.next_back()); // Some(2) println!("{:?}", iter.next()); // Some(0) println!("{:?}", iter.next()); // Some(1) println!("{:?}", iter.next_back()); // None 追加メソッドとして、 nth_back / try_rfold / rfold / rfind などが使える。 また Iterator::rev() も使えるようになる。 Copyright © OPTiM Corp. All Rights Reserved. 25
  19. FusedIterator pub trait FusedIterator: Iterator {} イテレーターが None を返したら、その後もずっと None

    を返し続けることを保証するトレイト。 実は Iterator::next() は None を返したあとに Some(v) を返しても良いと規定している イテレーターが None を返したあとでも None を返し続けることに依存するコードを書く場合、 Iterator::fuse() を呼び出すことで None を返し続ける挙動にすることが出来る。 ( FusedIterator に対して fuse を呼び出した場合でも呼び出しオーバーヘッドがないように実装されている) fn hoge<I: Iterator<Item = u32>>(mut iter: I) { // イテレーターを最後まで進める while let Some(_) = iter.next() {} println!("{:?}", iter.next()); // Noneである保証はない } fn fuga<I: Iterator<Item = u32>>(iter: I) { let mut iter = iter.fuse(); while let Some(_) = iter.next() {} println!("{:?}", iter.next()); // 常にNone } Copyright © OPTiM Corp. All Rights Reserved. 26
  20. FromIterator pub trait FromIterator<A>: Sized { fn from_iter<T: IntoIterator<Item =

    A>>(iter: T) -> Self; } イテレーターを元に構築出来る型に実装される。 これを実装することで Iterator::collect() が使えるようにな る。 impl FromIteartor<I> for X とすると、要素が I 型のイテレーターから X を構築出来る、という意味。 Rustの標準ドキュメント(https://doc.rust-lang.org/std/iter/trait.FromIterator.html)を見ると 実装されている型が大量にあることが分かる。 1. FromIterator<char> for String 2. FromIterator<Result<A, E>> for Result<V, E> 要素が Result<T, E> の場合に Result<Vec<T>, E> で受け取るような形 3. FromIterator<Option<A>> for Option<V> 要素が Option<T> の場合に Option<Vec<T>> で受け取るような形 4. FromIterator<AsRef<Path>> for PathBuf ["a", "b", "c"] から PathBuf("a/b/c") で受け取るような形 Copyright © OPTiM Corp. All Rights Reserved. 27
  21. FromIterator Iterator<Item=u32> から Vec<u32> を構築する let v: Vec<u32> = [1,

    2, 3].iter().copied().collect(); println!("{:?}", v); // [1, 2, 3] Iterator<Item=char> から String を構築する let v: String = ['a', 'b', 'c'].iter().copied().collect(); println!("{:?}", v); // "abc" Iterator<Item=Result<u32, u32>> から Result<Vec<u32>, u32> を構築する let v: Result<Vec<u32>, u32> = [Ok(1), Ok(2), Ok(3)].iter().copied().collect(); println!("{:?}", v); // Ok([1, 2, 3]) let v: Result<Vec<u32>, u32> = [Ok(1), Ok(2), Err(3)].iter().copied().collect(); println!("{:?}", v); // Err(3) Iterator<Item=AsRef<Path>> から PathBuf を構築する let v: std::path::PathBuf = ["a", "b", "c"].iter().collect(); println!("{}", v.display()); // "a/b/c" Copyright © OPTiM Corp. All Rights Reserved. 28
  22. std::iter::empty pub const fn empty<T>() -> Iterator<Item = T> 何も返さない、空のイテレーターを生成する。

    let mut iter = std::iter::empty::<u32>(); assert_eq!(iter.next(), None); assert_eq!(iter.len(), 0); // ExactSizeIterator assert_eq!(iter.next_back(), None); // DoubleEndedIterator Copyright © OPTiM Corp. All Rights Reserved. 29
  23. std::iter::from_fn pub fn from_fn<T, F>(f: F) -> Iterator<Item = T>

    where F: FnMut() -> Option<T> 指定されたクロージャーからイテレーターを生成する。 Iterator::next がクロージャーとして実装されるイメージ。 let iter = std::iter::from_fn(|| { match rand::random::<i32>() { i if i < 0 => None, i => Some(i), } }); // 乱数が負の値になるまで値を出力する for x in iter { println!("{}", x); } Copyright © OPTiM Corp. All Rights Reserved. 30
  24. std::iter::once / once_with pub fn once<T>(value: T) -> Iterator<Item =

    T> pub fn once_with<A, F>(gen: F) -> Iterator<Item = A> where F: FnOnce() -> A 指定された値(クロージャーから返された値)を1度返すイテレーターを生成する。 // イテレーター生成時に乱数を得る let mut iter = std::iter::once(rand::random::<u32>()); println!("{:?}", iter.next()); // Some(?) println!("{:?}", iter.next()); // None // イテレーター呼び出し時に乱数を得る let mut iter = std::iter::once_with(rand::random::<u32>); println!("{:?}", iter.next()); // Some(?) println!("{:?}", iter.next()); // None Copyright © OPTiM Corp. All Rights Reserved. 31
  25. std::iter::repeat / repeat_with pub fn repeat<T>(elt: T) -> Iterator<Item =

    T> where T: Clone pub fn repeat_with<A, F>(repeater: F) -> Iterator<Item = A> where F: FnMut() -> A 指定された値(クロージャーを呼び出して得られる値)を無限に繰り返すイテレーターを生成する。 let mut iter = std::iter::repeat(rand::random::<i32>()); // どちらも同じ値 println!("{:?}", iter.next()); println!("{:?}", iter.next()); let iter = std::iter::repeat_with(rand::random::<i32>) .take_while(|i| *i >= 0); // 乱数が負の値になるまで値を出力する for x in iter { println!("{}", x); } Copyright © OPTiM Corp. All Rights Reserved. 32
  26. std::iter::successors pub fn successors<T, F>(first: Option<T>, succ: F) -> Iterator<Item

    = T> where F: FnMut(&T) -> Option<T> 初期値からクロージャーを呼び出して値を計算し続けるイテレーターを生成する。 雰囲気は Iterator::fold に近い。 let mut iter = std::iter::successors(Some(1), |&v| { // bool.then()はtrueならSome(closure())を、falseならNoneを返す (v < 10).then(|| v * 10) }); println!("{:?}", iter.next()); // Some(1) println!("{:?}", iter.next()); // Some(10) println!("{:?}", iter.next()); // None クロージャーから返った値は次の next() で返ることに注意。 let mut iter = std::iter::successors(Some(0), |&x| { let x = x + 1; println!("closure: {}", x); Some(x) }); println!("next: {}", iter.next().unwrap()); // closure: 1 // next: 0 println!("next: {}", iter.next().unwrap()); // closure: 2 // next: 1 Copyright © OPTiM Corp. All Rights Reserved. 33
  27. 意外なイテレーター Option と Result は IntoIterator を実装しているのでイテレーターとして扱える。 let mut v

    = vec![0u32]; v.extend(Some(1)); // 追加される v.extend(None::<u32>); // 追加されない v.extend(Ok::<u32, ()>(2)); // 追加される v.extend(Err::<u32, ()>(())); // 追加されない println!("{:?}", v); // [0, 1, 2] once と empty を条件で呼び変える代わりにも使える。 fn get1() -> impl Iterator<Item = u32> { if rand::random::<bool>() { Box::new(std::iter::once(rand::random::<u32>())) as Box<dyn Iterator<Item = u32>> } else { Box::new(std::iter::empty()) as Box<dyn Iterator<Item = u32>> } } fn get2() -> impl Iterator<Item = u32> { if rand::random::<bool>() { Some(rand::random()) } else { None }.into_iter() } Copyright © OPTiM Corp. All Rights Reserved. 34
  28. Iteratorを実装する 最低限実装しなければならないアイテム pub trait Iterator { type Item; fn next(&mut

    self) -> Option<Self::Item>; } 無限に値を返すイテレーターを作ってみる struct Hoge; impl Iterator for Hoge { type Item = u32; fn next(&mut self) -> Option<Self::Item> { Some(1) } } fn main() { let mut iter = Hoge.map(|x| x * 2); println!("{:?}", iter.next()); // Some(2) println!("{:?}", iter.next()); // Some(2) println!("{:?}", iter.next()); // Some(2) } Copyright © OPTiM Corp. All Rights Reserved. 36
  29. カウントダウンするイテレーター struct CountDown(pub u32); impl Iterator for CountDown { type

    Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.0 == 0 { None } else { self.0 -= 1; Some(self.0) } } } fn main() { // 9 8 7 6 5 4 3 2 1 0 for x in CountDown(10) { println!("{}", x); } } (0..N).rev() のイメージ。 Copyright © OPTiM Corp. All Rights Reserved. 37
  30. 他に実装しておくべきもの pub trait Iterator { fn size_hint(&self) -> (usize, Option<usize>)

    { /* ... */ } } イテレーターの最小個数、または最大個数が分かる場合に実装する。 pub trait ExactSizeIterator: Iterator { fn len(&self) -> usize { /* ... */ } } 個数が特定出来る場合に実装する。 Iterator::size_hint と共に実装するべきである。 len にデフォルト実装はあるものの、 size_hint を参照して保証が保たれているかをアサートするものであるため、自分で実装するべきである。 pub trait DoubleEndedIterator: Iterator { fn next_back(&mut self) -> Option<Self::Item>; } 双方向イテレーターである場合に実装する。 next_back は最後の要素をイテレーターから除去して返す。 pub trait FusedIterator: Iterator {} イテレーターが None を返したあと、ずっと None を返し続ける場合に実装する。 マーカートレイトであり、実装すべきメソッドはない。 Copyright © OPTiM Corp. All Rights Reserved. 38
  31. IntoIteratorを実装する(省力版) use std::{array, slice}; struct Array<T, const N: usize>(pub [T;

    N]); impl<T, const N: usize> Array<T, N> { pub fn iter(&self) -> slice::Iter<T> { self.0.iter() } pub fn iter_mut(&mut self) -> slice::IterMut<T> { self.0.iter_mut() } } impl<T, const N: usize> IntoIterator for Array<T, N> { type Item = T; type IntoIter = array::IntoIter<T, N>; fn into_iter(self) -> Self::IntoIter { IntoIterator::into_iter(self.0) } } impl<'a, T, const N: usize> IntoIterator for &'a Array<T, N> { type Item = &'a T; type IntoIter = slice::Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<'a, T, const N: usize> IntoIterator for &'a mut Array<T, N> { type Item = &'a mut T; type IntoIter = slice::IterMut<'a, T>; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } fn main() { let mut arr = Array([0, 1, 2]); // 0, 1, 2 for x in &arr { println!("{}", x); } for x in &mut arr { *x *= 2; } // 0, 2, 4 for x in arr { println!("{}", x); } } Copyright © OPTiM Corp. All Rights Reserved. 39
  32. 他の言語にあってRustにないイテレーターの機能 Q. 他の言語にありがちだがRustの標準ライブラリにはない機能にどんなものがあるか? A. 文字列に結合する、いわゆるjoinは標準ライブラリにはないもののitertoolsクレートには join メソッドがある。 標準ライブラリとしては今後[interspace]メソッドによって近しいことが出来るようになる予定。 Python print(',

    '.join(['a', 'b', 'c'])) # 'a, b, c' JavaScript console.log(["a", "b", "c"].join(", ")); // "a, b, c" Rust(itertools) use itertools::Itertools; println!("{}", ["a", "b", "c"].iter().join(", ")); Rust(Nightly) #![feature(iter_intersperse)] println!("{}", ["a", "b", "c"].iter().copied().intersperse(", ").collect::<String>()); // "a, b, c" Copyright © OPTiM Corp. All Rights Reserved. 43
  33. オプションのイテレーター化 Q. オプションをイテレーターで使える件について、 C++にそれがあったらforで回せそうだがRustでそういう使い方はする? std::optional<int> op; for (auto& x :

    op) { std::cout << x << std::endl; } A. Rustにはif-let構文があるので使うことは無さそう。 let v = Some(0); for x in &v { println!("{}", x); } if let Some(x) = &v { println!("{}", x); } Copyright © OPTiM Corp. All Rights Reserved. 44
  34. Rust 1.51のreduceメソッド Q. 他の言語では良くある reduce メソッドが1.51にもなってから導入されたのはなぜか? A. JavaScriptなどでの reduce はRust

    1.0時点で fold メソッドとして実装されていた。 1.51で実装された reduce メソッドは初期値をイテレーターの最初の値から得るもの。 JavaScript console.log([1, 2, 3].reduce((a, b) => a + b, 0)); // 6 = 0 + 1 + 2 + 3 Rust println!("{}", [1, 2, 3].iter().fold(0, |a, b| a + b)); // 6 = 0 + 1 + 2 + 3 println!("{:?}", [1, 2, 3].iter().copied().reduce(|a, b| a + b)); // Some(6) = 1 + 2 + 3 Copyright © OPTiM Corp. All Rights Reserved. 46