Push メソッド

リストに値をプッシュする操作を実装していきましょう. push はリストを変更するので,&mut self を引数に取ります. さらに,プッシュする値が必要なので i32 型の変数も引数に用意します.

impl List {
    pub fn push(&mut self, elem: i32) {
        // TODO
    }
}

まず最初に,要素を格納するためのノードを作ります:

    pub fn push(&mut self, elem: i32) {
        let new_node = Node {
            elem: elem,
            next: ?????
        };
    }

next の中身はなんでしょう? 以前のリスト全体ですね! これで…できるかな?

impl List {
    pub fn push(&mut self, elem: i32) {
        let new_node = Node {
            elem: elem,
            next: self.head,
        };
    }
}
> cargo build
error[E0507]: cannot move out of borrowed content
  --> src/first.rs:19:19
   |
19 |             next: self.head,
   |                   ^^^^^^^^^ cannot move out of borrowed content

あーもう!Rust は正しいことを言ってるんでしょうが,具体的に何を意味しているのか, どうすればいいのかわかりません:

cannot move out of borrowed content

借用中の値をムーブすることはできません

私たちは今 self.head フィールドを next にムーブしようとしましたが,それが Rust のお咎めを受けました. これでは,借用を終了して正当な所有者に「返す」ときに,部分的にしか初期化されていない self を返すことになります. 先に述べたように,&mut でこんなことはできません. Rust は非常に礼儀正しいからです.こんな失礼なことはあってはなりません. (びっくりするほど危険なことでもありますが,礼を失することに比べればとるに足りないことです).

ムーブされた部分を埋めてみたらどうでしょうか?つまり,いま作ったリストを返すわけです:

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

    self.head = Link::More(new_node);
}
> cargo build
error[E0507]: cannot move out of borrowed content
  --> src/first.rs:19:19
   |
19 |             next: self.head,
   |                   ^^^^^^^^^ cannot move out of borrowed content

ダメでした.実際には Rust はこれを受け入れてもよいのですが,(様々な理由 -- 最も深刻なのは例外安全性です -- により) 受け容れません. Rust に気づかれないように,head を取得する方法が必要なのです. そこで,悪名高き Rust ハッカー,インディアナ・ジョーンズに助言を求めましょう:

Indy Prepares to mem::replace

おっ,インディは mem::replace を使うことを提案していますね. この信じられないほど便利な関数を使えば,値を別の値で 置き換える ことで,借りた値を盗むことができます. mem を使用するためには,ファイルの先頭に std::mem を引っ張ってきたうえで:

use std::mem;

使うべきところで使えばよいです:

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

    self.head = Link::More(new_node);
}

ここでは self.head を一時的に Link::Emptyreplace してから,リストの新しい head に置き換えています. 率直に言って,これはかなり不満の残るコードです.しかし悲しいかな,そうせざるを得ないのですよ. 今のところはね.

でも,これで push メソッドが完成しました!たぶんだけど. 正直,テストをしないとわかりません. 今テストをする最も簡単な方法は,おそらく pop も書いて,正しい結果が得られるか確認することでしょう.