Pop
push
同様,pop
もリストに変更を加えますが,push
と違い返り値があります.
そしてリストが空の場合という厄介な状態を考えなくてはいけません.この
ケースに対処するために,信頼できるOption
型を使います:
pub fn pop(&mut self) -> Option<i32> {
// TODO
}
Option<T>
は値が存在するかもしれないことを表すのに使われ,Some(T)
かNone
の状態を取ることができます.Linkのときやったように似たような型を自作することも
できますが,このリストを使う人が戻り値の型が一体全体何なのか考えなくていいように,
Optionという知らない人がいないほどありふれてる型を使います.実際あまりにも
基礎的なので何も書かなくてもSome
とNone
と一緒にすべての.rsファイルに
インポートされています(なのでOption::None
とか書かなくてもいいのです).
Option<T>
のとげとげはOptionがTのジェネリック型であることを表しています.
どんな型のOptionも作ることができるのです!
はい,で,このLink
とかいうのがあるわけですが,どうやってこれがEmptyかMoreか
判断するのでしょうか?match
を使ったパターンマッチングです!
pub fn pop(&mut self) -> Option<i32> {
match self.head {
Link::Empty => {
// TODO
}
Link::More(node) => {
// TODO
}
};
}
> cargo build
error[E0308]: mismatched types
--> src/first.rs:27:30
|
27 | pub fn pop(&mut self) -> Option<i32> {
| --- ^^^^^^^^^^^ expected enum `std::option::Option`, found ()
| |
| this function's body doesn't return
|
= note: expected type `std::option::Option<i32>`
found type `()`
おおおっと.pop
は値を返さなくてはいけませんが,まだ実装してませんでした.
None
を返すこともできますが,このような場合は関数がまだ未実装であることを
表すunimplemented!()
を返すのがよさそうです.unimplemented!()
はマクロで
(!
がマクロであることを表しています),実行するとプログラムがパニックします
(制御下でクラッシュさせます).
pub fn pop(&mut self) -> Option<i32> {
match self.head {
Link::Empty => {
// TODO
}
Link::More(node) => {
// TODO
}
};
unimplemented!()
}
無条件でパニックするような関数は発散する関数と呼ばれます.
発散する関数は値を返さず,したがってどんな型の値としても使うことが
できます.ここではunimplemented!()
の返り値をOption<T>
として使っています.
return
を書かなくていいことにも注目してください.最後の表現(基本的には行)
が暗黙的に関数の返り値になります.これでシンプルな関数をより簡潔に書くことが
できるのです.ほかのCライクな言語のように,明示的にreturn
を使って
早期リターンすることもできます.
> cargo build
error[E0507]: cannot move out of borrowed content
--> src/first.rs:28:15
|
28 | match self.head {
| ^^^^^^^^^
| |
| cannot move out of borrowed content
| help: consider borrowing here: `&self.head`
...
32 | Link::More(node) => {
| ---- data moved here
|
note: move occurs because `node` has type `std::boxed::Box<first::Node>`, which does not implement the `Copy` trait
--> src/first.rs:32:24
|
32 | Link::More(node) => {
| ^^^^
頼むぞRust,邪魔しないでくれ!いつもどおりRustは激おこです.ありがたいことに 今回は何がいけないのか全部言ってくれています!デフォルトでは,パターンマッチを するときmatch節への値のムーブが発生します.しかし今回はself.headの所有権を 持っていないためそれはできません.
help: consider borrowing here: `&self.head`
Rustはmatch
節で参照を使えと言ってます.🤷♀️まあ試してみましょう:
pub fn pop(&mut self) -> Option<i32> {
match &self.head {
Link::Empty => {
// TODO
}
Link::More(ref node) => {
// TODO
}
};
unimplemented!()
}
> cargo build
warning: unused variable: `node`
--> src/first.rs:32:24
|
32 | Link::More(node) => {
| ^^^^ help: consider prefixing with an underscore: `_node`
|
= note: #[warn(unused_variables)] on by default
warning: field is never used: `elem`
--> src/first.rs:13:5
|
13 | elem: i32,
| ^^^^^^^^^
|
= note: #[warn(dead_code)] on by default
warning: field is never used: `next`
--> src/first.rs:14:5
|
14 | next: Link,
| ^^^^^^^^^^
イエーイ,コンパイルが通りました!ではTODOに入る処理を考えていきましょう.
Optionを返したいので,そのための変数を作りましょう.EmptyのときにはNoneを
返せばいいですね.MoreのときはSome(i32)
を返し,Listのheadを更新します.
じゃあとりあえずはこんな感じでどうでしょうか?
pub fn pop(&mut self) -> Option<i32> {
let result;
match &self.head {
Link::Empty => {
result = None;
}
Link::More(ref node) => {
result = Some(node.elem);
self.head = node.next;
}
};
result
}
> cargo build
Compiling lists v0.1.0 (/Users/ABeingessner/dev/temp/lists)
error[E0507]: cannot move out of borrowed content
--> src/first.rs:35:29
|
35 | self.head = node.next;
| ^^^^^^^^^ cannot move out of borrowed content
(頭を机に伏せる)
共有参照しか持ってないnode
から値を取ろうとしているのがよくないようです.
ここは一旦私達が何をしようとしているのか考え直すべきでしょう.私達がやりたいのは こういうことです:
- リストが空か確認する
- もし空ならNoneを返す
- もし空でないなら...
- リストのheadを消す
- headの
elem
を消す - リストのheadを古いheadの
next
に更新する Some(elem)
を返す
重要な点は私達がなにかを消そうとしている点です.つまり,リストのheadの値を
持っている必要があります.これは明らかに&self.head
の共有参照を使っていては
達成できません.そもそもこの関数はself
の可変参照しか持っておらず,
入れ替えることでしか値を取ることができません.これはまたEmptyの出番のよう
ですね!
試してみましょう:
pub fn pop(&mut self) -> Option<i32> {
let result;
match mem::replace(&mut self.head, Link::Empty) {
Link::Empty => {
result = None;
}
Link::More(node) => {
result = Some(node.elem);
self.head = node.next;
}
};
result
}
cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
や っ た ぜ
一つの警告もなくコンパイルしました!!!!!
ちょっとここで個人的なlintを行いたいと思います.私達はresult
を返り値に
していますが,実はそんなことをする必要はないのです!関数がその最後の行を
返すように,全ての節もその最後の行を返すのです.普通はセミコロンをつけることで
空タプル()
を返すようにします.返り値を定義していない関数(push
みたいな)も
空タプルを返します.
そんなわけで,pop
はこんなふうに書けます:
pub fn pop(&mut self) -> Option<i32> {
match mem::replace(&mut self.head, Link::Empty) {
Link::Empty => None,
Link::More(node) => {
self.head = node.next;
Some(node.elem)
}
}
}
この方が簡潔で慣用的です.Link::Emptyのところにカッコがないことに注目してください. 一つしか表現がないシンプルな場合にはこのように書くことができます.
cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
よしよし,これでも動きますね!