前言
阅读耗时2分钟
目录
一、相关概念
- 指针
- 一个变量在内存中包含的是一个地址,也就是指向其它数据
Rust
中最常见的指针就是"引用",使用&
,只是借用数据
- 智能指针
- 行为和指针一样,但是有额外的元数据和功能,例如引用计数智能指针、
String
、Vec<T>
,都拥有一片内存区域,允许用户对其操作 - 一般都是拥有它所指向的数据
Rust
中存在不可变引用和可变引用(mut
),同一时刻不能存在多个不可变引用,不能存在一个可变和一个不可变引用。智能指针的存在就是可以存在多个指针指向同一个引用,并且修改,即允许多重所有权场景。但是这种场景的使用一般只限于单线程。
- 行为和指针一样,但是有额外的元数据和功能,例如引用计数智能指针、
二、智能指针实现
- 智能指针通常使用了
struct
实现,并且实现了Deref
和Drop
这两个Trait
Deref Trait
- 允许智能指针struct的实例像引用一样使用
Drop Trait
- 允许你自定义当智能指针实例走出作用域时的代码
- 允许你自定义当智能指针实例走出作用域时的代码
后面主要介绍一下几个智能指针类型及其使用
Box<T>
- 在
heap
内存上分配
- 在
Rc<T>
- 启用多重所有权的引用计数类型
Ref<T>
和RefMut<T>
- 通过
RefCell<T>
访问,在运行时而不是编译时强制借用规则的类型
- 通过
三、Box
应用场景
在编译时,某类型的大小无法确定,使用该类型时,上下文需要知道它的确切大小
当你有大量数据,想移交所有权,但需要确保在操作时数据不会被复制
使用某个值,你只关心它是否实现了特定trait
,而不关心它的具体类型
使用
fn main() {
let b = Box::new(5);
}//Box 指针和内存都会被释放
通过Box
将5
存储在堆上了
enum List {
Cons(i32, List),
Nil,
}
上述代码会报错,原因就是 recursive type
List has infinite size
,我们就可以通过Box
修改一下,可以存放指针,指针大小是确定的。
use crate::List::{Cons, Nil};
fn main() {
let b = Box::new(5);
let list = Cons(1,
Box::new(Cons(2,
Box::new(Cons(3,
Box::new(Nil))))));
}
enum List {
Cons(i32, Box<List>),
Nil,
}
综上,我们可以看出来
Box
它没有性能开销,只提供了间接存储,和heap
内存分配功能,它实现了Deref Trait
(能够当做引用一样使用)和Drop Trait
(内存在生命周期到达后自动释放)。
四、Deref Trait
这个可以理解为解引用,类似C++
的 *
,只有实现了Deref Trait
,才能够像常规引用一样来使用。
fn main() {
let x = 5;
let y = &x;
assert_eq!(x, *y);
}
上面代码等价于下面
fn main() {
let x = 5;
let y = Box::new(x);
assert_eq!(x, *y);
}
接下来自定义个类似Box::new
功能,实现Deref Trait
use std::ops::Deref;
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(x, *y);
}
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x:T) -> MyBox<T>{
MyBox(x)
}
}
impl <T> Deref for MyBox<T> {
type Target = T;//关联类型
fn deref(&self) -> &T{
&self.0
}
}
两者结果一致,都相等,就是为MyBox
实现了一个deref
解引用方法。
并且解引用这个操作,编译器在编译时会隐式转换,例如
fn main() {
let m = MyBox::new(String::from("Rust"));
// m 是MyBox类型,编译器会制动将 &MyBox<String> deref 成 &String, String deref 后就是 &str
hello(&m);
hello("Rust");
}
fn hello(name: &str){
println!("{}", name);
}
五、Drop Trait
实现了Drop Trait
,这个智能指针离开作用域就能被释放内存。我们也可以自定义值将要离开作用域时发生的动作。
fn main() {
let c = CustomSmartPointer{data: String::from("my stuff")};
println!("CustomSmartPointers created");
}
struct CustomSmartPointer{
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping with data {}", self.data);
}
}
//输出:
//CustomSmartPointers created
//Dropping with data my stuff
我们也可以使用std::mem::drop
函数,提前drop
,并且不需要担心被drop
多次。
六、Rc引用计数智能指针
应用场景
需要在heap上分配数据,这些数据被程序的多个部分读取但在编译时无法确定哪个部分最后使用完这些数据
Rc
用法有Rc::clone(&a)
,增加引用计数,只增加引用,不会执行数据的深度拷贝操作Rc::strong_count(&a)
,获得引用计数Rc::weak_count
函数
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after create a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after create b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after create c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
enum List {
Cons(i32, Rc<List>),
Nil,
}
//输出
//count after create a = 1
//count after create b = 2
//count after create c = 3
//count after c goes out of scope = 2
Rc<T>
这种引用是不可变的,只能读,不能写,这样就避免了多个不可变引用写数据资源产生竞争,不同步问题,只能用于单线程场景。
七、RefCell及内部可变性
之前说到Rc<T>
不可变引用,对应肯定会有可变引用。
内部可变性是Rust
的设计模式之一,它允许你在持有不可变引用的前提下对数据进行修改。
- 具体主要是用
unsafe
代码来绕过Rust
正常的可变性规则和借用规则。
- 借用规则。在任何给定的时间内,只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。
fn main() {
let message = RefCell::new(vec![]);
message.borrow_mut().push(String::from("hello1"));
message.borrow().len();
}
RefCell<T>
会记录当前存在多少个活跃的Ref<T>
和RefMut<T>
智能指针borrow
方法,返回智能指针Ref<T>
,实现了Deref
,每调一次,不可变引用计数加1,离开作用域时被释放,不可变引用减1borrow_mut
方法,返回智能指针RefMut<T>
,实现了Deref
,每调一次,可变引用计数加1,离开作用域时,可变引用减1
八、循环引用内存泄漏
Rust
的内存安全机制可以保证很难发生内存泄漏,但不是不可能
例如使用Rc<T>
和RefCell<T>
就可能创造出循环引用,从而发生内存泄漏(每个项的引用数量不会变成0,值也不会被处理掉)
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("rc count = {}", Rc::strong_count(&a));
println!("tail = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b = {}",Rc::strong_count(&a));
println!("b init rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail(){
*link.borrow_mut() = Rc::clone(&b); // a的第二元素个引用指向b
}
println!("b rc count after change = {}",Rc::strong_count(&b));
println!("a rc count after change = {}", Rc::strong_count(&a));
}
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>>{
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
结果
rc count = 1
tail = Some(RefCell { value: Nil })
a rc count after b = 2
b init rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after change = 2
a rc count after change = 2
上面已经造成了循环引用了。a
的第二个元素持有b
, b
的第二个元素指向a
我们只需要在末尾加一行打印,就堆栈溢出了
println!("a next item = {:?}", a.tail());
防止内存泄漏的解决办法
- 自己检查
Rc<T>
强引用 改成Weak<T>
弱引用形式。通过调用Rc::downgrade
方法,可以创建弱引用,weak_count
加1,Rc<T>
计数要为0才会被释放内存,而Weak<T>
则不是。
fn main(){
let leaf = Rc::new(Node{
value: 3,
parent:RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
let branch = Rc::new(Node{
value: 5,
parent:RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}
#[derive(Debug)]
struct Node{
value: i32,
parent:RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
输出
leaf parent = None
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) }, children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) }, children: RefCell { value: [] } }] } })