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

Peek

前回は、peek の実装にすら手を付けませんでした。では、やってしまいましょう。やるべきことは、リストの先頭にある要素への参照を、存在する場合に返すだけです。簡単そうですね。試してみましょう。

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


はぁ。今度は何なんですか、Rust?

Map は self を値で受け取るため、その中にあるものから Option をムーブしてしまいます。以前は、ちょうどそれを take した後だったので問題ありませんでしたが、今回は実際には元の場所に残しておきたいのです。これを処理する正しい方法は、Option の as_ref メソッドを使うことです。このメソッドは次のように定義されています。

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

これは Option<T> を、その内部への参照の Option に降格します。明示的な match を使えば自分たちでもこれを行えますが、うんざりですね。余分な間接参照をたどるために追加の参照外しが必要になる、ということではありますが、ありがたいことに . 演算子がそれを処理してくれます。

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
    })
}
> 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 と書いています。いったいどういうことでしょうか? 実は、クロージャの引数をこのように書いても、value が可変参照であることを指定したことにはなりません。代わりに、クロージャの引数に対してマッチされるパターンを作成します。|&mut value| は「引数は可変参照ですが、それが指している値を value にコピーしてください」という意味です。単に |value| を使えば、value の型は &mut i32 になり、実際に先頭をミューテートできます。

    #[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

ずっと良くなりました!