Rust的内存管理 #
所有权(ownership) #
- 程序的内存布局, linux进程内存分布简介
- 所有权规则
- Move和Copy语义
- 引用和借用
- 从几个示例代码理解所有权
- 内存相关的3个trait, Clone, Copy, Drop
- 引用的生命周期
- 什么是智能指针
- 编译时静态检查和运行时动态检查
智能指针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。
Cow
的Owned
变体与Box
有相似之处,两者都用于存储有所有权的数据,但关键区别在于Cow
的目的是延迟克隆,而Box
只是在堆上分配数据。Cow
只在需要修改数据时才进行克隆。
关于在Cow
中使用Box
、Rc
或 Arc
:
- 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}