🍉 加载中...


Rust 语言内存管理小记 😭

8 minute read

概述

  • std::mem::forget:该函数用于“忘记”给定的值,即在不运行该值的析构函数的情况下释放该值的所有权。
  • Box::leak:该函数可以将 Box 包装的变量转换为静态变量,从而延长变量的生命周期。
  • std::cell::RefCell:可以在运行时动态地检查和修改数据,非线程安全。
  • std::rc::Rc:可在多个线程间共享变量,但需要注意线程安全问题。
  • std::sync::Arc:Arc:Rc 的线程安全版本。

Forget

std::mem::forget 是一个函数,它用于“忘记”给定的值。这个函数接受一个类型为 T 的值作为参数,并在不运行该值的析构函数的情况下释放该值的所有权(回收变量占用的空间)。这意味着,如果一个值被传递给 std::mem::forget 函数,它将不会被释放,并可能导致内存泄漏或其他问题。

当底层资源的所有权先前被转移到 Rust 之外的代码,例如通过将原始文件描述符传输到 C 代码时,这很有用。

示例

 1use std::mem;
 2
 3struct MyType {
 4    value: i32,
 5}
 6
 7impl Drop for MyType {
 8    fn drop(&mut self) {
 9        println!("Dropping MyType({})", self.value);
10    }
11}
12
13fn main() {
14    let my_value = MyType { value: 42 };
15
16    // 忘记 my_value 的所有权
17    mem::forget(my_value);
18
19    println!("The program is still running");
20}

在这个例子中,定义了一个名为 MyType 的结构体,它包含一个 value 字段并实现了 Drop trait。Drop trait 的实现在值被释放时打印一条消息。在 main 函数中,我们创建一个 MyType 的值,并使用 std::mem::forget 函数来忘记它的所有权。这意味着在程序运行期间,该值的 Drop 实现不会被调用,即使该值已经离开了它的作用域。因此,在该程序中,我们只会看到打印的第一条消息,“Dropping MyType(42)” 不会被打印。

Leak

Box::leak 是一个 unsafe 函数,用于将 Box 中的值 泄漏 到堆上,以将其变为 静态生命周期。它的函数声明如下:

1pub fn leak<T: ?Sized>(b: Box<T>) -> &'static mut T

其会获取一个 Box<T>,并返回一个指向堆上分配的 T&'static mut T 指针。注意,其返回的是一个 静态生命周期 的引用。

示例

1fn main() {
2    let x = Box::new(10);
3    let p = Box::leak(x);
4    assert_eq!(*p, 10);
5    
6    // p 在程序结束前不会释放内存
7}

在这个例子中,我们将 Box<i32> 类型的 x 变量传递给 Box::leak 函数,其返回一个指向静态生命周期的 i32 类型的指针的引用。

RefCell

RefCell 实现了 内部可变性(Interior Mutability),它允许在不可变引用的前提下改变值,这是通过运行时的借用检查来实现的。它是在运行时检查可变性(而不是在编译时),这样可以更灵活地控制可变性,并且可以在必要的时候避免复制数据。

RefCell 类似于一个容器,可以通过 borrow 方法获得一个可变引用或者一个不可变引用。获取可变引用后,可以改变容器中的值,但是如果同一时间存在其他的引用,它们将不再是有效的引用,这时候程序将 panic

示例

1use std::cell::RefCell;
2
3let x = RefCell::new(42);
4let y = x.borrow(); // 获取一个不可变引用
5let mut z = x.borrow_mut(); // 获取一个可变引用
6
7*z = 13; // 改变容器中的值

本文番外章节中,有介绍到它的亲戚 Cell,闲时可以了解一下。

RC & ARC

RC 是 Rust 中的引用计数指针,它允许多个所有权持有者共享同一个值,并在没有任何所有权持有者时自动释放它。但是,RC 只能用于单线程环境,因为它不能保证并发操作的安全性。当有一个新的所有权持有者时,RC 会增加计数器,当计数器为零时,它将释放指向堆上数据的内存。

Arc 是 Rust 中的原子引用计数指针,与 RC 基本相同。主要的差异是 Arc 是线程安全的,即可以在不同的线程之间安全地共享数据。性能方面,Arc 比 RC 慢,因为它需要原子性地增加和减少计数器,以确保并发访问时的安全性。

RC 和 ARC 都是可变的,这意味着它们可以修改内部值。不当使用 Arc 和 RC 可能会导致循环引用,务必小心。

Cell

std::cell::Cell 是一个提供了内部可变性的类型。它允许你在不可变变量内部修改值,但是其限制了这个值的访问范围。具体来说,Cell 中的值可以被修改,但只能通过方法调用来访问。

Cell 通常用于一些需要在不可变变量中存储可变状态的情况,例如在多线程编程中。它的方法包括:

  • get:获取 Cell 中的值的不可变引用;
  • set:将 Cell 中的值设置为新的值;
  • replace:将 Cell 中的值替换为新值,并返回旧的值;
  • take:将 Cell 中的值替换为默认值,并返回旧的值;
  • into_inner:将 Cell 中的值移出 Cell 并返回它。

需要注意的是,Cell 中存储的值必须实现 Copy trait,这意味着它们必须是可以复制的类型,例如整数、浮点数和指针类型。如果需要存储的值不支持 Copy trait,可以考虑使用 RefCellMutex 等类型来实现内部可变性。

番外

内部可变性

实现了内部可变性的有 CellRefCellMutex 等,此处就不一一列举了。

终末

有兴趣的话,可以去了解一下 lazy_staticstatic_cell 这两个包,也蛮有意思的。