正式开始
指针和引用
- 指针是一个持有内存地址的值,可以通过解引用来访问它指向的内存地址,理论上可以解引用到任意数据类型;
- 引用是一个特殊的指针,它的解引用访问是受限的,只能解引用到它引用数据的类型,不能用作它用
智能指针
- 智能指针是一个表现行为很像指针的数据结构
- 除了指向数据的指针外,它还有元数据以提供额外的处理能力
- 智能指针一定是一个胖指针,但胖指针不一定是一个智能指针
定义
在 Rust 中,凡是需要做资源回收且实现了 Deref/DerefMut/Drop的数据结构都是智能指针
智能指针 String 和 胖指针&str 的区别
- String 多一个 capacity 字段
- String 对堆上的值有所有权,而 &str 是没有所有权的
智能指针与结构体的区别
- String 是用结构体定义的
pub struct String {
vec: Vec<u8>,
}
- String 实现了 Deref 和 DerefMut,这使得它在解引用的时候,会得到 &str。普通的结构体并不一定实现
- String 需要为其分配的资源做相应的回收。它内部使用了 Vec,所以可以依赖 Vec 的能力来释放堆内存
在堆上创建内存的 Box
- Rust 中最基本的在堆上分配内存的方式,绝大多数其它包含堆内存分配的数据类型,内部都是通过 Box 完成的
- Box<T> 的定义里,内部是一个 Unique<T>,它是一个私有的数据结构,我们不能直接使用,它包裹了一个 *const T 指针,并唯一拥有这个指针
- 堆上分配内存的 Box<T> 其实有一个缺省的泛型参数 A,就需要满足 Allocator trait,并且默认是 Global
- 可以使用 #[global_allocator] 标记宏,定义你自己的全局分配器
- 可以实现 GlobalAlloc trait 来撰写自己的全局分配器,它和 Allocator trait 的区别,主要在于是否允许分配长度为零的内存
Cow<'a, B>
- Cow 是 Rust 下用于提供 写时克隆(Clone-on-Write) 的一个智能指针,它跟 虚拟内存管理的写时复制(Copy-on-write) 有异曲同工之妙
- 包裹一个只读借用,但如果调用者需要所有权或者需要修改内容,那么它会 clone 借用的数据
pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
它是一个 enum,可以包含一个对类型 B 的只读引用,或者包含对类型 B 的拥有所有权的数据
pub trait ToOwned {
type Owned: Borrow<Self>;
#[must_use = "cloning is often expensive and is not expected to have side effects"]
fn to_owned(&self) -> Self::Owned;
fn clone_into(&self, target: &mut Self::Owned) { ... }
}
pub trait Borrow<Borrowed> where Borrowed: ?Sized {
fn borrow(&self) -> &Borrowed;
}
- type Owned: Borrow<Self> 是一个带有关联类型的 trait
MutexGuard<T>
-
通过 Deref 提供良好的用户体验
-
通过 Drop trait 来确保,使用到的内存以外的资源在退出时进行释放
-
MutexGuard这个结构是在调用 Mutex::lock 时生成的
pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> { unsafe { // 取得锁资源,如果拿不到,会在这里等待 self.inner.raw_lock(); // 如果拿到了,会把 Mutex 结构的引用传递给 MutexGuard MutexGuard::new(self) } }
新鲜知识点
-
自己写内存分配时不能使用 println!()
a. 因为 stdout 会打印到一个由 Mutex 互斥锁保护的共享全局 buffer 中,这个过程中会涉及内存的分配,分配的内存又会触发 println!(),最终造成程序崩溃。
b. 而 eprintln! 直接打印到 stderr,不会 buffer
-
在使用 Box 分配堆内存的时候要注意
a. Box::new() 是一个函数,传入它的数据会出现在栈上,再移动到堆上
b. 如果我们的 Matrix 结构不是 505 个字节,是一个非常大的结构,就有可能出问题
fn main() { // 在堆上分配 16M 内存,但它会先在栈上出现,再移动到堆上 let boxed = Box::new([0u8; 1 << 24]); println!("len: {}", boxed.len()); } -
一个类型还可以被借用成不同的引用
use std::borrow::Borrow; fn main() { let s = "hello world!".to_owned(); // 这里必须声明类型,因为 String 有多个 Borrow<T> 实现 // 借用为 &String let r1: &String = s.borrow(); // 借用为 &str let r2: &str = s.borrow(); println!("r1: {:p}, r2: {:p}", r1, r2); } -
三种分发手段
a. 使用泛型参数做静态分发
b. 使用 trait object 做动态分发
c. 根据 enum 的不同状态来进行统一分发
好用链接
- String Deref/DerefMut标准库实现
- Vec 的drop实现
- C++中智能指针unique_ptr
- Box<T> 中的Unique<T>
- buddy system 内存分配
- Allocator trait
- jemalloc
- 全局分配器 trait
- std::borrow
- str 对 ToOnwed trait的实现
- String 实现 Borrow
- Cow实现Deref trait
- 序列化库 serde
- Mutex::lock
- Mutex Guard定义
- smart string
- 内存分配器 glibc pthread malloc
- 内存分配器 Google开发的tcmalloc
- 内存分配器 FreeBSD上默认使用的jemalloc