イテレータのnext
メソッドを実装する。
成果物
参考
コード
impl<T> std::iter::Iterator for LinkedList<T> { type Item = T; fn next(&mut self) -> Option<Self::Item> { let mut first = std::mem::replace(&mut self.head, None); if first.is_none() { return None; } else { // 2番目ノードが存在するならそれを先頭にセット if first.as_mut().unwrap().next.is_some() { let second = std::mem::replace(&mut first.as_mut().unwrap().next, None); std::mem::replace(&mut self.head, second); } Some(first.unwrap().item) } } }
大きなポイントは以下。
next
メソッドは所有権をムーブするもの(リストから要素を削除し、戻り値として返す)T
でジェネリック型に対応する- 返す値は
T
にしたいNode
は隠蔽したいnext
メソッドの戻り値はOption
型にせねばならない
あとはいつものように、所有権ムーブ違反を回避するためにstd::mem::replace
関数を使っている。例によってここがキモであり混乱しやすい。
疑問
疑問1
if first.is_none() { return None; } else { // ... Some(first.unwrap().item) }
if
のときはreturn
がないと()
を返すのか成功しない。if first.is_none() { None }
にしたらダメだった。なのにelse
のほうはreturn
と;
なしの返却でOK。なぜ? なんか気持ち悪い。統一感ない。Rustってこういうものなの?
かといってif let
を使っても変わらない。また、末尾の;
を忘れそうだし違和感あるし面倒。代入式も兼ねているから;
使っているのかもしれないが。
if let None = first { return None; } else { // ... Some(first.unwrap().item) };
match
文を使うとreturn
と;
はどちらも不要になるが、ネストが深くなって嫌。あとアームごとの末尾にある,
忘れそうだし違和感だし面倒。
match first { None => None, _ => { // ... Some(first.unwrap().item) } }
疑問2
if first.is_none() { return first; }
のほうがいいのかな?
return Node;
としたら新しいインスタンスをわざわざ作ることになってメモリ浪費してないだろうか。でも、return first;
にするとそれ以前のコードを読まねばならならず端的にわかりにくい。うーん。
let mut first = std::mem::replace(&mut self.head, None); if first.is_none() { return None; }
大したメモリ量ではないだろうから、わかりやすさを重視した。果たして性能も重視するRustでこの判断は良いのか。
テストコード
#[test] fn LinkedList_next() { let mut list: LinkedList<i32> = LinkedList::new(); let a = list.next(); assert_eq!(list.next(), None); list.push(0); assert_eq!(list.next(), Some(0)); assert_eq!(list.next(), None); list.push(0); list.push(1); assert_eq!(list.next(), Some(0)); assert_eq!(list.next(), Some(1)); assert_eq!(list.next(), None); list.push(0); list.push(1); list.push(2); assert_eq!(list.next(), Some(0)); assert_eq!(list.next(), Some(1)); assert_eq!(list.next(), Some(2)); assert_eq!(list.next(), None); list.push(0); list.push(1); list.push(2); let expecteds = vec![0,1,2]; for expected in expecteds.iter() { assert_eq!(list.next(), Some(*expected)); } list.push(0); list.push(1); list.push(2); for (i, value) in expecteds.iter().enumerate() { assert_eq!(list.next(), Some(expecteds[i])); } list.push(0); list.push(1); list.push(2); for (i, value) in list.into_iter().enumerate() { assert_eq!(value, expecteds[i]); }
- 順番に取得できていること
next
するたびにその要素がリストから削除されていくこと(最後がNone
になること)for
文でも使えること
for
文はイテレータの使い方を知らねば使えなかった。勉強しつつ書いているのでテストとしては冗長な内容。でも理解が進んで満足。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
- rustc 1.34.2 (6c2484dc3 2019-05-13)
- cargo 1.34.0 (6789d8a0a 2019-04-01)
$ uname -a Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux