前回はRust学習における「最大の壁」である所有権と借用について解説しました。
メモリ管理の厳しいルールに、少し頭が疲れてしまったかもしれませんね。
しかし、ここからがRustの楽しいところです!
今回は、バラバラのデータをひとまとめにして扱いやすくする「構造体(Struct)」と、Rustにおいて非常に強力な機能を持つ「列挙型(Enum)」、そしてそれらを安全に処理する「match式」について学びます。
これらを使いこなせると、プログラムの「見通し」が一気に良くなり、「コンパイルが通れば正しく動く」というRustの安心感をより強く感じられるようになりますよ。
今回のゴール
- 構造体を使って、関連するデータを一つにまとめる。
- Enumを使い、データの「種類」や「状態」を定義する(データを持てるEnumの強さを知る)。
- match式を使って、あらゆるパターンを漏れなく処理する堅牢なコードを書く。
1. 構造体(Struct):関連するデータをパッケージ化しよう
プログラミングをしていると、「名前」と「年齢」と「メールアドレス」のように、関連するデータをセットで扱いたい場面が必ず出てきます。
これを実現するのが構造体(Struct)です。
構造体の定義
構造体は、データの「設計図」のようなものです。
// ユーザー情報をまとめる構造体
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}インスタンスの作成と利用
設計図ができたら、実際にデータ(インスタンス)を作ってみましょう。
fn main() {
// 構造体のインスタンスを作成
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// ドット(.)を使ってデータにアクセス
println!("ユーザー名: {}", user1.username);
println!("メール: {}", user1.email);
}
データの変更を行いたい場合は、変数宣言時にmutをつける必要があります(ここでもRustのデフォルト不変のルールが適用されます)。
2. 列挙型(Enum):RustのEnumはただの「列挙」じゃない!
次に紹介するのが列挙型(Enum)です。
「Enumなんて他の言語でも使ったことあるよ(定数のリストでしょ?)」と思った方、ちょっと待ってください!
RustのEnumは、C言語やJavaなどのEnumとは一味違います。
最大の特徴は、Enumの各バリアント(選択肢)の中に、直接データを持たせることができる点です。
基本的なEnum
まずは普通のEnumを見てみましょう。
enum IpAddrKind {
V4,
V6,
}これは「IPアドレスはV4かV6のどちらかである」という状態を表します。
データを持つEnum(Rustの真骨頂)
ここからが本番です。Rustでは、各種類ごとに具体的なデータを埋め込むことができます。
enum Message {
Quit, // データなし
Move { x: i32, y: i32 }, // ス構造体のように名前付きフィールドを持つ
Write(String), // Stringを持つ
ChangeColor(i32, i32, i32), // 3つのi32を持つ
}このように、Messageという一つの型の中に、全く異なる形式のデータを共存させることができます。
「終了シグナル」も「座標移動」も「テキストメッセージ」も、全て同じMessage型として扱えるため、関数の引数などで非常に取り回しが良くなります。

3. match式:パターンマッチで安全な分岐を作る
構造体やEnumを作っても、それを使えなければ意味がありません。
Rustには、Enumの可能性を最大限に引き出す「match式」という制御構文があります。
ifやswitchに似ていますが、決定的な違いは「全てのパターンを網羅しないとコンパイルエラーになる」という点です。これにより、「想定外のパターンが来てバグった!」という事態を防げます。
match式の基本
先ほどのMessage Enumを使って、ロジックを組んでみましょう。
fn process_message(msg: Message) {
match msg {
Message::Quit => {
println!("プログラムを終了します");
}
Message::Move { x, y } => {
println!("座標({}, {})へ移動しました", x, y);
}
Message::Write(text) => {
println!("メッセージ: {}", text);
}
Message::ChangeColor(r, g, b) => {
println!("色をR:{} G:{} B:{}に変更", r, g, b);
}
}
}
fn main() {
let msg = Message::Move { x: 10, y: 20 };
process_message(msg);
}
「漏れ」があると怒られる優しさ
もし上記のコードで、Message::Quitの処理を書き忘れたとしましょう。
Rustコンパイラは即座にエラーを出します。
error[E0004]: non-exhaustive patterns: ‘Quit’ not covered(パターンが網羅されていません。’Quit’がカバーされていません)
これがRustの安全性です。「処理忘れ」というバグを、実行する前に完全になくすことができるのです。
4. 実践:RPGのキャラクターを作ってみよう
ここまでの知識(構造体、Enum、match)を組み合わせて、簡単なRPGのようなコードを書いてみましょう。
- 構造体:キャラクターのステータス
- Enum:キャラクターの職業(職業ごとに固有のスキルデータを持つ)
- match:職業ごとの攻撃アクション
// 職業ごとの詳細データを持つEnum
enum Job {
Warrior, // 戦士(データなし)
Mage { mp: i32 }, // 魔法使い(MPを持つ)
Archer(i32), // 弓使い(矢の本数を持つ)
}
// キャラクターを表す構造体
struct Character {
name: String,
hp: i32,
job: Job, // ここでEnumを使用!
}
fn main() {
// 魔法使いのキャラクターを作成
let player = Character {
name: String::from("Rust使い"),
hp: 100,
job: Job::Mage { mp: 50 },
};
attack(player);
}
// 攻撃処理を行う関数
fn attack(char: Character) {
print!("{}の攻撃!: ", char.name);
// 職業によって処理を分岐
match char.job {
Job::Warrior => println!("剣で強力な一撃!"),
Job::Mage { mp } => {
if mp > 10 {
println!("ファイアボール発射! (残りMP: {})", mp - 10);
} else {
println!("MPが足りない!杖で殴った!");
}
},
Job::Archer(arrows) => println!("矢を放った! (残り矢: {})", arrows - 1),
}
}
構造体の中にEnumを入れることで、「キャラクター」という大きな枠組みの中に、「職業ごとの個別の事情」まできれいに整理することができました。
まとめ:データ構造を制する者はRustを制す
今回は、Rustでのデータ整理の基本となる3つの要素を学びました。
- 構造体 (struct):関連データをパッケージ化する。
- 列挙型 (enum):データの種類を定義し、それぞれにデータを持たせる。
- match式:Enumの全パターンを漏れなく安全に処理する。
特にEnumとmatchの組み合わせは、Rustプログラミングにおいて「最強のコンビ」と言われます。
エラー処理(Result/Option)など、Rustの核心部分でもこの仕組みが使われています。
次回は、いよいよ「モジュールシステム」について触れていきたいと思います。プログラムが大きくなってきたときに、ファイルを分割して管理する方法を学びましょう!

コメント