青蛙小白
目录

Rust的内存管理

所有权(ownership)

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

智能指针Box

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

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

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

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

引用计数(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两个变体。

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

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的写时克隆行为,只有在需要修改数据时才会进行克隆,从而提高了效率。

use std::borrow::Cow;

struct Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
    values: Cow<'a, [X]>,
}

impl<'a, X: Clone + 'a> Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
    fn new(v: Cow<'a, [X]>) -> Self {
        Items { values: v }
    }
}

// Creates a container from borrowed values of a slice
let readonly = [1, 2];
let borrowed = Items::new((&readonly[..]).into());
match borrowed {
    Items { values: Cow::Borrowed(b) } => println!("borrowed {b:?}"),
    _ => panic!("expect borrowed value"),
}

let mut clone_on_write = borrowed;
// Mutates the data from slice into owned vec and pushes a new value on top
clone_on_write.values.to_mut().push(3);
println!("clone_on_write = {:?}", clone_on_write.values);

// The data was mutated. Let's check it out.
match clone_on_write {
    Items { values: Cow::Owned(_) } => println!("clone_on_write contains owned data"),
    _ => panic!("expect owned data"),
}

Rc/Arc make_mut

make_mut方法的工作方式是:

  • 检查引用计数是否为 1。
  • 如果是1,则可以直接获取内部数据的可变引用,无需克隆。
  • 如果引用计数大于1,则会克隆内部数据,并将引用计数重置为1,然后返回克隆数据的可变引用。
use std::rc::Rc;
fn main() {
    let mut data = Rc::new(5);

    *Rc::make_mut(&mut data) += 1; // Won't clone anything
    let mut other_data = Rc::clone(&data); // Won't clone inner data
    *Rc::make_mut(&mut data) += 1; // Clones inner data
    *Rc::make_mut(&mut data) += 1; // Won't clone anything
    *Rc::make_mut(&mut other_data) *= 2; // Won't clone anything

    // Now `data` and `other_data` point to different allocations.
    assert_eq!(*data, 8);
    assert_eq!(*other_data, 12);
}

Rust Container Cheet Sheet

评论