前言
本篇文章以最短的文字囊括 Rust 可变性的主要知识点。
声明可变变量
我们在理解它的时候需要把 mut x
理解为一个整体,所以我们在模式解构元组的时候会这么写:
1
|
let (mut a, mut b) = (1, 2);
|
在函数绑定中它的行为和 C++ 的 const 函数的规则类似:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
struct Test {}
impl Test {
fn test_fn1(&self) -> i32 {
1
}
fn test_fn2(&mut self) -> i32 {
2
}
}
fn main() {
let t = Test {};
let mut t2 = Test {};
t.test_fn1();
t2.test_fn1();
t2.test_fn2();
}
|
不可变变量 t 只能调用 &self 函数,而可变的 t2 可以调用 &self 和 &mut self 的函数。
不过这种程度显然不能满足开发者的需求,假如 Rust 的引用计数智能指针 Rc:
1
2
3
4
5
6
7
8
9
10
|
extern crate hello_rust;
use std::rc::Rc;
fn main() {
let rc1 = Rc::new(1);
println!("rc count: {}", Rc::strong_count(&rc1));
let rc2 = rc1.clone();
println!("rc count: {}", Rc::strong_count(&rc2));
}
|
没有 mut 好像它的内部数据依然是可以改变的。这就涉及到 Rust 中内部可变性(interior mutability)的概念。
(这里排除自己手写 unsafe 代码)
内部可变性
前面提到有些时候业务上就是需要像 Rc 一样具有内部可变性的类型,Rust 的解决方案是提供 std::cell::UnsafeCell 类型:
1
2
3
4
5
6
7
|
#[lang = "unsafe_cell"]
#[repr(transparent)]
#[repr(no_niche)]
pub struct UnsafeCell<T>
where
T: ?Sized,
{ /* fields omitted */ }
|
看到 #[lang = “xxx”] 我们就知道它会被编译器特别关照。该类型的 get 方法可以从 &self 中拿到 *mut T 指针,这也是我们实现内部可变性的重要途径。
1
2
3
4
5
6
7
8
9
|
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_unsafecell_get", since = "1.32.0")]
pub const fn get(&self) -> *mut T {
// We can just cast the pointer from `UnsafeCell<T>` to `T` because of
// #[repr(transparent)]. This exploits libstd's special status, there is
// no guarantee for user code that this will work in future versions of the compiler!
self as *const UnsafeCell<T> as *const T as *mut T
|
排除多线程情况下的安全问题主要靠 !Sync:
1
2
|
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> !Sync for UnsafeCell<T> {}
|
这样所有需要 Sync 约束类型的地方 UnsafeCell 全都无法直接使用。
我们在实际使用的时候可以直接用 Cell 或者 RefCell:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
extern crate hello_rust;
use std::cell::Cell;
fn main() {
let data = Cell::new(1);
let p = &data;
data.set(2);
println!("{}", p.get());
p.set(3);
println!("{}", data.get());
}
|
这里面在程序上的逻辑在于对于需要内部可变的类型 T,我们把它用 Cell 包裹,在平时我们的所有引用都在 Cell 上,这样对于 Cell 的外部来说没有任何逻辑冲突。而因为在 Cell 内部我们利用了 Rust 提供的 UnsafeCell 得以实现对内部数据的修改。