Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

基本

さて、ここは本書の中でもひどい部分で、この章を書くのに 7 年もかかった理由です! すでに 5 回はやった本当に退屈なことを大量に一気に片付ける時間です。ただし、すべてを 2 回ずつ、しかも Option<NonNull<Node<T>>> でやらなければならないので、やたら冗長で長くなります!

impl<T> LinkedList<T> {
    pub fn new() -> Self {
        Self {
            front: None,
            back: None,
            len: 0,
            _boo: PhantomData,
        }
    }
}

PhantomData はフィールドを持たない奇妙な型なので、型名を言うだけで作れます。肩すくめ

pub fn push_front(&mut self, elem: T) {
    // 安全性: リンクリストなんだから、何を求めてる?
    unsafe {
        let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
            front: None,
            back: None,
            elem,
        })));
        if let Some(old) = self.front {
            // 新しい先頭を古いものの前に置く
            (*old).front = Some(new);
            (*new).back = Some(old);
        } else {
            // 先頭がないなら、これは空リストなので back も
            // 設定する必要がある。ついでに、やらかした場合に備えて
            // テスト用の整合性チェックも入れておく。
            debug_assert!(self.back.is_none());
            debug_assert!(self.front.is_none());
            debug_assert!(self.len == 0);
            self.back = Some(new);
        }
        self.front = Some(new);
        self.len += 1;
    }
}
error[E0614]: type `NonNull<Node<T>>` cannot be dereferenced
  --> src\lib.rs:39:17
   |
39 |                 (*old).front = Some(new);
   |                 ^^^^^^

ああそうだ、私は本当にこのポインターっぽい子供たちが大嫌いです。as_ptr で NonNull から生ポインターを明示的に取り出す必要があります。なぜなら DerefMut は &mut を使って定義されていて、unsafe コードの中に安全な参照を無造作に持ち込みたくないからです!

            (*old.as_ptr()).front = Some(new);
            (*new.as_ptr()).back = Some(old);
   Compiling linked-list v0.0.3
warning: field is never read: `elem`
  --> src\lib.rs:16:5
   |
16 |     elem: T,
   |     ^^^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `linked-list` (lib) generated 1 warning (1 duplicate)
warning: `linked-list` (lib test) generated 1 warning
    Finished test [unoptimized + debuginfo] target(s) in 0.33s

いいですね。次は pop(と len)です。

pub fn pop_front(&mut self) -> Option<T> {
    unsafe {
        // pop する先頭ノードがある場合にだけ、何かをする必要がある。
        // もはや `take` をいじくり回す必要がないことに注意。
        // なぜなら、すべてが Copy であり、失敗しても実行される
        // デストラクタはないから……そうだよね? :) そーうだよね? :)))
        self.front.map(|node| {
            // Box を生き返らせて、その値を move し、
            // Drop できるようにする(Box は引き続きこれを魔法のように理解してくれる)。
            let boxed_node = Box::from_raw(node.as_ptr());
            let result = boxed_node.elem;

            // 次のノードを新しい先頭にする。
            self.front = boxed_node.back;
            if let Some(new) = self.front {
                // 削除されたノードへの参照を片付ける
                (*new.as_ptr()).front = None;
            } else {
                // 先頭が null になったなら、このリストは空になった!
                debug_assert!(self.len == 1);
                self.back = None;
            }

            self.len -= 1;
            result
            // ここで Box は暗黙的に解放され、T がないことを認識している。
        })
    }
}

pub fn len(&self) -> usize {
    self.len
}
   Compiling linked-list v0.0.3
    Finished dev [unoptimized + debuginfo] target(s) in 0.37s

私には妥当に見えます。テストを書く時間です!

#[cfg(test)]
mod test {
    use super::LinkedList;

    #[test]
    fn test_basic_front() {
        let mut list = LinkedList::new();

        // 空リストを壊そうとしてみる
        assert_eq!(list.len(), 0);
        assert_eq!(list.pop_front(), None);
        assert_eq!(list.len(), 0);

        // 要素が 1 つのリストを壊そうとしてみる
        list.push_front(10);
        assert_eq!(list.len(), 1);
        assert_eq!(list.pop_front(), Some(10));
        assert_eq!(list.len(), 0);
        assert_eq!(list.pop_front(), None);
        assert_eq!(list.len(), 0);

        // いじくり回す
        list.push_front(10);
        assert_eq!(list.len(), 1);
        list.push_front(20);
        assert_eq!(list.len(), 2);
        list.push_front(30);
        assert_eq!(list.len(), 3);
        assert_eq!(list.pop_front(), Some(30));
        assert_eq!(list.len(), 2);
        list.push_front(40);
        assert_eq!(list.len(), 3);
        assert_eq!(list.pop_front(), Some(40));
        assert_eq!(list.len(), 2);
        assert_eq!(list.pop_front(), Some(20));
        assert_eq!(list.len(), 1);
        assert_eq!(list.pop_front(), Some(10));
        assert_eq!(list.len(), 0);
        assert_eq!(list.pop_front(), None);
        assert_eq!(list.len(), 0);
        assert_eq!(list.pop_front(), None);
        assert_eq!(list.len(), 0);
    }
}
   Compiling linked-list v0.0.3
    Finished test [unoptimized + debuginfo] target(s) in 0.40s
     Running unittests src\lib.rs

running 1 test
test test::test_basic_front ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

やったー、私たちは完璧です!

……そうですよね?