Open1
Rustの学習メモ

メソッドの第一引数に付与するSelf(構造体インスタンス)の所有権について
self
(所有権を取るが、フィールドの書き換えはできない)
1. - メソッド内で「変数として再代入(フィールドの書き換え)」を行わない場合
- フィールドを使って新しい値を作り直すことはできる
- インスタンスの所有権はメソッドに移動し、呼び出し元でその変数は使えなくなる
使用ケース
① フィールドを書き換えない場合
消費したうえで処理を終わらせる場合(何も返さないケースなど)
#[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
を取り出すことは可能
- 書き換えなくても
mut self
(所有権を取り、フィールドの書き換えを行う)
2. - メソッド内で「インスタンスのフィールドを再代入(書き換え)」したい場合
- メソッド内で
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
に返ってくる」という流れ
&self
(不変参照)
3. pub fn search_books(&self, title_query: &str) -> Vec<&Book> {
// インスタンスの値を読むだけ
}
- インスタンスを読み取りのみで借用
- 複数の不変参照が可能
- インスタンスの状態は変更不可
&mut self
(可変参照)
4. 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
}
}