Prompt
よくない設計だとわかっていても、複数から値を変更しなければいけないときはあるよね。
ならば、setterのような関数を経由することで実現できる?
お、それは**「アンチパターン(Anti-pattern)だと分かっていても、現実の要件として必要になる」**という、プログラマが直面する最も難しい問題の一つだね!深く共感するよ!
結論から言うと、Rustでもsetterのような関数を経由することで、複数箇所から値を安全に変更することは可能だよ。ただし、そのためには所有権と借用のルールを守りつつ、特殊な型を組み合わせる必要があるんだ!
1. ⚙️ setter関数を経由する基本的な実現方法
複数の箇所から値を変更したい場合、Rustでは以下の2つの主要なアプローチが考えられるよ。
A. 可変な借用 (&mut T) を使う (最も一般的)
もし、値の変更が単一のスレッド内で、**排他的(一度に一箇所からのみ)**に行われることが保証できるなら、これが最もシンプルで効率的だよ。
- 実現方法:
setter関数が引数として可変な参照 (&mut self) を受け取るように定義する。
Rust
struct Settings {
volume: u8,
}
impl Settings {
// 可変な参照を受け取り、排他的に値を変更する
pub fn set_volume(&mut self, new_volume: u8) {
self.volume = new_volume;
}
}
// 呼び出し側:
let mut config = Settings { volume: 50 }; // 1. 設定変数自体を mut にする必要がある
config.set_volume(80); // 2. configの排他的な借用を渡す
// config.set_volume(90); // 同じスコープ内で他の借用が生きているとエラー(借用ルール)
【この方法の制限】
-
mutが必要:config変数自体にmutが必要になってしまう。 -
排他性: 変更したいすべての場所が、
configへの排他的な借用(&mut)を同時に持てない。これが、君が求めている「複数から」というニーズに合わない可能性がある。
B. 内部可変性 (RefCell / Mutex) を使う (真の複数箇所変更)
もし、不変な変数(let config)として扱いたいのに、その中身だけを複数箇所から変更したいなら、前回の話に出てきた内部可変性の型が必要になるよ!
1. シングルスレッドでの実現 (RefCell)
Rust
use std::cell::RefCell;
struct Settings {
volume: RefCell<u8>, // フィールド自体を RefCell で包む
}
impl Settings {
pub fn set_volume(&self, new_volume: u8) {
// 不変な &self から、RefCell を使って可変な借用を得る
*self.volume.borrow_mut() = new_volume;
// 注意: borrow_mut()が失敗した場合(他の場所で借用中の場合)は panic! が発生する
}
}
// 呼び出し側:
let config = Settings { volume: RefCell::new(50) }; // 1. configは不変でOK
config.set_volume(80); // 2. 不変な参照で setter を呼び出し、内部を変更
2. マルチスレッドでの実現 (Arc<Mutex<T>>)
最も複雑な「悪い設計」の要求(複数のスレッドから共有・変更)に応えるには、この組み合わせが必要になるよ。
-
Arc: 参照カウントを持ち、複数のスレッド間で同じデータへの所有権を共有するために使う。 -
Mutex: データ競合を防ぐため、排他的アクセスをロックするためのメカニズム。
Rust
use std::sync::{Arc, Mutex};
// ...
let shared_config = Arc::new(Mutex::new(Settings { volume: 50 }));
// 別のスレッド A:
let config_a = Arc::clone(&shared_config);
thread::spawn(move || {
let mut data = config_a.lock().unwrap(); // ロックを取得し、排他的なアクセス権を得る
data.volume = 90;
});
【この方法の制限】
-
実行時コスト:
RefCell::borrow_mut()やMutex::lock()は、実行時に安全性をチェックする(ランタイムコスト)。借用ルール違反やデッドロックの可能性をゼロにはできない。 -
複雑性:
Arc<Mutex<RefCell<T>>>のように型が非常に複雑になり、コードの可読性が下がる。
2. ⚠️ 設計が良くないことを認識しているプログラマへのアドバイス
君が「よくない設計だとわかっていても」という前提を持っているのが素晴らしいね!情報工学を学ぶ君には、その悪い設計の「ツケ」が、そのまま技術的な複雑性としてコードに現れることを理解してほしいんだ。
ツケの例:
-
RefCellを使う場合: 「実行時に借用ルール違反によるpanic!」というリスクを負う。 -
Mutexを使う場合: 「デッドロック」や「ロック待機によるパフォーマンス低下」というリスクを負う。
だから、これらの内部可変性パターンを使うときは、「ここには、Rustのコンパイル時チェックから漏れた、実行時のリスクが潜んでいる」という警告ラベルを貼るような気持ちでいるといいよ!
理想は、可能な限り値を単一の責任者に所有させ、ミュータブルな借用 (&mut T) の範囲を最小限に抑えることだよ!✨