やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rust自習(連結リスト5)

 イテレータ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文はイテレータの使い方を知らねば使えなかった。勉強しつつ書いているのでテストとしては冗長な内容。でも理解が進んで満足。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで