Peek

前回実装する気もなかった機能はpeek1です.これをやっていきましょう.やること といえば,リストのheadがあるときその参照を返せばいいだけです.簡単そうですね. やってみましょう:

pub fn peek(&self) -> Option<&T> {
    self.head.map(|node| {
        &node.elem
    })
}
> cargo build

error[E0515]: cannot return reference to local data `node.elem`
  --> src/second.rs:37:13
   |
37 |             &node.elem
   |             ^^^^^^^^^^ returns a reference to data owned by the current function

error[E0507]: cannot move out of borrowed content
  --> src/second.rs:36:9
   |
36 |         self.head.map(|node| {
   |         ^^^^^^^^^ cannot move out of borrowed content


ハァ〜今度はなんだよ?

mapはselfの値を取ってしまいますから,Optionの外に要素をムーブしてしまいます. 前回はtakeした直後だったのでそれでよかったのですが,今回は値を取っておきたいので だめです.これに対処する正しい方法はOptionが持つas_refメソッドを使うことです. as_refのシグネチャはこんな感じです:

impl<T> Option<T> {
    pub fn as_ref(&self) -> Option<&T>;
}

このメソッドはT型についてのOptionを,T型の参照についてのOptionに降格させます. これをmatchで自前実装することもできますが嫌ですね.それをやろうとすると Optionを剥がして詰め替えることをしなくてはいけません.幸いなことにそれは. 演算子がやってくれます.

pub fn peek(&self) -> Option<&T> {
    self.head.as_ref().map(|node| {
        &node.elem
    })
}
cargo build

    Finished dev [unoptimized + debuginfo] target(s) in 0.32s

はいバッチリ

これの可変バージョンをas_mutで作ることもできます:

pub fn peek_mut(&mut self) -> Option<&mut T> {
    self.head.as_mut().map(|node| {
        &mut node.elem
    })
}
lists::cargo build

楽勝か?

テストも忘れず書きましょう:

#[test]
fn peek() {
    let mut list = List::new();
    assert_eq!(list.peek(), None);
    assert_eq!(list.peek_mut(), None);
    list.push(1); list.push(2); list.push(3);

    assert_eq!(list.peek(), Some(&3));
    assert_eq!(list.peek_mut(), Some(&mut 3));
}
cargo test

     Running target/debug/lists-5c71138492ad4b4a

running 3 tests
test first::test::basics ... ok
test second::test::basics ... ok
test second::test::peek ... ok

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

いいですね.しかし,これではpeek_mutで取った値が本当に可変かどうか わかりませんよね?値が可変だとしても、誰も変更しなかったら可変性をテストしたと 本当に言えるでしょうか?返り値のOption<&mut T>mapを使って全然違う値を 突っ込んでみましょう:

#[test]
fn peek() {
    let mut list = List::new();
    assert_eq!(list.peek(), None);
    assert_eq!(list.peek_mut(), None);
    list.push(1); list.push(2); list.push(3);

    assert_eq!(list.peek(), Some(&3));
    assert_eq!(list.peek_mut(), Some(&mut 3));
    list.peek_mut().map(|&mut value| {
        value = 42
    });

    assert_eq!(list.peek(), Some(&42));
    assert_eq!(list.pop(), Some(42));
}
> cargo test

error[E0384]: cannot assign twice to immutable variable `value`
   --> src/second.rs:100:13
    |
99  |         list.peek_mut().map(|&mut value| {
    |                                   -----
    |                                   |
    |                                   first assignment to `value`
    |                                   help: make this binding mutable: `mut value`
100 |             value = 42
    |             ^^^^^^^^^^ cannot assign twice to immutable variable          ^~~~~

コンパイラはvalueが不変だと言って怒っています.でも明らかに&mut valueって 書いてますよね.どゆこと?実はこの書き方はクロージャの引数が可変参照であることを 指定していることにならないのです.そうではなく,引数に対してマッチするパターンを 指定していることになります.|&mut value|は「この引数は可変参照だけど,こいつの 値をコピーしてvalueに入れてね」という意味になります.|value|と書くことで valueの型を&mut i32にでき,headを変更できるようになります:

    #[test]
    fn peek() {
        let mut list = List::new();
        assert_eq!(list.peek(), None);
        assert_eq!(list.peek_mut(), None);
        list.push(1); list.push(2); list.push(3);

        assert_eq!(list.peek(), Some(&3));
        assert_eq!(list.peek_mut(), Some(&mut 3));

        list.peek_mut().map(|value| {
            *value = 42
        });

        assert_eq!(list.peek(), Some(&42));
        assert_eq!(list.pop(), Some(42));
    }
cargo test

     Running target/debug/lists-5c71138492ad4b4a

running 3 tests
test first::test::basics ... ok
test second::test::basics ... ok
test second::test::peek ... ok

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

かなり良くなりましたね!

1

訳注:スタックの一番上の要素をpopせずに取得する(覗き見る)機能