注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
Rustにおけるミュータビリティ、何かを変更する能力は、他のプログラミング言語とはすこし異なっています。 ミュータビリティの一つ目の特徴は、それがデフォルトでは無いという点です:
fn main() { let x = 5; // x = 6; // error! x = 6; // エラー! }let x = 5; x = 6; // エラー!
mut
キーワードによりミュータビリティを導入できます:
let mut x = 5; x = 6; // 問題なし!
これはミュータブルな 変数束縛 です。束縛がミュータブルであるとき、その束縛が何を指すかを変更して良いことを意味します。
つまり上記の例では、x
の値を変更したのではなく、ある i32
から別の値へと束縛が変わったのです。
束縛が指す先を変更する場合は、ミュータブル参照 を使う必要があるでしょう:
fn main() { let mut x = 5; let y = &mut x; }let mut x = 5; let y = &mut x;
y
はミュータブル参照へのイミュータブルな束縛であり、 y
を他の束縛に変える(y = &mut z
)ことはできません。
しかし、y
に束縛されているものを変化させること(*y = 5
)は可能です。微妙な区別です。
もちろん、両方が必要ならば:
fn main() { let mut x = 5; let mut y = &mut x; }let mut x = 5; let mut y = &mut x;
今度は y
が他の値を束縛することもできますし、参照している値を変更することもできます。
mut
は パターン の一部を成すことに十分注意してください。
つまり、次のようなことが可能です:
let (mut x, y) = (5, 6); fn foo(mut x: i32) {
一方で、Rustで「イミュータブル(immutable)」について言及するとき、変更不可能であることを意味しない:
「外側のミュータビリティ(exterior mutability)」を表します。例として、Arc<T>
を考えます:
use std::sync::Arc; let x = Arc::new(5); let y = x.clone();
clone()
を呼び出すとき、Arc<T>
は参照カウントを更新する必要があります。しかし、
ここでは mut
を一切使っていません。つまり x
はイミュータブルな束縛であり、
&mut 5
のような引数もとりません。一体どうなっているの?
これを理解するには、Rust言語の設計哲学の中心をなすメモリ安全性と、Rustがそれを保証するメカニズムである 所有権 システム、 特に 借用 に立ち返る必要があります。
次の2種類の借用のどちらか1つを持つことはありますが、両方を同時に持つことはありません。
- リソースに対する1つ以上の参照(
&T
)- ただ1つのミュータブルな参照(
&mut T
)
つまり、「イミュータビリティ」の真の定義はこうです: これは2箇所から指されても安全ですか?
Arc<T>
の例では、イエス: 変更は完全にそれ自身の構造の内側で行われます。ユーザからは見えません。
このような理由により、 clone()
を用いて &T
を配るのです。仮に &mut T
を配ってしまうと、
問題になるでしょう。
(訳注: Arc<T>
を用いて複数スレッドにイミュータブル参照を配布し、スレッド間でオブジェクトを共有できます。)
std::cell
モジュールにあるような別の型では、反対の性質: 内側のミュータビリティ(interior mutability)を持ちます。
例えば:
use std::cell::RefCell; let x = RefCell::new(42); let y = x.borrow_mut();
RefCellでは borrow_mut()
メソッドによって、その内側にある値への &mut
参照を配ります。
それって危ないのでは? もし次のようにすると:
use std::cell::RefCell; let x = RefCell::new(42); let y = x.borrow_mut(); let z = x.borrow_mut();
実際に、このコードは実行時にパニックするでしょう。これが RefCell
が行うことです:
Rustの借用ルールを実行時に強制し、違反したときには panic!
を呼び出します。
これによりRustのミュータビリティ・ルールのもう一つの特徴を回避できるようになります。
最初に見ていきましょう。
ミュータビリティとは、借用(&mut
)や束縛(let mut
)に関する属性です。これが意味するのは、
例えば、一部がミュータブルで一部がイミュータブルなフィールドを持つ struct
は作れないということです。
struct Point { x: i32, mut y: i32, // ダメ }
構造体のミュータビリティは、それへの束縛の一部です。
fn main() { struct Point { x: i32, y: i32, } let mut a = Point { x: 5, y: 6 }; a.x = 10; let b = Point { x: 5, y: 6}; // b.x = 10; // error: cannot assign to immutable field `b.x` b.x = 10; // エラー: イミュータブルなフィールド `b.x` へ代入できない }struct Point { x: i32, y: i32, } let mut a = Point { x: 5, y: 6 }; a.x = 10; let b = Point { x: 5, y: 6}; b.x = 10; // エラー: イミュータブルなフィールド `b.x` へ代入できない
しかし、Cell<T>
を使えば、フィールド・レベルのミュータビリティをエミュレートできます。
use std::cell::Cell; struct Point { x: i32, y: Cell<i32>, } let point = Point { x: 5, y: Cell::new(6) }; point.y.set(7); println!("y: {:?}", point.y);
このコードは y: Cell { value: 7 }
と表示するでしょう。ちゃんと y
を更新できました。