Open1

Rustの学習メモ

K'K'

メソッドの第一引数に付与するSelf(構造体インスタンス)の所有権について

1. self(所有権を取るが、フィールドの書き換えはできない)

  • メソッド内で「変数として再代入(フィールドの書き換え)」を行わない場合
    • フィールドを使って新しい値を作り直すことはできる
  • インスタンスの所有権はメソッドに移動し、呼び出し元でその変数は使えなくなる

使用ケース

① フィールドを書き換えない場合

消費したうえで処理を終わらせる場合(何も返さないケースなど)

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

impl Person {
    // 所有権を取るが、メソッド内では書き換えない場合
    fn consume(self) {
        println!("消費: {:?}", self);
        // ここで self は終わり(所有権がここまで)
    }
}

fn main() {
    let p = Person {
        name: "Alice".to_string(),
        age: 20,
    };
    p.consume(); // 所有権を渡す
    // p はもう使えない
    // p.consume(); <- エラー
}
  • consume(self) は「Person の所有権を受け取って処理したあと、そのまま破棄」する
    • このケースでは、フィールドを再代入する必要がないので mut self にする必要がない
  • 呼び出し元(main) の方では、呼び出し後に p が使えなくなる
    • 所有権が consumeメソッドに移り、その後、返却されていないので

② 新しい値を作り直す(元の構造体のフィールドを消費)

フィールドを再代入せずに、古い値をバラして新しいインスタンスを組み立てる場合

impl Person {
    // self の所有権を奪い、新しい Person を返す
    fn rename(self, new_name: &str) -> Self {
        // ここで self のフィールドをバラして新しい構造体にする
        Person {
            name: new_name.to_string(),
            age: self.age,
        }
    }
}

fn main() {
    let p = Person {
        name: "Bob".to_string(),
        age: 30,
    };
    // 所有権をメソッドに渡して、新しく作り直された Person を戻す
    let p2 = p.rename("Bobby");
    // p2 は使えるが、 p はもう使えない
    println!("{:?}", p2);
}
  • rename(self, new_name)self を受け取って、新しい Person を組み立て直すイメージ
  • フィールドを「いったん取り出して、別のインスタンスを生成」しているので、self をインプレースで書き換えているわけではない
  • 上記のように、フィールドを再度組み立てるときは、self で十分
    • 書き換えなくても self.age を取り出すことは可能

2. mut self(所有権を取り、フィールドの書き換えを行う)

  • メソッド内で「インスタンスのフィールドを再代入(書き換え)」したい場合
    • メソッド内で self のフィールドを書き換え可能(インプレースで値を変更できる)
  • インスタンスの所有権はメソッドに移動し、呼び出し元でその変数は使えなくなる

使用ケース

① フィールドをそのまま書き換える

メンバ変数を再代入(書き換え)したい場合

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

impl Person {
    // mut self でフィールドを再代入(書き換え)
    fn rename_in_place(mut self, new_name: &str) -> Self {
        // self 自体を可変として扱える
        self.name = new_name.to_string();
        self
    }
}

fn main() {
    let p = Person {
        name: "Alice".to_string(),
        age: 25,
    };
    // 所有権を渡しつつ、書き換えた結果を受け取る
    let p2 = p.rename_in_place("Alicia");
    // p は使えない
    println!("{:?}", p2);
}
  • 上記の rename_in_place では、元のインスタンスをインプレースで書き換える操作をしている
    • mut self によって self.name = ... のように代入できるようになっている
  • 書き換えた結果は新しい変数 p2 に返ってくる

② メソッドチェーンでの変換操作

ビルダーパターンなどで「インスタンス内部を順々に更新していく」ような場面で使える。
mut self は、以下のように一度にいろいろ書き換えたいときに直感的に書ける。

#[derive(Debug)]
struct Config {
    ip: String,
    port: u16,
    debug: bool,
}

impl Config {
    fn new() -> Self {
        Self {
            ip: "127.0.0.1".to_string(),
            port: 8080,
            debug: false,
        }
    }

    // mut self で書き換え
    fn set_ip(mut self, ip: &str) -> Self {
        self.ip = ip.to_string();
        self
    }

    fn set_debug(mut self, debug: bool) -> Self {
        self.debug = debug;
        self
    }
}

fn main() {
    // 所有権移動を連鎖させながら、内部をどんどん書き換え
    let config = Config::new()
        .set_ip("192.168.1.10")
        .set_debug(true);

    println!("{:?}", config);
    // `Config` は最終的に呼び出し元に返ってくる
}
  • self でも同じことは 「作り直し」 という形で可能だが、単純に「書き換える」という意図なら mut self のほうがわかりやすい
  • いずれも「呼び出すたびに所有権が移動して、最終的に config に返ってくる」という流れ

3. &self(不変参照)

pub fn search_books(&self, title_query: &str) -> Vec<&Book> {
    // インスタンスの値を読むだけ
}
  • インスタンスを読み取りのみで借用
  • 複数の不変参照が可能
  • インスタンスの状態は変更不可

4. &mut self(可変参照)

pub fn add_book(&mut self, book: Book) {
    self.books.push(book);  // インスタンスの状態を変更
}
  • インスタンスを変更可能な形で借用
  • 同時に1つの可変参照のみ許可
  • インスタンスの状態を変更可能

まとめ

メソッドの目的に応じて適切な形式を選択するが、

基本的には&self&mut selfの借用を使う

  • インスタンスを再利用可能。再利用するのでメモリ効率は良い

self(所有権のムーブ)は特別なケース(変換や消費)でのみ使う

  • インスタンスを別の型に変換する
  • インスタンスを消費して何か処理する
  • メソッドチェーンを利用したい場合
impl Bookshelf {
    // 所有権を移動させるメソッドの例
    pub fn into_vec(self) -> Vec<Book> {
        self.books  // booksの所有権を返す
    }
}

各用途の使用例

impl Bookshelf {
    // 不変参照: 読み取りのみ
    pub fn count_books(&self) -> usize {
        self.books.len()
    }

    // 可変参照: 状態を変更
    pub fn add_book(&mut self, book: Book) {
        self.books.push(book);
    }

    // 所有権を取る: インスタンスを消費
    pub fn into_sorted(mut self) -> Self {
        self.books.sort_by(|a, b| a.title.cmp(&b.title));
        self
    }
}