C++中的智能指针我们知道有shared_ptr,unique_ptr,weak_ptr等。rust中你将进一步对智能指针的意义加深了解。
我们先回顾一下指针:它的值是一个内存地址,要想访问它指向的这个内存地址,需要解引用。理论上可以解引用到任意数据类型。
智能指针
智能指针除了指向数据的指针外,还有源数据以提供额外的处理能力。
智能指针与胖指针的区别:智能指针一定是一个胖指针,但胖指针不一定是一个智能指针。
String(智能指针) 对堆上的值有所有权,而 &str(胖指针) 是没有所有权的,这是 Rust 中智能指针和普通胖指针的区别。
pub struct String {
vec: Vec,
}
从String的定义来看明明就是个结构体,怎么就高攀上了智能指针了呢?这是因为String 实现了 我们昨天刚学的 Deref 和 DerefMut 这2个trait,这使得它在解引用的时候,会得到 &str。
impl ops::Deref for String {
type Target = str;
fn deref(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.vec) }
}
}
由于在堆上分配了数据,String 还需要为其分配的资源做相应的回收。而 String 内部使用了 Vec,所以它可以依赖 Vec的能力来释放堆内存。
现在我们发现,在Rust中,但凡是要回收资源,并实现了Deref/DerefMut/Drop这3个trait的数据结构,都是智能指针。
这么说的话,我们发现以下这些都是智能指针
用于在堆上分配内存的Box 和 Vec 、
用于引用计数的 Rc 和 Arc
还有很多其他的数据结构 PathBuf、Cow <'a B>、MutexGuard 等。
BOX
它是 Rust 中最基本的在堆上分配内存的方式,绝大多数其它包含堆内存分配的数据类型,内部都是通过 Box完成的,比如 Vec。
我们先来复习一下C是怎么分配堆内存的。C提供了malloc/calloc/realloc/free 来处理内存分配,但是最后为了安全释放内存,给我们码农造成比较大的心智负担。
C++发现这个问题,于是搞了一个智能指针unique,可以在指针退出作用域的时候释放堆内存,这样保证了堆内存的单一所有权。这就是Box的前身。
我们看到在Box的定义里就有Unique表示借鉴的C++
堆上分配内存的 Box其实有一个缺省的泛型参数 A,就需要满足 Allocator trait,这其实是指定一种内存分配器,并且默认是 Global,当然也可以替换成自己的内存分配器。
Allocator trait 提供很多方法:
allocate 是主要方法,用于分配内存,对应 C 的 malloc/calloc;
deallocate,用于释放内存,对应 C 的 free;
还有 grow / shrink,用来扩大或缩小堆上已分配的内存,对应 C 的 realloc。
再来看下Box的Drop实现:
#[stable(feature = “rust1”, since = “1.0.0”)]
unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box
fn drop(&mut self) {
// FIXME: Do nothing, drop is currently performed by compiler.
}
}
可以看到这是个空的实现。像我们平时开发一样,先确定接口,然后各自去自己的服务里实现自己的逻辑。这是不是就是我们一直说的面向接口编程的思想?
Cow/MutexGuard
Cow<'a, B>
这是用于提供**写时克隆(Clone-on-Write)的一个智能指针,和虚拟内存管理的写时复制很像。**多用于读多写少的场景
代码定义如下:
pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized {
Borrowed(&'a B),
Owned(::Owned),
}
包含一个只读借用,如果调用方需要所有权做修改操作,他就会clone借用的数据。
不太懂,先不细说了。
MutexGuard 是另外一类很有意思的智能指针:它不但通过 Deref 提供良好的用户体验,还通过 Drop trait 来确保,使用到的内存以外的资源在退出时进行释放。
MutexGuard这个结构是在调用 Mutex::lock 时生成的:
pub fn lock(&self) -> LockResult
unsafe {
self.inner.raw_lock();
MutexGuard::new(self)
}
}
先拿锁,拿不到就等。
拿到了,就把Mutex的引用给MutexGuard。
类似 MutexGuard 的智能指针有很多用途。比如要创建一个连接池,你可以在 Drop trait 中,回收 checkout 出来的连接,将其再放回连接池。
Rust中 Box,Rc,arc CELL RefCell之间的区别
Box 是一种独享所有权智能指针,类似C++的unique_ptr.资源分配在堆上,依靠Deref和Drop来管理堆上的资源,零运行时开销
Rc是一种共享所有权智能指针,类似C++的shared_ptr 资源分配在堆上,依靠Deref和Drop来管理堆上的资源,使用引用计数算法
Arc是一种线程安全的共享所有权智能指针,类似C++的shared_ptr + mutex资源分配在堆上,依靠Deref和Drop来管理堆上的资源,使用引用计数算法
Cell和RefCell可以实现内部可变性:
1.Cell包装Copy值,并且没有借用检查;
2.RefCell包装任何类型的值,有运行时借用检查,并且需要用borrow或borrow_mut锁定,分别提供一个不可变或可变引用;
3.Cell和RefCell都不是线程安全的。
RefCell相比Cell,内部维护了一个包装对象的引用计数。当通过RefCell的borrow获取一个不可变引用时,内部引用计数加一,当获取的引用离开作用域,内部引用计数减一;
由于Cell并未引入引用计数,所以Cell需要满足T: Copy。
impl
对于Cell而言,通过get获取到的是原有对象的拷贝,set则使用新的对象替换旧的对象。
use std::cell::Cell;
#[derive(Debug)]
struct Person {
name: String,
age: Cell<usize>,
}
fn main() {
let person = Person {
name: "XiaoMing".to_string(),
age: Cell::new(18),
};
let p = &person;
p.age.set(20);
// Person { name: "XiaoMing", age: Cell { value: 20 } }
println!("{:?}", p);
}
use std::cell::RefCell;
#[derive(Debug)]
struct Person {
name: String,
age: RefCell<usize>,
}
fn main() {
let person = Person {
name: "XiaoMing".to_string(),
age: RefCell::new(18),
};
let p = &person;
*p.age.borrow_mut() = 20;
// Person { name: "XiaoMing", age: Cell { value: 20 } }
println!("{:?}", p);
}
可以看到cell,refcell作用就是,虽p是不可变的借用,但是可以利用cell,refcell实现修改p内部的值。不同的是cell是通过get获取到的是原有对象的拷贝,适合实现了Copy的类型,或者体积小的struct,因为get方法是直接按位复制的。
refcell是通过借用检查返回可变引用。适合未实现了Copy的类型,或者体积大的struct,Cell不好使的都是用这个。