第10章: 構造体

学習目標

  • 構造体の定義と使い方を理解する
  • メソッドと関連関数を実装する
  • implブロックの役割を把握する
  • タプル構造体とユニット構造体を学ぶ

---

10.1 構造体の基本

10.1.1 構造体の定義

複数の関連する値をまとめたデータ型:

struct User {
    username: String,
    email: String,
    age: u32,
    active: bool,
}

fn main() {
    let user = User {
        username: String::from("alice"),
        email: String::from("alice@example.com"),
        age: 30,
        active: true,
    };

    println!("ユーザー名: {}", user.username);
}

構文

struct 構造体名 {
    フィールド名: 型,
    ...
}

10.1.2 フィールドへのアクセス

ドット記法でアクセス:

fn main() {
    let user = User {
        username: String::from("bob"),
        email: String::from("bob@example.com"),
        age: 25,
        active: true,
    };

    println!("名前: {}", user.username);
    println!("年齢: {}", user.age);
}

10.1.3 可変な構造体

fn main() {
    let mut user = User {
        username: String::from("alice"),
        email: String::from("alice@example.com"),
        age: 30,
        active: true,
    };

    user.email = String::from("newemail@example.com");
    user.age += 1;

    println!("新しいメール: {}", user.email);
}

注意:構造体全体がmutかimmutか(個別フィールドごとには指定できない)

---

10.2 構造体の生成

10.2.1 フィールド初期化省略記法

変数名とフィールド名が同じ場合:

fn build_user(username: String, email: String) -> User {
    User {
        username,  // username: username の省略形
        email,     // email: email の省略形
        age: 0,
        active: true,
    }
}

10.2.2 構造体更新記法

既存の構造体から一部を変更:

fn main() {
    let user1 = User {
        username: String::from("alice"),
        email: String::from("alice@example.com"),
        age: 30,
        active: true,
    };

    let user2 = User {
        email: String::from("different@example.com"),
        ..user1  // 残りのフィールドはuser1と同じ
    };

    // println!("{}", user1.username); // ❌ エラー!usernameはムーブ済み
    println!("{}", user1.age);       // ✅ OK(Copyトレイト)
}

注意点

  • ..は最後に記述
  • Stringなどの非Copyフィールドは所有権が移動

---

10.3 タプル構造体

10.3.1 基本的な定義

名前付きフィールドがない構造体:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

    println!("R: {}, G: {}, B: {}", black.0, black.1, black.2);
}

用途

  • 型に意味を持たせたい
  • フィールド名が不要

10.3.2 newtype パターン

型安全性のために1つのフィールドを持つ構造体:

struct Meters(f64);
struct Kilometers(f64);

fn distance_in_meters(d: Meters) -> f64 {
    d.0
}

fn main() {
    let m = Meters(100.0);
    let km = Kilometers(1.0);

    println!("{}", distance_in_meters(m));
    // println!("{}", distance_in_meters(km)); // ❌ 型が違う
}

---

10.4 ユニット構造体

10.4.1 フィールドがない構造体

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

用途

  • トレイトの実装が必要だがデータは不要な場合
  • マーカー型として使用

trait Greet {
    fn greet(&self);
}

struct English;
struct Japanese;

impl Greet for English {
    fn greet(&self) {
        println!("Hello!");
    }
}

impl Greet for Japanese {
    fn greet(&self) {
        println!("こんにちは!");
    }
}

fn main() {
    let en = English;
    let ja = Japanese;

    en.greet();
    ja.greet();
}

---

10.5 メソッド

10.5.1 implブロック

構造体にメソッドを実装:

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // メソッド(&selfを取る)
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn perimeter(&self) -> u32 {
        2 * (self.width + self.height)
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };

    println!("面積: {}", rect.area());
    println!("周囲: {}", rect.perimeter());
}

10.5.2 selfの3つの形

┌─────────────────────────────────────────────────────────┐
│            selfの種類                                    │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  &self                                                  │
│  - 不変借用                                             │
│  - 値を読むだけ                                         │
│  - 最も一般的                                           │
│                                                         │
│  &mut self                                              │
│  - 可変借用                                             │
│  - 値を変更する                                         │
│                                                         │
│  self                                                   │
│  - 所有権を取る                                         │
│  - 値を消費する                                         │
│  - 変換メソッドなど                                     │
│                                                         │
└─────────────────────────────────────────────────────────┘

impl Rectangle {
    // 不変借用
    fn width(&self) -> u32 {
        self.width
    }

    // 可変借用
    fn set_width(&mut self, width: u32) {
        self.width = width;
    }

    // 所有権を取る
    fn into_square(self) -> Rectangle {
        let size = std::cmp::min(self.width, self.height);
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("幅: {}", rect.width());

    let mut rect2 = Rectangle { width: 30, height: 50 };
    rect2.set_width(40);

    let square = rect.into_square();
    // println!("{}", rect.width); // ❌ rectは消費された
}

---

10.6 関連関数

10.6.1 コンストラクタパターン

new関数を定義するのが慣習:

impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }

    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let rect = Rectangle::new(30, 50);
    let square = Rectangle::square(20);
}

特徴

  • selfを取らない
  • 構造体名::関数名()で呼び出す
  • コンストラクタとして使用

10.6.2 ファクトリーメソッド

impl Rectangle {
    fn from_dimensions(width: u32, height: u32) -> Self {
        Self { width, height }
    }

    fn default() -> Self {
        Self {
            width: 1,
            height: 1,
        }
    }
}

fn main() {
    let rect1 = Rectangle::from_dimensions(10, 20);
    let rect2 = Rectangle::default();
}

Selfキーワード

  • 現在の型を表す
  • 型名の繰り返しを避ける

---

10.7 複数のimplブロック

同じ構造体に複数のimplブロックを持てます:

impl Rectangle {
    fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn is_square(&self) -> bool {
        self.width == self.height
    }
}

用途

  • ジェネリクスのトレイト境界ごとに分ける
  • 関連する機能をグループ化
  • ---

    10.8 デバッグ出力

    10.8.1 Debug トレイト

    #[derive(Debug)]
    struct Rectangle {
        width: u32,
        height: u32,
    }
    
    fn main() {
        let rect = Rectangle {
            width: 30,
            height: 50,
        };
    
        println!("{:?}", rect);
        // Rectangle { width: 30, height: 50 }
    
        println!("{:#?}", rect);
        // Rectangle {
        //     width: 30,
        //     height: 50,
        // }
    }
    

    10.8.2 カスタムDisplay

    use std::fmt;
    
    struct Point {
        x: i32,
        y: i32,
    }
    
    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "({}, {})", self.x, self.y)
        }
    }
    
    fn main() {
        let point = Point { x: 10, y: 20 };
        println!("{}", point); // (10, 20)
    }
    

    ---

    10.9 実践例

    例1:ユーザー管理システム

    #[derive(Debug)]
    struct User {
        id: u64,
        username: String,
        email: String,
        active: bool,
    }
    
    impl User {
        fn new(id: u64, username: String, email: String) -> Self {
            Self {
                id,
                username,
                email,
                active: true,
            }
        }
    
        fn deactivate(&mut self) {
            self.active = false;
        }
    
        fn update_email(&mut self, new_email: String) {
            self.email = new_email;
        }
    
        fn is_active(&self) -> bool {
            self.active
        }
    }
    
    fn main() {
        let mut user = User::new(
            1,
            String::from("alice"),
            String::from("alice@example.com"),
        );
    
        println!("アクティブ: {}", user.is_active());
    
        user.deactivate();
        println!("アクティブ: {}", user.is_active());
    }
    

    例2:図形の計算

    #[derive(Debug)]
    struct Circle {
        radius: f64,
    }
    
    impl Circle {
        fn new(radius: f64) -> Self {
            Self { radius }
        }
    
        fn area(&self) -> f64 {
            std::f64::consts::PI * self.radius * self.radius
        }
    
        fn circumference(&self) -> f64 {
            2.0 * std::f64::consts::PI * self.radius
        }
    
        fn scale(&mut self, factor: f64) {
            self.radius *= factor;
        }
    }
    
    fn main() {
        let mut circle = Circle::new(5.0);
    
        println!("半径: {}", circle.radius);
        println!("面積: {:.2}", circle.area());
        println!("円周: {:.2}", circle.circumference());
    
        circle.scale(2.0);
        println!("スケール後の半径: {}", circle.radius);
    }
    

    例3:銀行口座

    #[derive(Debug)]
    struct BankAccount {
        account_number: String,
        balance: f64,
        owner: String,
    }
    
    impl BankAccount {
        fn new(account_number: String, owner: String) -> Self {
            Self {
                account_number,
                balance: 0.0,
                owner,
            }
        }
    
        fn deposit(&mut self, amount: f64) -> Result<(), String> {
            if amount <= 0.0 {
                return Err(String::from("金額は正の数である必要があります"));
            }
    
            self.balance += amount;
            Ok(())
        }
    
        fn withdraw(&mut self, amount: f64) -> Result<(), String> {
            if amount <= 0.0 {
                return Err(String::from("金額は正の数である必要があります"));
            }
    
            if amount > self.balance {
                return Err(String::from("残高不足"));
            }
    
            self.balance -= amount;
            Ok(())
        }
    
        fn balance(&self) -> f64 {
            self.balance
        }
    }
    
    fn main() {
        let mut account = BankAccount::new(
            String::from("123-456"),
            String::from("Alice"),
        );
    
        account.deposit(1000.0).unwrap();
        println!("残高: {}", account.balance());
    
        match account.withdraw(500.0) {
            Ok(_) => println!("引き出し成功。残高: {}", account.balance()),
            Err(e) => println!("エラー: {}", e),
        }
    }
    

    例4:タスク管理

    #[derive(Debug, PartialEq)]
    enum TaskStatus {
        Todo,
        InProgress,
        Done,
    }
    
    #[derive(Debug)]
    struct Task {
        id: u32,
        title: String,
        description: String,
        status: TaskStatus,
    }
    
    impl Task {
        fn new(id: u32, title: String, description: String) -> Self {
            Self {
                id,
                title,
                description,
                status: TaskStatus::Todo,
            }
        }
    
        fn start(&mut self) {
            if self.status == TaskStatus::Todo {
                self.status = TaskStatus::InProgress;
            }
        }
    
        fn complete(&mut self) {
            self.status = TaskStatus::Done;
        }
    
        fn is_done(&self) -> bool {
            self.status == TaskStatus::Done
        }
    }
    
    fn main() {
        let mut task = Task::new(
            1,
            String::from("Rustを学ぶ"),
            String::from("構造体の章を完了する"),
        );
    
        println!("{:?}", task.status);
    
        task.start();
        println!("{:?}", task.status);
    
        task.complete();
        println!("完了: {}", task.is_done());
    }
    

    ---

    10.10 構造体のパターン

    10.10.1 ビルダーパターン

    struct User {
        username: String,
        email: String,
        age: Option<u32>,
    }
    
    struct UserBuilder {
        username: String,
        email: String,
        age: Option<u32>,
    }
    
    impl UserBuilder {
        fn new(username: String, email: String) -> Self {
            Self {
                username,
                email,
                age: None,
            }
        }
    
        fn age(mut self, age: u32) -> Self {
            self.age = Some(age);
            self
        }
    
        fn build(self) -> User {
            User {
                username: self.username,
                email: self.email,
                age: self.age,
            }
        }
    }
    
    fn main() {
        let user = UserBuilder::new(
            String::from("alice"),
            String::from("alice@example.com"),
        )
        .age(30)
        .build();
    }
    

    10.10.2 状態パターン

    struct Draft {
        content: String,
    }
    
    struct PendingReview {
        content: String,
    }
    
    struct Published {
        content: String,
    }
    
    impl Draft {
        fn new(content: String) -> Self {
            Self { content }
        }
    
        fn request_review(self) -> PendingReview {
            PendingReview {
                content: self.content,
            }
        }
    }
    
    impl PendingReview {
        fn approve(self) -> Published {
            Published {
                content: self.content,
            }
        }
    
        fn reject(self) -> Draft {
            Draft {
                content: self.content,
            }
        }
    }
    
    fn main() {
        let draft = Draft::new(String::from("記事の内容"));
        let review = draft.request_review();
        let published = review.approve();
    }
    

    ---

    10.11 まとめ

    学んだこと

  • 構造体の定義
- 名前付きフィールド - タプル構造体 - ユニット構造体

  • メソッド
- implブロック - &self、&mut self、self - 関連関数

  • パターン
- フィールド初期化省略記法 - 構造体更新記法 - newtype パターン

  • 実践
- ユーザー管理 - 銀行口座 - タスク管理

次のステップ

Rust Foundations コースの基礎編は完了です。次は: