🍣

Tour of Rust 3章を勉強した [学習記録]

に公開

Rustを学習できるwebアプリ、Tour of Rustをつかって最近勉強しています
すこしづつやっていまして、今日で3章終わりまでいけました!
ということで、簡単な学習記録をつけようと思います
Rustを一緒に勉強している皆さんの役にたてればと思います

※この記事は、「Tour of Rust 第3章終わったから自分でコード書いてみた」的な内容になっています。Tour of Rustの練習問題は出てきません

0. Tour of Rustって?

Tour of Rust とは、プログラミング言語 Rust の機能を段階的に学べる対話的なチュートリアルウェブサイトです
https://tourofrust.com/30_ja.html

この記事は、Tour of Rust 3章最後、「データを持つ列挙型」のセクションの内容を扱います

コードはコーヒー屋さん

Tour of Rust 3章はenumstructを解説する章です
せっかくなので、それらを使ったコーヒー屋さんのようなコード(?)を作りました

店員さんに渡す、注文用紙(伝票)を作るイメージです
「想定されるオーダー」をずべて受けきれるものでなければなりません
matchを使った条件分岐で、全ての可能性を網羅するようにします

コードの全体像

まずは完成したコードから。
※練習用のため使っていないコードがあることに注意してください

#![allow(dead_code)] // この行でコンパイラのwaringsメッセージを止めます

enum MySize { Short, Medium, Large, Order(u32) } // order=height[cm]
enum CType { Espresso, Cappuccino, American, Order(String) }
enum Sugar { Little, Lots, None, Order(u32) } // order=角砂糖の個数
enum Chara { Pikachu, Achamo, Mikalge, None }
enum Color { Green, Blue, Red, None }
struct Trend {
    chara: Chara,
    color: Color,
}

struct Coffee {
    mysize: MySize,
    ctype: CType,
    sugar: Sugar,
    trend: Trend
}

fn main() {
    let my_coffee = Coffee {
        mysize: MySize::Short,
        ctype: CType::Cappuccino,
        sugar: Sugar::None,
        trend: Trend { chara: Chara::Pikachu, color: Color::Blue }
    };

    let yourcoffee = Coffee {
        mysize: MySize::Order(20),
        ctype: CType::Order("cafelate".to_string()),
        sugar: Sugar::Order(4),
        trend: Trend { chara: Chara::Achamo, color: Color::Red }
    };

    let size_string: String = match my_coffee.mysize {
        MySize::Short => "ショートサイズ".to_string(),
        MySize::Medium => "ミディアムサイズ".to_string(),
        MySize::Large => "ラージサイズ".to_string(),
        MySize::Order(height) => format!("オーダーサイズ {}", height)
    };
    
    println!("{} is selected!", size_string);
} 

キーワードの紹介です
enumは選択肢を列挙します。structは、いわゆるオブジェクトで、キーと値が入ります。各項目はフィールドというのだとか
どちらもデータを受け取る準備をしているのであって、まだ実データは入ってきません。箱を作る作業をします

コードの解説(前半)

まずはmain関数の外、グローバル領域の解説をします
流れとしては、最初にenumで選択肢を定義し、次のstructで構造体、つまり注文をいれる箱を作るかたちです

enumについて

enumの文法は、enum MySize { Short, Medium, Large } だけではありません
enumの各要素はバリアントと呼ばれますが、3つの書き方が存在しています

enumの要素、3つの書き方

Rustでは、enumの各要素、つまり選択肢のことをバリアントと呼びます
ここには、普通に選択肢を列挙できるし、enumやstructをさらにネストして埋め込むようなことだって可能です

structは、JavaScriptでいうオブジェクトですね
完全にイコールではないけど、いろんな言語のオブジェクトが、Rustではstructに相当するらしいです

そして結論からいってしまいますが、enumの選択肢、バリアントには以下3つの種類があって、

  • 普通に列挙
  • タプル埋め込み
  • struct埋め込み

こんなふうにガラパゴス的にまぜて使うことが可能です↓

enum {
    Choice1,  // 普通に列挙
    Choice(u32),  // タプル埋め込み
    StructChoice {key: value, key2: value2}  //struct(オブジェクト)埋め込み
}

※「タプル埋め込み=enum埋め込み」というわけではないみたい。難しい。

詳しく見ていきましょう。↓

1. ユニットバリアント(データを持たない。普通の選択肢)

enum MySize { Short, Medium }
//             ↑        ↑
//         バリアント1 バリアント2

2. タプルバリアント(名前なしのデータをもつ。タプル埋め込み)

enum MySize { order1(u32), order2(u32) }
// 名前(キー)を持たず、Valueだけ

3. 構造体バリアント(名前付きのデータを持つ。struct埋め込み)

enum MySize {
    Custom { width: u32, height: u32 }
}
// "Custom:"としない。enumの唯一のセパレータは","でないとエラーがでる

「えっ、structとかenumって単語を中につけなくていいの?」と思うかもしれません
私も思いました
でも(){}で自明だから、書かなくてもコンパイラーに伝わるらしいんです。
すごいですよね

// 間違ったコード
// こうenum enumしなくてい
enum Sample {
    Choice1,
    enum Choice(u32),
    struct {...} 
}

structの解説

struct Trend {
    chara: Chara,
    color: Color,
}

struct Coffee {
    mysize: MySize,
    ctype: CType,
    sugar: Sugar,
    trend: Trend
}

まずは2つ目のstructからお話します。こっちが本丸。これが注文用紙です
キャラと色、これらは「選択肢」ではなく「全部使うデータ」なのでstructで定義しました

main処理 - 実データ作成

構造体はあくまで「注文用紙」
それ自体はなんのオーダー情報も入っていません
なので注文を取るときは構造体を使って、実際の注文データを構築します

実際にデータを作る1

let my_Coffee = Coffee {
    mysize: MySize::Short,
    ctype: CType::Cappuccino,
    sugar: Sugar::None,
    trend: Trend { chara: Chara::Pikachu, color: Color::Blue }
};

enumで定義された選択肢を選択をする場合は、名前::選択肢のようにします

mysize: Mysize::Short

こうすることで、実データに "Short" という選択肢がのります
ただ、実際に "Short" という文字列が登録されるわけではないので注意が必要です

最後ややこしくなっている trend の部分は、trendのキーに構造体を渡しています

実際にデータを作る2

let your_Coffee = Coffee {
    mysize: MySize::Order(20),
    ctype: CType::Order("cafelate".to_string()),
    sugar: Sugar::Order(4),
    trend: Trend { chara: Chara::Achamo, color: Color::Red }
};

ユニットバリアント、つまりデータを持たない普通の選択肢はMySize::Shortとすれば大丈夫でした
では名前無しデータを持つタプルバリアントはどうするのでしょうか?

その場合は、mysize: MySize::Order(20)このように()をつけて数値や文字列を渡します

条件分岐と出力

最後の節までやってきました。
本当は入れた内容全てに対し条件分岐をしたいとこですが、練習用のため一部のみ実装してあります

matchを使えば、簡単に条件分岐をかけます。
個人的にはif, elseifより好きかも。
でも複雑な真偽判定はどうやるんでしょうか?←また今度の課題です

let size_string: String = match myCoffee.mysize {
    MySize::Short => "ショートサイズ".to_string(),
    MySize::Medium => "ミディアムサイズ".to_string(),
    MySize::Large => "ラージサイズ".to_string(),
    MySize::Order(height) => format!("オーダーサイズ {}", height)
};
    
println!("{} is selected!", sizeString);

ここでは、matchでsize_stringに入る文字列を条件分けし、それを出力しています
ここでは、以下のことがわかりました

  • matchでは各条件の戻り値は全て同じ型でなければならない
  • format!()は、「文字列にフォーマット」してくれる
  • MySize::Shortは、Shortという文字列を返すわけではない

最初、let size_string: &str = ... としていました
"ショートサイズ"は&str(文字列スライス)なのでOK
しかし、最後の format! だけStringで帰ってくるのでエラーになってしまいます。
(format!("オーダーサイズ {}".to_string(), height)としなくてもいいらしい。)

また、MySize::ShortやMySize::Mediumは0,1といったインデックス?に変換、コンパイルされるらしく、let size: String = MySize::Medium としたらエラーになります
インデックス?で返すのか文字列で返すのかわからないかららしい

まとめ

今回はTour of Rust 3章のまとめとして学習記録を書いてみました。
この3章最後難しいですよね。
何日も止まっていたけど、頑張って最後までやりました!

そういえば、ここでよく登場するferrisという単語、カニのrustのマスコットキャラクターだそうですね!とてもかわいいです!

Discussion