Optionを使う

特別目ざとい人は私達が劣化Optionを再発明したことに気づいたかもしれません:

enum Link {
    Empty,
    More(Box<Node>),
}

LinkはOption<Box<Node>>と同じことです.かといってOption<Box<Node>>を どこにでも書くのは気が引けますし,popの戻り値とは違いLinkは外部から 見ることはできないので,これをLinkと呼び続けることは問題なさそうです. しかし,Optionには超いいメソッドがいくつもあり,Optionを使えば (私達がやったように)それらを自力で実装することもありません.なので 全部Optionを使いましょう.まずは愚直に全部のMoreとEmptyをSomeとNoneに 書き換えていきましょう:

use std::mem;

pub struct List {
    head: Link,
}

// 型エイリアスです!イエーイ!
type Link = Option<Box<Node>>;

struct Node {
    elem: i32,
    next: Link,
}

impl List {
    pub fn new() -> Self {
        List { head: None }
    }

    pub fn push(&mut self, elem: i32) {
        let new_node = Box::new(Node {
            elem: elem,
            next: mem::replace(&mut self.head, None),
        });

        self.head = Some(new_node);
    }

    pub fn pop(&mut self) -> Option<i32> {
        match mem::replace(&mut self.head, None) {
            None => None,
            Some(node) => {
                self.head = node.next;
                Some(node.elem)
            }
        }
    }
}

impl Drop for List {
    fn drop(&mut self) {
        let mut cur_link = mem::replace(&mut self.head, None);
        while let Some(mut boxed_node) = cur_link {
            cur_link = mem::replace(&mut boxed_node.next, None);
        }
    }
}

これは若干良くなった程度ですが,さらなる良さがOptionのメソッドを使うことで 得られます.

まずmem::replace(&mut option, None)はめっちゃよくある形で, あまりにもよくあるのでOptionにはそのためのtakeというメソッドがあります.

pub struct List {
    head: Link,
}

type Link = Option<Box<Node>>;

struct Node {
    elem: i32,
    next: Link,
}

impl List {
    pub fn new() -> Self {
        List { head: None }
    }

    pub fn push(&mut self, elem: i32) {
        let new_node = Box::new(Node {
            elem: elem,
            next: self.head.take(),
        });

        self.head = Some(new_node);
    }

    pub fn pop(&mut self) -> Option<i32> {
        match self.head.take() {
            None => None,
            Some(node) => {
                self.head = node.next;
                Some(node.elem)
            }
        }
    }
}

impl Drop for List {
    fn drop(&mut self) {
        let mut cur_link = self.head.take();
        while let Some(mut boxed_node) = cur_link {
            cur_link = boxed_node.next.take();
        }
    }
}

次にmatch option { None => None, Some(x) => Some(y) }もめっちゃよくある形で, あまりにもよくあるのでmapというメソッドがあります.mapにはSome(x)x からSome(y)yを作る関数を渡す必要があります.fnで関数を宣言して渡す のもいいですが,インラインで書くほうがいいでしょう.

そのためにはクロージャを使います.クロージャは,クロージャ外のローカル変数を 参照することができる無名関数です.この強力な機能によって,条件に依存する処理を 超簡単に書くことができます.matchを使っているのはpopだけですね.書き換えて みましょう:

pub fn pop(&mut self) -> Option<i32> {
    self.head.take().map(|node| {
        self.head = node.next;
        node.elem
    })
}

アーイイ.今回の変更で何もぶっ壊れてないことを確認しましょう:

> cargo test

     Running target/debug/lists-5c71138492ad4b4a

running 2 tests
test first::test::basics ... ok
test second::test::basics ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured

いいですね!次はコードの挙動を改善していきましょう!