Prompt
Rustでも同様に、mutの有無に関わらずオブジェクトの中身を変更することができる?
なるほど、C#やJavaのような言語だと、参照型オブジェクト(ミュータブルなフィールドを持つクラスのインスタンス)は、参照変数自体にfinalやreadonlyがついていなくても、その中身は変更できることが多いよね。
Rustでは、mutの有無に関わらずオブジェクトの中身を変更できるか? という問いだけど、答えは**「原則として、NO!」**だよ。
これはRustの**不変性(Immutability)と内部可変性(Interior Mutability)**という、非常に重要な概念に関わってくるんだ。
🔒 原則:mutがないと中身は変更できない
Rustの変数は、特別な理由がない限り、**イミュータブル(不変)**なんだ。そしてこの不変性は、単に変数への再代入を禁止するだけでなく、その変数が所有するオブジェクトの内部状態の変更も禁止するように働いているよ!
例:StringやVecの場合
StringやVec<T>(ベクトル)のような、内部にヒープデータを持つ構造体は、デフォルトでミュータブルな参照 (&mut) を要求するメソッド(例:pushやclear)を持っているんだ。
Rust
let s = String::from("Hello"); // sは不変
// s.push_str(" World"); // -> コンパイルエラー!
// sが不変なので、&mut selfを要求するpush_strは呼べない
このとき、sが不変であるため、sのメソッドを呼び出すとき、Rustは自動的に不変な参照 (&s) を渡すんだ。push_strは可変な参照(&mut self)を要求するので、型ミスマッチとしてコンパイルエラーになるんだよ。
💡 例外:内部可変性(Interior Mutability)
しかし、Rustにも、mut キーワードがない変数(不変な変数)の中身を、安全に変更できるようにするための、非常に巧妙な仕組みがあるんだ。これが内部可変性というパターンだよ。
これは、Rustの安全性の保証を崩すことなく、特定の状況下で不変性を破るための手法なんだ。
実現のための主要な型:
-
std::cell::Cell<T>:-
参照が不要で、中の値
Tをコピーできる型(Copyトレイトを実装している型)を扱うときに使う。 -
set()やget()メソッドで、不変な参照を通して中の値を直接書き換えられる。
-
-
std::cell::RefCell<T>:-
参照が必要で、ランタイム(実行時)に借用ルール(「ひとつの可変、複数の不変」)をチェックするために使う。
-
実行時に
borrow()(&T) やborrow_mut()(&mut T) を呼び出すんだけど、もし借用ルールを破るようなアクセスが発生したら、panic!を発生させることで安全性を守るんだ。 -
主にシングルスレッド環境で使われる。
-
-
std::sync::Mutex<T>/std::sync::RwLock<T>:-
マルチスレッド環境で安全に内部可変性を実現するために使う。
-
ロックを介して排他的アクセスを強制し、中のデータへのアクセスをスレッドセーフにする。
-
内部可変性の例(RefCell):
Rust
use std::cell::RefCell;
let my_data = RefCell::new(String::from("初期値")); // my_data は不変 (let mut がない)
// 不変な my_data から、可変な借用を得る!
let mut inner_data = my_data.borrow_mut();
// 中身を変更!
inner_data.push_str("が変更されました!");
// inner_data がスコープを抜けると、ロックが解除される
println!("{:?}", my_data); // RefCell { value: "初期値が変更されました!" }
君が考えているように、Rustでは「変数にmutがなくても中身をいじる」ことは、これらの特別な内部可変性の型を使って、意識的かつ安全性を担保した上でのみ許可される、というわけだね!
この設計のおかげで、Rustはほとんどの状況でコンパイル時に安全性をチェックし、特別な場合だけ実行時の安全チェック(RefCellのパニックやMutexのロック)に頼るという、ゼロコスト抽象化を実現しているんだ!これは情報工学的に見ても、非常に洗練されたデザインだよ!