RefCell
Rust在編譯階段會進行嚴格的借用規則檢查,規則如下:
- 在任意給定時間,要么只能有一個可變引用,要么只能有多個不可變引用。
- 引用必須總是有效。
即在編譯階段,當有一個不可變值時,不能可變的借用它。如下代碼所示:
fn main() {
let x = 5;
let y = &mut x;
}
會產生編譯錯誤:
error[E0596]: cannot borrow immutable local variable `x` as mutable
--> src/main.rs:32:18
|
31 | let x = 5;
| - consider changing this to `mut x`
32 | let y = &mut x;
| ^ cannot borrow mutably
但是在實際的編程場景中可能會需要在有不可變引用時改變數據的情況,這時可以考慮Rust中的內部可變性。其借用規則檢查由編譯期推遲到運行期。對應的,在編譯期借用規則檢查不通過,則會產生編譯錯誤;而運行期借用規則檢查不通過,則會panic
,且有運行期的代價。
所以實際代碼中使用RefCell<T>
的情況是當你確定你的代碼遵循借用規則,而編譯器不能理解和確定的時候。代碼仍然要符合借用規則,只不過規則檢查放到了運行期。
RefCell代碼實例1:
use std::cell::RefCell;
fn main() {
let x = RefCell::new(5u8);
assert_eq!(5, *x.borrow());
{
let mut y = x.borrow_mut();
*y = 10;
assert_eq!(10, *x.borrow());
let z = x.borrow(); //編譯時會通過,但運行時panic!
}
}
運行結果:
thread 'main' panicked at 'already mutably borrowed: BorrowError', libcore/result.rs:983
:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.
可以看到在運行時進行了借用檢查,並且panic!
RefCell代碼實例2:
#[derive(Debug, Default)]
struct Data {
a: u8,
b: RefCell<u8>,
}
impl Data {
// 編譯通過
pub fn value_b(&self) -> u8 {
let mut cache = self.b.borrow_mut();
if *cache != 0 {
return *cache;
}
*cache = 100;
*cache
}
//編譯錯誤:cannot mutably borrow field of immutable binding
pub fn value_a(&self) -> u8 {
if self.a != 0 {
return self.a;
}
self.a = 100;
self.a
}
}
fn main() {
let value = Data::default();
println!("{:?}", value);
value.value_b();
println!("{:?}", value);
}
把value_a
注釋掉運行結果如下:
Data { a: 0, b: RefCell { value: 0 } }
Data { a: 0, b: RefCell { value: 100 } }
很多時候我們只能獲取一個不可變引用,然而又需要改變所引用數據,這時用RefCell<T>
是解決辦法之一。
內部可變性
內部可變性(Interior mutability)是Rust中的一個設計模式,它允許你即使在有不可變引用時改變數據,這通常是借用規則所不允許的。為此,該模式在數據結構中使用unsafe代碼來模糊Rust通常的可變性和借用規則。當可以確保代碼在運行時會遵守借用規則,即使編譯器不能保證的情況,可以選擇使用那些運用內部可變性模式的類型。所涉及的 unsafe 代碼將被封裝進安全的 API 中,而外部類型仍然是不可變的。