06. 構造体とメソッド

🎯 この章のゴール

  • 構造体(struct)でデータをまとめる
  • impl でメソッドと関連関数を定義する
  • #[derive(Debug)] でデバッグ表示する

💡 ひとことで言うと

構造体は 名前付きの入れ物。impl でその入れ物に できること(メソッド) を生やします。

定義と生成

#[derive(Debug)]
struct User {
    name: String,
    age: u32,
    active: bool,
}

fn main() {
    let name = String::from("Ferris");
    let mut user = User {
        name,           // 変数名とフィールド名が同じなら省略できる
        age: 8,
        active: true,
    };
    user.age += 1;      // インスタンス全体が mut なら変更できる

    let other = User {
        name: String::from("Corro"),
        ..user          // 残りは user からコピー(構造体更新記法)
    };
    println!("{user:?}");
    println!("{other:#?}"); // {:#?} は整形して表示
}
Playground で開く ↗

タプル構造体・ユニット様構造体

struct Color(u8, u8, u8);   // フィールド名なし
struct Meters(f64);         // 型に意味を持たせる(newtype)
struct Marker;              // フィールドなし

fn main() {
    let red = Color(255, 0, 0);
    let d = Meters(42.0);
    let _m = Marker;
    println!("{} {}", red.0, d.0);
}
Playground で開く ↗

Meters(f64) と Seconds(f64) を別の型にすれば、うっかり足し算するミスをコンパイラが防いでくれます。

メソッドと関連関数

#[derive(Debug)]
struct Rect {
    w: u32,
    h: u32,
}

impl Rect {
    // 関連関数(self なし): Rect::square(3) で呼ぶ
    fn square(size: u32) -> Self {
        Self { w: size, h: size }
    }

    // メソッド: rect.area() で呼ぶ
    fn area(&self) -> u32 {
        self.w * self.h
    }

    fn scale(&mut self, k: u32) {
        self.w *= k;
        self.h *= k;
    }

    fn can_hold(&self, other: &Rect) -> bool {
        self.w > other.w && self.h > other.h
    }
}

fn main() {
    let mut big = Rect { w: 30, h: 50 };
    let small = Rect::square(10);
    big.scale(2);
    println!("面積 {}、入る? {}", big.area(), big.can_hold(&small));
}
Playground で開く ↗
第 1 引数意味使いどころ
&self借りて読むほとんどのメソッド
&mut self借りて書き換える状態を変更する
self所有権を奪う別の値に変換して元を使えなくする

big.area() と書くと Rust が自動的に (&big).area() にしてくれます(自動参照)。C++ の -> のような使い分けは不要です。

🧪 やってみよう

下の書きかけコードの TODO を埋めて、▶ 実行 で確かめよう。todo!() のままだと実行時に panic します。

Rect に「正方形かどうか」を返すメソッドを追加しよう。

struct Rect {
    w: u32,
    h: u32,
}

impl Rect {
    fn is_square(&self) -> bool {
        todo!()
    }
}

fn main() {
    println!("{}", Rect { w: 3, h: 3 }.is_square()); // true
    println!("{}", Rect { w: 3, h: 5 }.is_square()); // false
}
Playground で開く ↗
💡 解答例を見る
struct Rect {
    w: u32,
    h: u32,
}

impl Rect {
    fn is_square(&self) -> bool {
        self.w == self.h
    }
}

fn main() {
    println!("{}", Rect { w: 3, h: 3 }.is_square()); // true
    println!("{}", Rect { w: 3, h: 5 }.is_square()); // false
}
Playground で開く ↗

📝 まとめ

  • struct でデータをまとめ、impl で振る舞いを追加
  • &self / &mut self / self で借用の仕方を選ぶ
  • #[derive(Debug)] + {:?} でお手軽デバッグ表示

✅ 理解度クイズ

Q1
メソッドの第 1 引数 &self は何の省略形?
Q2
String::from("x") や Rect::square(3) のように :: で呼ぶ、self を取らない関数を何と呼ぶ?
Q3
次のコードの出力は?
#[derive(Debug)]
struct P { x: i32, y: i32 }

fn main() {
    let a = P { x: 1, y: 2 };
    let b = P { x: 10, ..a };
    println!("{:?}", b);
}
Q4
println!("{:?}", rect) で構造体を表示するために必要なものは?