Rust 所有權(六):內部可變性
內部可變性(Interior Mutability Pattern)是 Rust 語言中的一種編程模式。
下面是一段 JavaScript 代碼:
// JavaScript
const point = { x: 0, y: 0 };
// 錯誤!常量 point 不允許重新賦值
point = { x: 1, y: 1 };
// 正確,允許設置屬性 x 和 y 的值
point.x = 1;
point.y = 1;
第 5 行的錯誤好理解,因爲 point 是常量,不允許重新賦值。
第 8、9 行,雖說 point 是常量,但是仍然可以隨意改變屬性 x 和 y 的值。不知道大家怎麼看待該行爲,是預料之中呢還是感到意外。
作爲對比,在 Rust 中是不允許出現第 8、9 行的行爲的。示例:
// Rust
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 0, y: 0 };
// 錯誤!變量 p 是不可變的,不允許重新賦值
p = Point { x: 1, y: 1 };
// 錯誤!p 是不可變的,不允許重新賦值
p.x = 1;
p.y = 1;
能夠看到,因爲 p 是不可變的,所以不允許對其及其屬性重新賦值。
若想要能夠改變 p 的值,首選的做法是將其聲明爲可變的。示例:
// Rust
let mut p = Point { x: 0, y: 0 };
// ^^^
// 正確
p = Point { x: 1, y: 1 };
// 正確
p.x = 1;
p.y = 1;
有可能實現和 JavaScript 中一樣的行爲嗎?換句話說,在 p 是不可變的情況下能夠修改屬性 x 和 y 的值。答案是使用「內部可變性」模式。示例:
// Rust
use std::cell::Cell;
struct Point {
x: Cell<i32>,
y: Cell<i32>,
}
fn main() {
let p = Point {
x: Cell::new(0),
y: Cell::new(0),
};
p.x.set(1);
p.y.set(1);
// Point {
// x: Cell { value: 1 },
// y: Cell { value: 1 },
// }
}
內部可變性是通過將值包裹在特殊的 Cell 結構體中來實現的。Cell 適用於具有 Copy 語義的值,例如各種標量類型 bool、i32、f64 等。它的 API 比較直接,直達內部的值。
Rust 還提供了另一個更通用的 RefCell 類型,它通過(可變或不可變)借用來操作內部值。和 Cell 的 API 相比它的 API 是間接的,因爲使用了指針。示例:
// Rust
use std::cell::RefCell;
struct Point {
x: RefCell<i32>,
y: RefCell<i32>,
}
fn main() {
let p = Point {
x: RefCell::new(0),
y: RefCell::new(0),
};
*p.x.borrow_mut() = 1;
*p.y.borrow_mut() = 1;
// Point {
// x: RefCell { value: 1 },
// y: RefCell { value: 1 }
// }
}
內部可變性模式既沒有違反所有權規則,也沒有違反之前介紹的借用規則。Cell 會在編譯時執行所有權檢查,RefCell 則在運行時動態地執行所有權的檢查。因此,RefCell 有運行時的開銷,並且如果在運行時發現違背了所有權規則,則會強制終止程序的運行。可見,內部可變性這種模式要小心使用。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/vuNsowGCObdIIvwB4egcNQ