設計
で,片方向キューとは何でしょうか?片方向スタックのとき,私達はリストの最後にpushして 同じ方向からpopしました.キューとスタックの違いはpopするのが逆側からであることだけです. スタックのときはこんな感じでした:
input list:
[Some(ptr)] -> (A, Some(ptr)) -> (B, None)
stack push X:
[Some(ptr)] -> (X, Some(ptr)) -> (A, Some(ptr)) -> (B, None)
stack pop:
[Some(ptr)] -> (A, Some(ptr)) -> (B, None)
キューを作るには,popとpushのどちらでリストの最後の要素を操作するか決めなくては いけません.片方向リストなのでどっちにしろ同程度の計算量になります.
push
を最後にするなら,リストをNone
が出るまでたどり新しい要素が入ったSomeに
入れ替えます.
input list:
[Some(ptr)] -> (A, Some(ptr)) -> (B, None)
flipped push X:
[Some(ptr)] -> (A, Some(ptr)) -> (B, Some(ptr)) -> (X, None)
pop
を最後にするなら,リストをNone
が出る直前までたどりtake
します.
input list:
[Some(ptr)] -> (A, Some(ptr)) -> (B, Some(ptr)) -> (X, None)
flipped pop:
[Some(ptr)] -> (A, Some(ptr)) -> (B, None)
これのどちらかをやって完成でもいいですが,これではお粗末です!どちらにせよリストの 全体をたどる必要があります.正しいインターフェースを提供しているのでキューの実装としては これでいいという人もいるでしょう.ですが私はパフォーマンスを保証することもインターフェースの 一部であると考えます.漸近的境界がどうのという話ではなく速いか遅いかです.キューは popとpushが速いことを保証するデータ構造であり,リスト全体をたどる操作は明らかに速くは ありません.
ひとつの重要な視点は,同じことを何回も繰り返してリソースを無駄にしていることです. この操作を記憶できないでしょうか?なんと,できます!リストの末尾へのポインタを持ち, そこに直接行けばいいのです!
リストの末尾を使う操作はpush
とpop
のどちらかで大丈夫です.pop
で使う場合
末尾へのポインタを逆向きにたどる必要がありますが,片方向リストの性質上これを
効率的に行うのは困難です.push
で使うようにすれば,popするときはリストの先頭の
ポインタを順方向にたどればいいので楽ですね.
試してみましょう:
use std::mem;
pub struct List<T> {
head: Link<T>,
tail: Link<T>, // NEW!
}
type Link<T> = Option<Box<Node<T>>>;
struct Node<T> {
elem: T,
next: Link<T>,
}
impl<T> List<T> {
pub fn new() -> Self {
List { head: None, tail: None }
}
pub fn push(&mut self, elem: T) {
let new_tail = Box::new(Node {
elem: elem,
// pushしたときのnextは常にNone
next: None,
});
// 古いtailを新しいtailに更新
let old_tail = mem::replace(&mut self.tail, Some(new_tail));
match old_tail {
Some(mut old_tail) => {
// もしtailがすでにあれば新しいtailを入れる
old_tail.next = Some(new_tail);
}
None => {
// そうでなければheadに入れる
self.head = Some(new_tail);
}
}
}
}
この手の操作を書くのはかなり慣れてきていると思うので実装のペースをすこし上げていきます.
とはいえ上のようなコードを一発で書き上げる必要はありません.今まであったような
トライアンドエラーをとばして書いているだけです.実際私はこのコードを書くにあたって
めちゃくちゃ間違えてます.私が出したエラーを全部見せていたらmut
や;
の書き忘れが
あまりにも多く教本の体をなしていないでしょう.でも心配しないでください.これから
別のエラーメッセージを沢山見ることになります!
> cargo build
error[E0382]: use of moved value: `new_tail`
--> src/fifth.rs:38:38
|
26 | let new_tail = Box::new(Node {
| -------- move occurs because `new_tail` has type `std::boxed::Box<fifth::Node<T>>`, which does not implement the `Copy` trait
...
33 | let old_tail = mem::replace(&mut self.tail, Some(new_tail));
| -------- value moved here
...
38 | old_tail.next = Some(new_tail);
| ^^^^^^^^ value used here after move
クソ!
use of moved value:
new_tail
BoxはCopyを実装していないので2箇所に入れることはできません.更に重要なことはBoxは
中身の所有権を持つのでBoxがdropされたとき中身を解放しようとする点です.もしこの
実装がコンパイルしてしまったらpush
するたびにリストの末尾を2回解放することになります.
というか実際私達のコードはpushのたびにold_tailを解放しています!ギエー!🙀
OK,でも私達は所有権を持たないポインタがなにか知っています.ふつうの参照です!
pub struct List<T> {
head: Link<T>,
tail: Option<&mut Node<T>>, // NEW!
}
type Link<T> = Option<Box<Node<T>>>;
struct Node<T> {
elem: T,
next: Link<T>,
}
impl<T> List<T> {
pub fn new() -> Self {
List { head: None, tail: None }
}
pub fn push(&mut self, elem: T) {
let new_tail = Box::new(Node {
elem: elem,
// pushしたときのnextは常にNone
next: None,
});
// Put the box in the right place, and then grab a reference to its Node
let new_tail = match self.tail.take() {
Some(old_tail) => {
// もしtailがすでにあれば新しいtailを入れる
old_tail.next = Some(new_tail);
old_tail.next.as_mut().map(|node| &mut **node)
}
None => {
// そうでなければheadに入れる
self.head = Some(new_tail);
self.head.as_mut().map(|node| &mut **node)
}
};
self.tail = new_tail;
}
}
特にトリッキーなことはしていません.基本的な発想はさっきと同じで,違いは暗黙のリターン を使ってnew_tailを作っていることくらいです.
> cargo build
error[E0106]: missing lifetime specifier
--> src/fifth.rs:3:18
|
3 | tail: Option<&mut Node<T>>, // NEW!
| ^ expected lifetime parameter
ああそうでした.ライフタイムを与えなくてはいけません.うーん...ここのライフタイムは
何でしょう?えっと,これってIterMutに似てますよね?IterMutと同じことをして,
パラメータ名は'a
を使いましょう:
pub struct List<'a, T> {
head: Link<T>,
tail: Option<&'a mut Node<T>>, // NEW!
}
type Link<T> = Option<Box<Node<T>>>;
struct Node<T> {
elem: T,
next: Link<T>,
}
impl<'a, T> List<'a, T> {
pub fn new() -> Self {
List { head: None, tail: None }
}
pub fn push(&mut self, elem: T) {
let new_tail = Box::new(Node {
elem: elem,
// pushしたときのnextは常にNone
next: None,
});
// Put the box in the right place, and then grab a reference to its Node
let new_tail = match self.tail.take() {
Some(old_tail) => {
// もしtailがすでにあれば新しいtailを入れる
old_tail.next = Some(new_tail);
old_tail.next.as_mut().map(|node| &mut **node)
}
None => {
// そうでなければheadに入れる
self.head = Some(new_tail);
self.head.as_mut().map(|node| &mut **node)
}
};
self.tail = new_tail;
}
}
cargo build
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> src/fifth.rs:35:27
|
35 | self.head.as_mut().map(|node| &mut **node)
| ^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 18:5...
--> src/fifth.rs:18:5
|
18 | / pub fn push(&mut self, elem: T) {
19 | | let new_tail = Box::new(Node {
20 | | elem: elem,
21 | | // pushしたときのnextは常にNone
... |
39 | | self.tail = new_tail;
40 | | }
| |_____^
note: ...so that reference does not outlive borrowed content
--> src/fifth.rs:35:17
|
35 | self.head.as_mut().map(|node| &mut **node)
| ^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 13:6...
--> src/fifth.rs:13:6
|
13 | impl<'a, T> List<'a, T> {
| ^^
= note: ...so that the expression is assignable:
expected std::option::Option<&'a mut fifth::Node<T>>
found std::option::Option<&mut fifth::Node<T>>
わー,これはずいぶん詳細なエラーメッセージですね.これはすこし憂慮すべきエラーです. というのもこれによると私達はかなりめちゃくちゃなことをやっているからです.興味深いのは 部分です:
the lifetime must be valid for the lifetime
'a
as defined on the impl
self
を借用しているわけですが,コンパイラは少なくとも'a
だけ生存して欲しがっています.
ではself
が実際'a
だけ生存すると示せばどうでしょう...?
pub fn push(&'a mut self, elem: T) {
cargo build
warning: field is never used: `elem`
--> src/fifth.rs:9:5
|
9 | elem: T,
| ^^^^^^^
|
= note: #[warn(dead_code)] on by default
見てください,動きました!やった!
pop
も同様に実装します:
pub fn pop(&'a mut self) -> Option<T> {
// リストの今のheadを取る
self.head.take().map(|head| {
let head = *head;
self.head = head.next;
// もし`head`がなかったらtailに`None`を入れる
if self.head.is_none() {
self.tail = None;
}
head.elem
})
}
そしてちゃちゃっとテストを書いてしまいましょう:
mod test {
use super::List;
#[test]
fn basics() {
let mut list = List::new();
// 空のリストが動くことを確認
assert_eq!(list.pop(), None);
// リストの要素をつめる
list.push(1);
list.push(2);
list.push(3);
// 普通に要素を削除してみる
assert_eq!(list.pop(), Some(1));
assert_eq!(list.pop(), Some(2));
// 何も壊れてないことを確認するためにもう一回push
list.push(4);
list.push(5);
// 普通に要素を削除してみる
assert_eq!(list.pop(), Some(3));
assert_eq!(list.pop(), Some(4));
// リストを出し切ったとき
assert_eq!(list.pop(), Some(5));
assert_eq!(list.pop(), None);
}
}
cargo test
error[E0499]: cannot borrow `list` as mutable more than once at a time
--> src/fifth.rs:68:9
|
65 | assert_eq!(list.pop(), None);
| ---- first mutable borrow occurs here
...
68 | list.push(1);
| ^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
error[E0499]: cannot borrow `list` as mutable more than once at a time
--> src/fifth.rs:69:9
|
65 | assert_eq!(list.pop(), None);
| ---- first mutable borrow occurs here
...
69 | list.push(2);
| ^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
error[E0499]: cannot borrow `list` as mutable more than once at a time
--> src/fifth.rs:70:9
|
65 | assert_eq!(list.pop(), None);
| ---- first mutable borrow occurs here
...
70 | list.push(3);
| ^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
....
** まだまだあるエラー **
....
error: aborting due to 11 previous errors
🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀🙀
何ということでしょう.
コンパイラは悪くありません.私達はたった今Rustの原罪1を犯したのです.私達は
自分への参照を自分自身の中に持ってしまいました.push
とpop
の実装では
なんとかRustを言いくるめることができました(それができたのは衝撃的でしたが).
私はRustがpush
とpop
を見た時点では参照がそれ自身の中にあるかどうか
分からなかったんだと思いますが--というか,Rustにはそういう概念がありません.
自分自身への参照が動作しないのは単なる現象に過ぎず,根本的な問題ではありません.
私達のリストは使おうとした途端全てが空中分解します.push
やpop
を呼んだ瞬間
リストは自分への参照を持ち,詰みます.自分自身を借用しているのです.
pop
の実装を見るとなぜこれが危険であるかわかります:
// ...
if self.head.is_none() {
self.tail = None;
}
もし私達がこれを忘れたらどうなっていたでしょう?リストのtailはリストから除外された ノードを指すことになります.リストから除外されたノードのメモリは解放され,Rust が防いでくれるはずのダングリングポインタが生まれてしまいます!
Rustはたしかにそういう危険から私達を守ってくれているのです.ただかなり... 回りくどいやりかたですが.
ではどうしたらいいでしょうか?またRc<RefCell>>
地獄に戻る?
だめです.勘弁してください.
かわりに敷かれたレールを外れて生のポインタを使います.私達のリストはこんな感じに なるでしょう:
pub struct List<T> {
head: Link<T>,
tail: *mut Node<T>, // DANGER DANGER
}
type Link<T> = Option<Box<Node<T>>>;
struct Node<T> {
elem: T,
next: Link<T>,
}
そしてこれが全てです.もうクソ雑魚参照カウント付動的借用チェックには頼りません! 本物の,過酷な,未検査の,ポインタを使います.
みなさん,C言語でいきましょう.いつも,いつでもCで.
私はここに帰ってきました.もう準備はできてます.
Hello, unsafe
.
訳注:Lust(色欲)とかかっている