メソッド

Rust では、新しい型に関数を関連付けることができます。これは impl ブロックで行います:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
struct CarRace {
    name: String,
    laps: Vec<i32>,
}

impl CarRace {
    // レシーバなし、静的メソッド
    fn new(name: &str) -> Self {
        Self { name: String::from(name), laps: Vec::new() }
    }

    // self への排他的な借用による読み書きアクセス
    fn add_lap(&mut self, lap: i32) {
        self.laps.push(lap);
    }

    // self への共有の読み取り専用借用アクセス
    fn print_laps(&self) {
        println!("Recorded {} laps for {}:", self.laps.len(), self.name);
        for (idx, lap) in self.laps.iter().enumerate() {
            println!("Lap {idx}: {lap} sec");
        }
    }

    // self の排他的な所有権(後で扱います)
    fn finish(self) {
        let total: i32 = self.laps.iter().sum();
        println!("Race {} is finished, total lap time: {}", self.name, total);
    }
}

fn main() {
    let mut race = CarRace::new("Monaco Grand Prix");
    race.add_lap(70);
    race.add_lap(68);
    race.print_laps();
    race.add_lap(71);
    race.print_laps();
    race.finish();
    // race.add_lap(42);
}

self 引数は「レシーバ」、つまりそのメソッドが作用するオブジェクトを 指定します。 メソッドでよく使われるレシーバには、いくつかの共通パターンがあります:

  • &self: 共有かつ不変の参照を使って、呼び出し元からオブジェクトを 借用します。その後もオブジェクトは再び使用できます。
  • &mut self: 一意で可変な参照を使って、呼び出し元からオブジェクトを 借用します。その後もオブジェクトは再び使用できます。
  • self: オブジェクトの所有権を取得し、呼び出し元からムーブします。 メソッドがそのオブジェクトの所有者になります。所有権が明示的に 移譲されない限り、メソッドから戻るときにそのオブジェクトはドロップ (メモリ解放)されます。完全な所有権を持つことは、自動的に可変であることを意味するわけではありません。
  • mut self: 上と同じですが、メソッドがオブジェクトを変更できます。
  • レシーバなし: これは構造体上の静的メソッドになります。通常、慣例的に new と呼ばれるコンストラクタを作るために使われます。

重要なポイント:

  • メソッドは関数と比較して導入すると理解しやすいことがあります。
    • メソッドは、型(構造体や列挙型など)のインスタンスに対して呼び出され、最初 のパラメータは self としてそのインスタンスを表します。
    • 開発者は、メソッドレシーバ構文を活用し、より整理しやすくするためにメソッド を選ぶことがあります。メソッドを使うことで、すべての実装コードを予測しやす い 1 か所にまとめておけます。
    • メソッドは、レシーバを明示的に渡すことで、関連関数のように呼び出すこともで きます。たとえば CarRace::add_lap(&mut race, 20) のようになります。
  • メソッドレシーバであるキーワード self の使い方を指摘してください。
    • これが self: Self の省略形であることを示し、場合によっては構造体名も使え ることを示してください。
    • Self はその impl ブロックが属する型の型エイリアスであり、ブロック内の他 の場所でも使えることを説明してください。
    • self が他の構造体と同じように使われ、ドット記法で個々のフィールドを参照で きることに触れてください。
    • finish を 2 回実行してみて、&selfself の違いを示すには良いタイミ ングかもしれません。
    • self のバリエーションに加えて、 特別なラッパー型 といったレシーバ型として許可される特別なラッパー型もあり、その例が Box<Self> です。