Please enable Javascript to view the contents

[Rust 基础知识]mut 与可变性

 ·  ☕ 2 分钟

前言

    本篇文章以最短的文字囊括 Rust 可变性的主要知识点。

声明可变变量

1
let mut x = 5;

我们在理解它的时候需要把 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 得以实现对内部数据的修改。

分享

saberuster
作者
saberuster
一个喜欢编程和折腾的追风少年