Option を使う
特に観察眼の鋭い読者は、私たちが実際には Option のかなりひどいバージョンを再発明していたことに気づいたかもしれません。
enum Link {
Empty,
More(Box<Node>),
}
Link は単なる Option<Box<Node>> です。確かに、あちこちに
Option<Box<Node>> と書かずに済むのはよいことですし、pop とは違って、これを
外部に公開しているわけでもないので、問題ないかもしれません。しかし Option には、
私たちが手作業で実装してきたものの代わりになる、本当に素晴らしいメソッドがいくつかあります。それを自分たちで実装するのはやめて、
すべてを Options に置き換えましょう。まずは単純に、すべての名前を変更して 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 を書いて
それを map に渡すこともできますが、どうするかをインラインで書くほうがずっと好ましいです。
これを行う方法がクロージャです。クロージャは無名関数であり、
追加のすごい能力を持っています。それは、クロージャの外側にあるローカル変数を参照できることです!
これにより、あらゆる種類の条件付きロジックを書くのに非常に便利になります。
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
素晴らしい!それでは、コードの振る舞いを実際に改善する作業に進みましょう。