可變性

可變性(mutability),就是改變事物的能力,它在 Rust 中的運作方式跟其他語言不太一樣。 第一個方面就是 Rust 的可變性並不是預設狀態:

let x = 5;
x = 6; // error!

我們可以透過 mut 關鍵字來使用可變性:

let mut x = 5;

x = 6; // no problem!

這是一個可變的(mutable)變數綁定。 當一個綁定是可變的,代表你被允許改變它所指向的內容。 所以在上述範例中,x 的值並沒有太多改變,只是從一個 i32 換到另一個而已。

如果你想改變綁定指向的東西,你需要 可變參照(mutable reference):

let mut x = 5;
let y = &mut x;

y 是個指向一個可變 reference 的不可變(immutable)綁定,這代表你不能把 y 綁定到其他東西上(例如 y = &mut z),但你可以改變綁定到 y 上的變數的值(*y = 5)。 這有一些微妙的不同。

當然,如果你兩種都想要可變:

let mut x = 5;
let mut y = &mut x;

現在 y 可以綁到其他變數上,且它所參照的值也可以被改變。

很重要的是 mut 也是 模式(pattern)的一部份,所以你可以像這樣做:

let (mut x, y) = (5, 6);

fn foo(mut x: i32) {
# }

內部及外部可變性(Interior vs. Exterior Mutability)

然而,當我們提到 Rust 中有些東西是「不可變」的同時,這不代表它不能真的被改變:我們說它有著「外部可變性」(exterior mutability)。 以 Arc<T> 為例:

use std::sync::Arc;

let x = Arc::new(5);
let y = x.clone();

當我們呼叫 clone() 時,Arc<T> 需要更新參照計數器。 沒錯,我們在此處沒有使用任何 mutx 是個不可變綁定,而且我們沒有使用 &mut 5 或其他東西。 那麼是為什麼呢?

要明白這些,我們要回去看 Rust 的指導哲學的核心是什麼,就是記憶體安全性,以及 所有權 系統,和更特別的 借用 這些 Rust 用來保證其安全的機制:

你可能有以下兩種借用的其中一種,但是不能同時使用兩者:

  • 一到多個對資源的 references(&T
  • 唯一一個可變 reference(&mut T

所以,「不可變性」(immutability)的真正定義是:當兩個參照指向同一個資源時安全嗎? 在 Arc<T> 的例子中,是的沒錯:改變完全被包含在結構自身當中。 與使用者無關。 因為這個原因,它會在 clone() 時提供 &T。 如果它提供 &mut T,那就會是個問題。

其他型別,像是 [std::cell]stdcell 模組中的這個,有相反的屬性:內部可變性(interior mutability)。 例如:

use std::cell::RefCell;

let x = RefCell::new(42);

let y = x.borrow_mut();

RefCell 替其內部的東西提供 &mut 參照給 borrow_mut() 方法。 這難道不危險嗎? 如果我們這樣做:

use std::cell::RefCell;

let x = RefCell::new(42);

let y = x.borrow_mut();
let z = x.borrow_mut();
# (y, z);

實際上這會在執行時引發 panic。 這是 RefCell 做的事情:它在執行期強制使用 Rust 得借用規則,且在有違反規則時 panic!。 這使我們可以繞開 Rust 的可變性規則的另一面。 讓我們來說明它。

欄位等級的可變性(Field-level mutability)

可變性,它不是借用(&mut)就是綁定(let mut)的一個屬性。 這代表你不可能有某些欄位可變、而某些欄位不可變的 結構體(struct)。

struct Point {
    x: i32,
    mut y: i32, // nope
}

結構體的可變性在於它的綁定:

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`

但是,透過使用 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

commit 024aa9a