Slide 1

Slide 1 text

Rustのイテレーター完全制覇 株式会社オプティム R&Dユニット 齋藤 OPTiM TECH NIGHT OVER ZOOM 2021/07/29 Copyright © OPTiM Corp. All Rights Reserved. 1

Slide 2

Slide 2 text

齋藤( @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

Slide 3

Slide 3 text

目次 1. イテレーター入門 2. イテレーターの使い方 3. イテレーターの作り方 4. さいごに 5. Q&A Copyright © OPTiM Corp. All Rights Reserved. 3

Slide 4

Slide 4 text

イテレーター入門 Copyright © OPTiM Corp. All Rights Reserved. 4

Slide 5

Slide 5 text

入門の前に:iterの読み方 Rustではイテレーターを iter と略すことが多いです。 Copyright © OPTiM Corp. All Rights Reserved. 5

Slide 6

Slide 6 text

入門の前に:iterの読み方 Rustではイテレーターを iter と略すことが多いです。 ここでは国際熱核融合実験炉に従って「イーター」と呼んでいます。 https://ja.wikipedia.org/wiki/ITER Copyright © OPTiM Corp. All Rights Reserved. 6

Slide 7

Slide 7 text

イテレーターのおさらい 大雑把に言えば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

Slide 8

Slide 8 text

イテレーターのおさらい または関数型プログラミングスタイルで使うもの let v = vec![1, 2, 3]; v.iter() // Iterator .copied() // Iterator .filter(|x| *x % 2 == 1) // Iterator .map(|x| x * 2) // Iterator .for_each(|x| println!("{}", x)); // -> 2, 6 イテレーターとは・・・ 値を1つずつ取り出して処理(=「反復」「イテレート(iterate)」)するためのもの 日本語ではイテレーターのことを反復子と呼ぶ(JIS規格より) Copyright © OPTiM Corp. All Rights Reserved. 8

Slide 9

Slide 9 text

イテレーターとして扱える型 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

Slide 10

Slide 10 text

Rustのイテレーターの特徴 1. 回す時は手続き型(for文)でも関数型( for_each などでメソッドチェーン)でも使える 読みやすい方で書ける 2. 遅延評価 JavaScriptだと const v = [1, 2, 3]; v // -> Array .filter(x => x % 2 == 1) // -> Array .map(x => console.log(x)) // -> Array ; // 1, 3 Rustだと let v = vec![1, 2, 3]; v.iter() // -> Iterator .copied() // -> Iterator .filter(|x| *x % 2 == 1) // -> Iterator .map(|x| println!("{}", x)) // -> Iterator ; // 何も出力されない // 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

Slide 11

Slide 11 text

イテレーターは何者だ イテレーターとは Iterator トレイトを実装する型 Range は Iterator を実装するが、 Vec は実装しない つまり Vec そのものはイテレーターではない pub trait Iterator { // ループする値の型 type Item; // 次の値を取得する fn next(&mut self) -> Option; // ... } impl Iterator for ops::Range { /* ... */ } // 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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

IntoIteratorの定義 pub trait IntoIterator { // Iteratorでループする値の型 type Item; // 変換後のイテレーターの型 type IntoIter: Iterator; // 値をイテレーターに変換する fn into_iter(self) -> Self::IntoIter; } Iterator には IntoIterator が自動で実装される。 これによりイテレーター自体をforループの対象に出来る。 impl 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

Slide 16

Slide 16 text

イテレーターの使い方 Copyright © OPTiM Corp. All Rights Reserved. 16

Slide 17

Slide 17 text

3パターンのイテレーター Vec を始め HashMap や BTreeMap などのコレクション型には IntoIterator が3パターン実装されており、 使い方によって所有権の扱いが異なる。 また、それぞれの IntoIterator と対応するメソッドも用意されており、関数型スタイルで使うことも出来る。 Copyright © OPTiM Corp. All Rights Reserved. 17

Slide 18

Slide 18 text

3パターンのイテレーター:ムーブ 値をムーブするので中身の所有権を奪うことが出来る。 let v: Vec = vec![1, 2, 3]; // xの型はu32 for x in v { /* ... */ } for x in v.into_iter() { /* ... */ } // 関数型スタイル v.into_iter().for_each(|x| { /* ... */ }); impl IntoIterator for Vec { type Item = T; type IntoIter = IntoIter; fn into_iter(self) -> IntoIter { /* ... */ } } Copyright © OPTiM Corp. All Rights Reserved. 18

Slide 19

Slide 19 text

3パターンのイテレーター:借用 借用するので値を再利用出来る。 大抵の場合 IntoIterator は iter() を呼び出しているだけ。 let v: Vec = 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 { 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

Slide 20

Slide 20 text

3パターンのイテレーター:可変借用 可変借用するので値を変更出来る。 大抵の場合 IntoIterator は iter_mut() を呼び出しているだけ。 let mut v: Vec = 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 { 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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

その他のイテレーター Iterator 以外のトレイトや Iterator のメソッドではない自由関数、 イテレーターとして使うことを思いつかない型を紹介。 Copyright © OPTiM Corp. All Rights Reserved. 23

Slide 24

Slide 24 text

ExactSizeIterator pub trait Iterator { fn size_hint(&self) -> (usize, Option) { /* ... */ } } 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

Slide 25

Slide 25 text

DoubleEndedIterator pub trait DoubleEndedIterator: Iterator { fn next_back(&mut self) -> Option; // ... } 双方向イテレーターである場合に実装される。 つまり最後の要素を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

Slide 26

Slide 26 text

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

Slide 28

Slide 28 text

FromIterator Iterator から Vec を構築する let v: Vec = [1, 2, 3].iter().copied().collect(); println!("{:?}", v); // [1, 2, 3] Iterator から String を構築する let v: String = ['a', 'b', 'c'].iter().copied().collect(); println!("{:?}", v); // "abc" Iterator> から Result, u32> を構築する let v: Result, u32> = [Ok(1), Ok(2), Ok(3)].iter().copied().collect(); println!("{:?}", v); // Ok([1, 2, 3]) let v: Result, u32> = [Ok(1), Ok(2), Err(3)].iter().copied().collect(); println!("{:?}", v); // Err(3) Iterator> から PathBuf を構築する let v: std::path::PathBuf = ["a", "b", "c"].iter().collect(); println!("{}", v.display()); // "a/b/c" Copyright © OPTiM Corp. All Rights Reserved. 28

Slide 29

Slide 29 text

std::iter::empty pub const fn empty() -> Iterator 何も返さない、空のイテレーターを生成する。 let mut iter = std::iter::empty::(); 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

Slide 30

Slide 30 text

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

Slide 33

Slide 33 text

std::iter::successors pub fn successors(first: Option, succ: F) -> Iterator where F: FnMut(&T) -> Option 初期値からクロージャーを呼び出して値を計算し続けるイテレーターを生成する。 雰囲気は 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

Slide 34

Slide 34 text

意外なイテレーター Option と Result は IntoIterator を実装しているのでイテレーターとして扱える。 let mut v = vec![0u32]; v.extend(Some(1)); // 追加される v.extend(None::); // 追加されない v.extend(Ok::(2)); // 追加される v.extend(Err::(())); // 追加されない println!("{:?}", v); // [0, 1, 2] once と empty を条件で呼び変える代わりにも使える。 fn get1() -> impl Iterator { if rand::random::() { Box::new(std::iter::once(rand::random::())) as Box> } else { Box::new(std::iter::empty()) as Box> } } fn get2() -> impl Iterator { if rand::random::() { Some(rand::random()) } else { None }.into_iter() } Copyright © OPTiM Corp. All Rights Reserved. 34

Slide 35

Slide 35 text

イテレーターの作り方 Copyright © OPTiM Corp. All Rights Reserved. 35

Slide 36

Slide 36 text

Iteratorを実装する 最低限実装しなければならないアイテム pub trait Iterator { type Item; fn next(&mut self) -> Option; } 無限に値を返すイテレーターを作ってみる struct Hoge; impl Iterator for Hoge { type Item = u32; fn next(&mut self) -> Option { 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

Slide 37

Slide 37 text

カウントダウンするイテレーター struct CountDown(pub u32); impl Iterator for CountDown { type Item = u32; fn next(&mut self) -> Option { 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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

IntoIteratorを実装する(省力版) use std::{array, slice}; struct Array(pub [T; N]); impl Array { pub fn iter(&self) -> slice::Iter { self.0.iter() } pub fn iter_mut(&mut self) -> slice::IterMut { self.0.iter_mut() } } impl IntoIterator for Array { type Item = T; type IntoIter = array::IntoIter; fn into_iter(self) -> Self::IntoIter { IntoIterator::into_iter(self.0) } } impl<'a, T, const N: usize> IntoIterator for &'a Array { 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 { 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

Slide 40

Slide 40 text

さいごに Copyright © OPTiM Corp. All Rights Reserved. 40

Slide 41

Slide 41 text

イテレーターあるある イテレーターを使う時は便利系メソッドを見逃しがちで、 あとから「もっと簡単に書けたやんけ!」となりがちで落ち込みがち。 作る時は任意で実装するべきトレイト・メソッドがいくつもあって面倒がち。 でもそのおかげで高いパフォーマンスが実現しがち。 イテレーター色んな人が解説しがち。 イテレーターは正しく使って正しく作って良い感じのプログラムを作りましょう。 Copyright © OPTiM Corp. All Rights Reserved. 41

Slide 42

Slide 42 text

Q&A Copyright © OPTiM Corp. All Rights Reserved. 42

Slide 43

Slide 43 text

他の言語にあって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::()); // "a, b, c" Copyright © OPTiM Corp. All Rights Reserved. 43

Slide 44

Slide 44 text

オプションのイテレーター化 Q. オプションをイテレーターで使える件について、 C++にそれがあったらforで回せそうだがRustでそういう使い方はする? std::optional 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

Slide 45

Slide 45 text

非同期イテレーター Q. JavaScriptにある AsyncIterator のような仕組みがRustに導入される予定はあるか? A. futuresクレートに Stream トレイトがあり、現在はこれを使うことが出来る。 Rust標準にも、将来的には Stream 及びそれを使ったasync for文が導入されることが検討されている。 RFC 2996 Tracking Issue#79024 Copyright © OPTiM Corp. All Rights Reserved. 45

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Copyright © OPTiM Corp. All Rights Reserved. 47