ミュータビリティ

Rustにおけるミュータビリティ、何かを変更する能力は、他のプログラミング言語とはすこし異なっています。 ミュータビリティの一つ目の特徴は、それがデフォルトでは無いという点です:

fn main() { let x = 5; // x = 6; // error! x = 6; // エラー! }
let x = 5;
x = 6; // エラー!

mut キーワードによりミュータビリティを導入できます:

fn main() { let mut x = 5; // x = 6; // no problem! x = 6; // 問題なし! }
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パターン の一部を成すことに十分注意してください。 つまり、次のようなことが可能です:

fn main() { let (mut x, y) = (5, 6); fn foo(mut x: i32) { } }
let (mut x, y) = (5, 6);

fn foo(mut x: i32) {

内側 vs. 外側のミュータビリティ

一方で、Rustで「イミュータブル(immutable)」について言及するとき、変更不可能であることを意味しない: 「外側のミュータビリティ(exterior mutability)」を表します。例として、Arc<T> を考えます:

fn main() { use std::sync::Arc; let x = Arc::new(5); let y = x.clone(); }
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)を持ちます。 例えば:

fn main() { use std::cell::RefCell; let x = RefCell::new(42); let y = x.borrow_mut(); }
use std::cell::RefCell;

let x = RefCell::new(42);

let y = x.borrow_mut();

RefCellでは borrow_mut() メソッドによって、その内側にある値への &mut 参照を配ります。 それって危ないのでは? もし次のようにすると:

fn main() { use std::cell::RefCell; let x = RefCell::new(42); let y = x.borrow_mut(); let z = x.borrow_mut(); (y, z); }
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 は作れないということです。

fn main() { struct Point { x: i32, // mut y: i32, // nope mut y: i32, // ダメ } }
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> を使えば、フィールド・レベルのミュータビリティをエミュレートできます。

fn main() { 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); }
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 を更新できました。