Rust的内存管理

Rust的内存管理 #

所有权(ownership) #

Memory Management in RustOwnership • Borrowing • LifetimesStackOwnerHeapDataownsReferences&mut (exclusive)& (shared)

智能指针Box #

Rust的Box是一种智能指针。它的主要目的是提供一种在堆上分配数据的方法。

在Rust中,在堆上分配数据的两种主要方法是使用Vec或Box。Box的功能非常有限,它只处理它所持有对象的内存分配和释放,它就是这么设计的。

在使用Box时,通常会在Option中保留一个Box,以防Boxed数据可能不存在的情况。例如:

1#[derive(Debug)]
2struct Node {
3    value: i32,
4    parent: Option<Box<Node>>,
5}

引用计数(Rc, Arc) #

使用Box可以强制将数据分配在堆上,然后栈上放一个指针指向并拥有这个数据,堆内存中数据的生命周期与栈上指针的生命周期一致。 在单一所有权下有了Box<T>,String,Vec<T>等智能指针,就可以利用栈内存的自动管理实现堆内存的自动管理。

在实际中还是会存在单个值需要有多个所有者的使用场景。引用计数是内存管理中的一种常见技术,用于跟踪指针的副本数,当没有更多副本时, 内存将被释放。实现通常依赖于保留给定指针副本数的静态计数器,并在每次制作新副本时增加计数器。当副本被销毁时,计数器会减少。如果计数器达到零,则可以释放内存,因为这意味着没有指针的更多副本,因此内存不再使用或可访问。

Rust提供了两种不同的引用计数指针:

  • Rc - 单线程、引用计数的智能指针,可以实现对象的共享所有权
  • Arc - 多线程、引用计数的智能指针,可以实现跨线程对象的共享所有权

内部可变性: Cell和RefCell #

提示 应该不会经常需要使用RefCell或Cell。如果发现需要试图使用它们来绕过借用检查器,我们可能需要重新考虑正在做什么。 它们主要用于特定情况,例如容器和保存需要可变访问的数据的数据结构。

Cell和RefCell的一个限制是它们仅适用于单线程应用程序。如果需要跨执行线程的安全性,则可以使用Mutex或RwLock,它们提供相同的功能以启用内部可变性 ,但可以跨线程使用。它们通常与Arc而不是Rc对应。

Interior Mutability(内部可变性) #

内部可变性(Interior mutability)是Rust中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。 为了改变数据,该模式在数据结构中使用unsafe代码(不安全代码)来模糊Rust通常的可变性和借用规则。 当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的unsafe代码将被封装进安全的API中,而外部类型仍然是不可变的。

写时克隆(Clone on Write) #

Rust提供了三个智能指针来帮助实现写入时克隆:

  • Cow - 基于枚举的智能指针,提供了对写时克隆的便捷支持
  • Rc和Arc - 这两个引用计数的智能指针通过make_mut方法提供写时克隆语义
    • Rc是单线程版本
    • Arc是多线程版本

Cow #

Cow是Rust标准库中的枚举,包含Borrowed和Owned两个变体。

1pub enum Cow<'a, B> where
2    B: 'a + ToOwned + ?Sized,  {
3    Borrowed(&'a B),
4    Owned(<B as ToOwned>::Owned),
5}

Cow(Clone-on-Write)是一个枚举,用于处理借用或拥有的数据,从而避免不必要的克隆。

其中:

  • 'a是生命周期参数,表示借用数据的生命周期。
  • B是被借用或拥有的类型,它必须实现ToOwned trait,该trait定义了如何从借用类型创建拥有类型。
  • Borrowed(&'a B) 表示Cow持有对类型B的借用。
  • Owned(<B as ToOwned>::Owned) 表示 Cow 持有类型B的拥有值。

对于Owned变体与Box非常相似,不同的是使用Cow时,数据不一定分配到堆上。如果想要使用Cow在堆上分配数据,则需要在Cow中使用Box,或者改用Rc或Arc。

CowOwned变体与Box有相似之处,两者都用于存储有所有权的数据,但关键区别在于Cow的目的是延迟克隆,而Box只是在堆上分配数据。Cow只在需要修改数据时才进行克隆。

关于在Cow中使用BoxRcArc

  • Box: 如果需要将Cow中的数据明确地放在堆上,并且不需要共享所有权,那么可以使用Box。例如:Cow<'a, Box<str>>Cow<'a, Box<[u8]>>
  • Rc/Arc: 如果需要在多个所有者之间共享Cow 中的数据,并且需要自动的内存管理(引用计数),那么可以使用Rc(单线程)或Arc(多线程)。例如:Cow<'a, Rc<str>>Cow<'a, Arc<[u8]>>

Cow的写时克隆行为,只有在需要修改数据时才会进行克隆,从而提高了效率。

 1use std::borrow::Cow;
 2
 3struct Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
 4    values: Cow<'a, [X]>,
 5}
 6
 7impl<'a, X: Clone + 'a> Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
 8    fn new(v: Cow<'a, [X]>) -> Self {
 9        Items { values: v }
10    }
11}
12
13// Creates a container from borrowed values of a slice
14let readonly = [1, 2];
15let borrowed = Items::new((&readonly[..]).into());
16match borrowed {
17    Items { values: Cow::Borrowed(b) } => println!("borrowed {b:?}"),
18    _ => panic!("expect borrowed value"),
19}
20
21let mut clone_on_write = borrowed;
22// Mutates the data from slice into owned vec and pushes a new value on top
23clone_on_write.values.to_mut().push(3);
24println!("clone_on_write = {:?}", clone_on_write.values);
25
26// The data was mutated. Let's check it out.
27match clone_on_write {
28    Items { values: Cow::Owned(_) } => println!("clone_on_write contains owned data"),
29    _ => panic!("expect owned data"),
30}

Rc/Arc make_mut #

make_mut方法的工作方式是:

  • 检查引用计数是否为 1。
  • 如果是1,则可以直接获取内部数据的可变引用,无需克隆。
  • 如果引用计数大于1,则会克隆内部数据,并将引用计数重置为1,然后返回克隆数据的可变引用。
 1use std::rc::Rc;
 2fn main() {
 3    let mut data = Rc::new(5);
 4
 5    *Rc::make_mut(&mut data) += 1; // Won't clone anything
 6    let mut other_data = Rc::clone(&data); // Won't clone inner data
 7    *Rc::make_mut(&mut data) += 1; // Clones inner data
 8    *Rc::make_mut(&mut data) += 1; // Won't clone anything
 9    *Rc::make_mut(&mut other_data) *= 2; // Won't clone anything
10
11    // Now `data` and `other_data` point to different allocations.
12    assert_eq!(*data, 8);
13    assert_eq!(*other_data, 12);
14}

Rust Container Cheet Sheet #

© 2025 青蛙小白 | 总访问量 | 总访客数