こんなことを理解できないなんて!先が思いやられるわと思っている柴犬です。
また、この連結リストの作成方法は応用範囲が広そうで、今後役に立ちそうなテクニックと考え投稿します。
概要
RUST の列挙型について調べるために、RUSTの次の公式サイトを読みました。
3.2.3. テストケース: 連結リスト
https://doc.rust-jp.rs/rust-by-example-ja/custom_types/enum/testcase_linked_list.html
ここ中で書かれているコードが理解できず、さらにRUSTの列挙型の理解もなかなか難しく、ここ数日考えていました。
ここに至り自分なりの考えがまとまりましたので投稿します。
3.2.3. テストケース: 連結リスト
下のコードは、3.2.3. テストケース: 連結リストに記載されているコードから、先入観を除くためコメントを消去したものです。
use crate::List::*; enum List { Cons(u32, Box<List>), Nil, } impl List { fn new() -> List { Nil } fn prepend(self, elem: u32) -> List { Cons(elem, Box::new(self)) } fn len(&self) -> u32 { match *self { Cons(_, ref tail) => 1 + tail.len(), Nil => 0 } } fn stringify(&self) -> String { match *self { Cons(head, ref tail) => { format!("{} {}", head, tail.stringify()) }, Nil => { format!("Nil") }, } } } fn main() { let mut list = List::new(); list = list.prepend(1); list = list.prepend(2); list = list.prepend(3); println!("linked list has length: {}", list.len()); println!("{}", list.stringify()); }
自分の理解
以下は、自分の理解をまとめたものです。
最初、len() をリストの len() に惑わされ tail をリストと誤解したので、この誤解から脱却するのにかなりの時間を費やさせられました。
何度も読み返しここで理解したことは、tail.len() は単に列挙子のメソッドを実行しているだけだということです。
そして、列挙体 List のメッソド fn len と fn stringify は再帰をして、再帰の終了条件は Nil であることです。
fn len(&self) -> u32 {
match *self {
// 実行するごとに 1 を加算
// `tail.len()` === `List::len(&tail)`
Cons(_, ref tail) => 1 + tail.len(),
// 終了条件 0 を加算
Nil => 0
}
}
fn stringify(&self) -> String {
match *self {
// 実行するごとに head(elem)を書き出す
Cons(head, ref tail) => {
// `tail.stringify()` === `List::stringify(&tail)`
format!("{} {}", head, tail.stringify())
},
// 終了条件 Nil を書き出して終了
Nil => {
format!("Nil")
},
}
}
list は列挙子 Nil、Cons を順に格納して、最終的に List型 Cons(3, X2) であることを理解しました。
// list は List型 Nil を格納しました。
let mut list = List::new();
// X0 は List型 Nil をヒープ領域に作成したアドレス
// アドレスX0を格納する List型 Cons(1, X0) をlist に格納します。
list = list.prepend(1);
// X1 は List型 Cons(1, X0) をヒープ領域に作成したアドレス
// アドレスX1を格納する List型 Cons(2, X1) をlist に格納します。
list = list.prepend(2);
// X2 は List型 Cons(2, X1) をヒープ領域に作成したアドレス
// アドレスX2を格納する List型 Cons(3, X2) をlist に格納します。
list = list.prepend(3);
list は Cons(3, X2) で再帰的に Cons(2, X1)、 Cons(1, X0)、 Nil と遡っていき、Nil でストップします。
println!("linked list has length: {}", list.len());
println!("{}", list.stringify());
List を Rist に置き換えてみた
リストの呪縛から逃れるために、List の名称を変えることにします。
トップの use 句を除いて次のように List を Rist に置き換えてみました。
use crate::List::*;
enum Rist {
Cons(u32, Box<Rist>),
Nil,
}
impl Rist {
fn new() -> Rist {
Nil
}
fn prepend(self, elem: u32) -> Rist {
Cons(elem, Box::new(self))
}
fn len(&self) -> u32 {
match *self {
Cons(_, ref tail) => 1 + tail.len(),
Nil => 0
}
}
fn stringify(&self) -> String {
match *self {
Cons(head, ref tail) => {
format!("{} {}", head, tail.stringify())
},
Nil => {
format!("Nil")
},
}
}
}
fn main() {
let mut list = Rist::new();
list = list.prepend(1);
list = list.prepend(2);
list = list.prepend(3);
println!("linked list has length: {}", list.len());
println!("{}", list.stringify());
}
エラー発生
上のコードを実行してみましたところ、次のとおりエラーになりました。
use crate::Rist::Cons;
とインフォメーションがあるので、トップも Rist に変えてみました。
use crate::List::*;
↓
use crate::Rist::*;
無事実行できました。
疑問
ここで分からないことがまた発生しました。それは、 use crate::Rist(use crate::List) は何者かということです。
これは、今後の課題とします。
全コード
最後に、実行できた全コードです。
use crate::Rist::*; enum Rist { Cons(u32, Box<Rist>), Nil, } impl Rist { fn new() -> Rist { Nil } fn prepend(self, elem: u32) -> Rist { Cons(elem, Box::new(self)) } fn len(&self) -> u32 { match *self { Cons(_, ref tail) => 1 + tail.len(), Nil => 0 } } fn stringify(&self) -> String { match *self { Cons(head, ref tail) => { format!("{} {}", head, tail.stringify()) }, Nil => { format!("Nil") }, } } } fn main() { let mut list = Rist::new(); list = list.prepend(1); list = list.prepend(2); list = list.prepend(3); println!("linked list has length: {}", list.len()); println!("{}", list.stringify()); }