```json
{
"chapter_title": "データ構造を定義する:構造体、列挙型、パターンマッチ",
"content": "# 第4章 データ構造を定義する:構造体、列挙型、パターンマッチ
Rustプログラミングの真髄は、型システムを活用して安全で効率的なコードを書くことにあります。前章までで基本的なデータ型と制御構造を学びましたが、現実世界の複雑なデータを表現するには、より高度なデータ構造が必要です。本章では、Rustの三種の神器とも言える「構造体(struct)」「列挙型(enum)」「パターンマッチ(pattern matching)」を深く探求します。これらの概念を習得することで、データを直感的にモデル化し、コンパイル時に多くのエラーを検出できるようになります。
4.1 構造体(Struct):データのグループ化
構造体は、関連するデータを一つの型としてグループ化するためのRustの主要な手段です。他の言語のクラスや構造体に似ていますが、Rustの構造体には継承がなく、データと振る舞いが明確に分離されています。
4.1.1 構造体の定義とインスタンス化
最も基本的な構造体の定義は以下のようになります:
```rust
// ユーザーを表す構造体の定義
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
```
この定義は、`User`という新しい型を作成します。各フィールドには名前と型が指定されています。構造体のインスタンスを作成するには、以下のようにします:
```rust
// 構造体インスタンスの作成
let user1 = User {
email: String::from(\"someone@example.com\"),
username: String::from(\"someusername123\"),
active: true,
sign_in_count: 1,
};
```
Rustでは、フィールドの順序は定義と一致している必要はありませんが、すべてのフィールドを指定する必要があります。
4.1.2 フィールドへのアクセスと変更
構造体のフィールドにアクセスするにはドット記法を使用します:
```rust
println!(\"ユーザー名: {}\", user1.username);
println!(\"メールアドレス: {}\", user1.email);
```
構造体インスタンスが可変(`mut`)である場合、フィールドの値を変更できます:
```rust
let mut user2 = User {
email: String::from(\"another@example.com\"),
username: String::from(\"anotheruser\"),
active: true,
sign_in_count: 1,
};
user2.email = String::from(\"newemail@example.com\");
user2.sign_in_count += 1;
```
4.1.3 構造体更新記法
既存の構造体インスタンスから新しいインスタンスを作成する際、構造体更新記法を使用すると便利です:
```rust
let user3 = User {
email: String::from(\"third@example.com\"),
username: String::from(\"thirduser\"),
..user1 // 残りのフィールドはuser1からコピー
};
```
この記法は、特に多くのフィールドを持つ構造体で、一部だけを変更したい場合に有用です。
4.1.4 タプル構造体
フィールド名を持たない構造体も定義できます。これらはタプル構造体と呼ばれます:
```rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
```
`Color`と`Point`は異なる型ですが、同じフィールド構成を持っています。タプル構造体は、単純なデータのグループ化や、新しい型を作成して型安全性を高める場合に有用です。
4.1.5 ユニットライク構造体
フィールドを全く持たない構造体も定義できます。これらはユニットライク構造体と呼ばれます:
```rust
struct AlwaysEqual;
let subject = AlwaysEqual;
```
ユニットライク構造体は、トレイトを実装する必要があるがデータを保持しない場合や、マーカーとして使用する場合に便利です。
4.1.6 メソッドの定義
構造体に振る舞いを追加するには、`impl`ブロックを使用してメソッドを定義します:
```rust
impl User {
// 関連関数(スタティックメソッド)
fn new(username: String, email: String) -> User {
User {
username,
email,
active: true,
sign_in_count: 0,
}
}
// インスタンスメソッド
fn display_info(&self) {
println!(\"ユーザー名: {}\", self.username);
println!(\"メールアドレス: {}\", self.email);
println!(\"アクティブ: {}\", self.active);
println!(\"サインイン回数: {}\", self.sign_in_count);
}
// 可変参照を受け取るメソッド
fn increment_sign_in(&mut self) {
self.sign_in_count += 1;
}
// 所有権を取るメソッド
fn deactivate(self) -> User {
User {
active: false,
..self
}
}
}
// 使用例
let mut user = User::new(
String::from(\"rustacean\"),
String::from(\"rust@example.com\")
);
user.display_info();
user.increment_sign_in();
let deactivated_user = user.deactivate();
```
メソッドの第一引数は常に`self`で、その参照方法(`&self`、`&mut self`、`self`)によって、メソッドがインスタンスをどのように扱うかが決まります。
4.1.7 複数のimplブロック
一つの構造体に対して複数の`impl`ブロックを定義できます:
```rust
impl User {
fn is_active(&self) -> bool {
self.active
}
}
impl User {
fn get_email(&self) -> &str {
&self.email
}
}
```
この機能は、ジェネリック型やトレイト実装で特に有用です。
4.2 列挙型(Enum):選択肢の表現
列挙型は、値が取り得る有限のバリアント(変種)を定義する型です。Rustの列挙型は他の言語の列挙型よりも強力で、各バリアントに関連データを持たせることができます。
4.2.1 基本的な列挙型
最も単純な列挙型は、単なる値のリストです:
```rust
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
```
4.2.2 データを持つ列挙型
Rustの列挙型の真の力は、各バリアントに関連データを持たせられる点にあります:
```rust
enum IpAddr {
V4(u8, u8, u8, u8), // IPv4アドレス
V6(String), // IPv6アドレス
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from(\"::1\"));
```
各バリアントは異なる型とデータ量を持つことができます。これは、他の言語では実現が難しい強力な機能です。
4.2.3 列挙型のメソッド
構造体と同様に、列挙型にもメソッドを定義できます:
```rust
impl IpAddr {
fn to_string(&self) -> String {
match self {
IpAddr::V4(a, b, c, d) => format!(\"{}.{}.{}.{}\", a, b, c, d),
IpAddr::V6(s) => s.clone(),
}
}
}
println!(\"{}\", home.to_string()); // \"127.0.0.1\"
```
4.2.4 Option列挙型:nullの代わり
Rustにはnullがありません。代わりに、値が存在するかしないかを表現する`Option`列挙型が標準ライブラリに定義されています:
```rust
enum Option {
Some(T), // 値がある
None, // 値がない
}
```
`Option`は非常に一般的に使用され、プレリュード(自動的にインポートされるモジュール)に含まれているため、`Some`と`None`を直接使用できます:
```rust
let some_number = Some(5); // Option
let some_string = Some(\"a string\"); // Option
let absent_number: Option = None; // 明示的な型注釈が必要
```
`Option`を使用することで、コンパイラは値が存在しない可能性を強制的に処理させ、null参照エラーを防ぎます。
4.2.5 Result列挙型:エラー処理
もう一つの重要な列挙型は`Result`で、成功または失敗を表します:
```rust
enum Result {
Ok(T), // 成功、値Tを持つ
Err(E), // 失敗、エラーEを持つ
}
```
`Result`はエラー処理の基盤であり、第6章で詳しく説明します。
4.2.6 列挙型のメモリ表現
Rustの列挙型は、タグ付きユニオンとして実装されています。各バリアントにはタグ(識別子)が付き、必要なメモリは最も大きなバリアントのサイズにタグのサイズを加えたものになります。この実装は効率的で、パターンマッチングを高速に行えます。
4.3 パターンマッチング:制御フローの強化
パターンマッチングはRustの最も強力な機能の一つです。値の構造を調べ、その構造に基づいてコードを実行できます。
4.3.1 match式
`match`式は、値と一連のパターンを比較し、一致するパターンに対応するコードを実行します:
```rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!(\"Lucky penny!\");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
```
`match`式は網羅的でなければなりません。すべての可能性をカバーしていない場合、コンパイルエラーになります。
4.3.2 パターンに値を束縛する
パターンは値の一部を変数に束縛できます:
```rust
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// ... 他の州
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // 州情報を持つ25セント硬貨
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!(\"State quarter from {:?}!\", state);
25
}
}
}
```
4.3.3 Optionとのマッチング
`Option`とのパターンマッチングは非常に一般的です:
```rust
fn plus_one(x: Option) -> Option {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five); // Some(6)
let none = plus_one(None); // None
```
4.3.4 ワイルドカードパターン
すべてのケースを列挙したくない場合、ワイルドカードパターン`_`を使用できます:
```rust
let some_u8_value = 0u8;
match some_u8_value {
1 => println!(\"one\"),
3 => println!(\"three\"),
5 => println!(\"five\"),
7 => println!(\"seven\"),
_ => (), // その他の値では何もしない
}
```
4.3.5 if let構文
一つのパターンにのみ関心がある場合、`match`の簡略構文として`if let`を使用できます:
```rust
let some_u8_value = Some(3u8);
// matchを使用した場合
match some_u8_value {
Some(3) => println!(\"three\"),
_ => (),
}
// if letを使用した場合(より簡潔)
if let Some(3) = some_u8_value {
println!(\"three\");
}
```
`if let`は`else`節も持てます:
```rust
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!(\"State quarter from {:?}!\", state);
} else {
count += 1;
}
```
4.4 実践的な例:在庫管理システム
これまで学んだ概念を統合して、シンプルな在庫管理システムを作成してみましょう:
```rust
// 商品カテゴリを表す列挙型
enum Category {
Electronics,
Clothing,
Books,
Food,
Other,
}
// 商品状態を表す列挙型
enum ItemCondition {
New,
Used(UsedCondition), // 中古品の状態
Refurbished, // 再生品
}
// 中古品の状態
enum UsedCondition {
LikeNew,
Good,
Fair,
Poor,
}
// 商品を表す構造体
struct InventoryItem {
id: u32,
name: String,
category: Category,
condition: ItemCondition,
price: f64,
quantity: u32,
location: String, // 倉庫内の位置
}
impl InventoryItem {
// 新しい商品を作成
fn new(id: u32, name: String, category: Category,
condition: ItemCondition, price: f64,
quantity: u32, location: String) -> Self {
InventoryItem {
id,
name,
category,
condition,
price,
quantity,
location,
}
}
// 商品情報を表示
fn display(&self) {
println!(\"商品ID: {}\", self.id);
println!(\"商品名: {}\", self.name);
// カテゴリの表示
let category_str = match self.category {
Category::Electronics => \"電子機器\",
Category::Clothing => \"衣類\",
Category::Books => \"書籍\",
Category::Food => \"食品\",
Category::Other => \"その他\",
};
println!(\"カテゴリ: {}\", category_str);
// 状態の表示
match &self.condition {
ItemCondition::New => println!(\"状態: 新品\"),
ItemCondition::Used(cond) => {
let cond_str = match cond {
UsedCondition::LikeNew => \"新品同様\",
UsedCondition::Good => \"良好\",
UsedCondition::Fair => \"可\",
UsedCondition::Poor => \"不良\",
};
println!(\"状態: 中古 ({})\", cond_str);
}
ItemCondition::Refurbished => println!(\"状態: 再生品\"),
}
println!(\"価格: {:.2}円\", self.price);
println!(\"在庫数: {}\", self.quantity);
println!(\"保管場所: {}\", self.location);
println!(\"---\");
}
// 在庫を増減
fn update_quantity(&mut self, change: i32) -> Result {
let new_quantity = self.quantity as i32 + change;
if new_quantity 0.0 && percentage f64 {
self.price * self.quantity as f64
}
}
// 在庫を管理する構造体
struct Inventory {
items: Vec,
}
impl Inventory {
fn new() -> Self {
Inventory { items: Vec::new() }
}
// 商品を追加
fn add_item(&mut self, item: InventoryItem) {
self.items.push(item);
}
// IDで商品を検索
fn find_by_id(&self, id: u32) -> Option {
self.items.iter().find(|item| item.id == id)
}
// カテゴリ別に商品をフィルタリング
fn filter_by_category(&self, category: Category) -> Vec {
self.items.iter()
.filter(|item| match (&item.category, &category) {
(Category::Electronics, Category::Electronics) => true,
(Category::Clothing, Category::Clothing) => true,
(Category::Books, Category::Books) => true,
(Category::Food, Category::Food) => true,
(Category::Other, Category::Other) => true,
_ => false,
})
.collect()
}
// 在庫の総価値を計算
fn total_inventory_value(&self) -> f64 {
self.items.iter().map(|item| item.total_value()).sum()
}
// 在庫が少ない商品をリストアップ
fn low_stock_items(&self, threshold: u32) -> Vec {
self.items.iter()
.filter(|item| item.quantity println!(\"負の数\"),
x if x == 0 => println!(\"ゼロ\"),
_ => println!(\"正の数\"),
}
```
4.5.3 エラー処理のパターン
`Result`と`Option`を効果的に使用するパターン:
```rust
// 早期リターンによるエラー処理
fn process_data(data: Option) -> Result {
let content = data.ok_or(\"データがありません\")?;
// 処理を続行...
Ok(())
}
// チェーンメソッド
let result = get_data()
.and_then(validate_data)
.and_then(process_data)
.unwrap_or_else(|err| {
eprintln!(\"エラー: {}\", err);
default_value()
});
```
4.6 まとめ
本章では、Rustのデータモデリングの中核となる三つの概念を学びました:
1. 構造体(struct):関連データをグループ化し、新しい型を定義します。
2. 列挙型(enum):値が取り得るバリアントを定義し、各バリアントに関連データを持たせられます。
3. パターンマッチング:値の構造を調べ、それに基づいてコードを実行します。
これらの概念を組み合わせることで、複雑なデータ構造を安全かつ効率的に表現できます。Rustの型システムとこれらの機能は、コンパイル時に多くのエラーを検出し、実行時エラーを大幅に減らすことができます。
次章では、これらのデータ構造を効果的に管理するためのコレクション(ベクター、ハッシュマップなど)について学びます。また、所有権と借用の概念がこれらのデータ構造とどのように相互作用するかも探求します。
演習問題
1. 図形(円、長方形、三角形)を表す列挙型を定義し、面積を計算するメソッドを実装してください。
2. 銀行口座を表す構造体を作成し、入金、出金、残高照会のメソッドを実装してください。
3. `Option`の値を受け取り、それが`Some`の場合は値を2倍し、`None`の場合は0を返す関数を作成してください。
4. トランプのカードを表す列挙型と、ポーカーの手役を判定する関数を作成してください。
これらの演習を通じて、本章で学んだ概念の理解を深めてください。"
}
```