Rust指针:一个变量在内存中包含的是一个地址(即指向其他数据)。
介绍
Rust 中最常见的指针就是 引用:
引用
智能指针
智能指针是这样的一些数据结构:
- 行为和指针类似
- 有额外的元数据和功能
- 很多时候拥有它所指向的数据
智能指针示例:String
、Vec<T>
,特点如下:
- 都拥有一块内存,且允许用户对其操作
- 都拥有元数据,如容量等
- 提供额外的功能或保障(String 保障其数据是合法的 UTF-8 编码)
智能指针的实现:通常使用 struct 实现,并且实现如下 trait:
Deref trait
:允许智能指针 struct 的实例像引用一样使用
Drop trait
:允许自定义当智能指针实例走出作用域时的代码
引用技术(reference counting)智能指针类型
- 通过记录所有者的数量,使一份数据被多个所有者同时持有
- 当没有任何所有者持有数据时,自动清理数据
标准库中常见的智能指针:
Box<T>
Box<T>
:最简单的只能指针,它被定义成拥有一个元素的 tuple struct
- 在 heap 内存上存储数据
- stack 存储指向 heap 数据的指针
- 没有性能开销和其他额外的功能
- 使用场景:
- 大数据量时,想移交所有权,但需要确保在操作时数据不会被复制
- 编译时,某类型的大小无法确定
use std::ops::Deref;
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
}
}
fn main() {
let x: Box<i32> = Box::new(5);
println!("x={}", x);
let x:i32 = 2;
let y: &i32 = &x;
let z: Box<i32> = Box::new(x);
assert_eq!(2, x);
assert_eq!(2, *y);
assert_eq!(2, *z);
let z: MyBox<i32> = MyBox::new(2);
assert_eq!(2, *z);
}
Deref trait
Deref
解引用
Deref trait
可以自定义解引用运算符 *
的行为
- 实现
Deref trait
(需要实现 deref
方法),智能指针可以像常规引用一样来处理
- 实现后,会被隐式的转化为
*(x.deref())
函数和方法的 隐式解引用转化(deref coercion)
隐式解引用转化(deref coercion)
是为函数和方法提供的一种便捷特性
- 若 T 实现了
deref trait
,则 deref coercion
可把 T 的引用转化为 T 经过 deref 操作后生成的引用
- 当把某类型的引用传递给函数或方法时,但它的类型与定义的类型不匹配:
deref coercion
会自动发生
- 编译器会对 deref 进行一系列调用(编译时完成,没有额外性能开销),将其转化为所需的参数类型
如:示例中的 MyBox<String>
的引用可以隐式解引用为 &str
&x: &MyBox<String> -> deref &String -> deref &str
$x -> &(*mybox)[..]
解引用和可变引用
- 可以使用
DerefMut trait
重载可变引用的 *
运算符
- 在类型和 trait 在下列三种情况发生时,Rust会执行 deref coercion
- 当
T: Deref<Target=U>
,允许 &T
转化为 &U
- 当
T: DerefMut<Target=U>
,允许 &mut T
转化为 &mut U
- 当
T: Deref<Target=U>
,允许 &mut T
转化为 &U
Drop trait
Drop trait
可以让自定义当值将要离开作用域时发生的动作,如:关闭文件、网络资源释放等
- 任何类型都可以实现
Drop trait
,需要实现 drop
方法,Drop trait
在预导入模块里(prelude)导入
Drop trait
的 drop
方法不允许手动调用(但可以调用 std::mem::drop()
函数提前手动释放),释放处理时自动清理
#[derive(Debug)]
struct CustomPointer {
data: String,
}
impl Drop for CustomPointer {
fn drop(&mut self) {
println!("data is {}", self.data)
}
}
fn main() {
let c: CustomPointer = CustomPointer{ data: String::from("abcd") };
println!("{:?}", c);
// drop(c);
}
Rc<T>
Rc<T>
:启用多重所有权的引用计数智能指针类型,通过不可变引用,使程序在不同部分之间共享只读数据
- rc 是
reference counting
的缩写
- 一个值会有多个所有者
- 追踪所有值的引用
- 当 0 个引用时,该值可以被清理掉
Rc<T>
不在预导入模块(prelude),需要手动导入 ``
- 方法:
Rc::clone(&a)
增加引用计数
Rc::clone()
增加引用,不会进行数据的深度拷贝操作
- 类型的
.clone()
方法一般指定类型数据的深拷贝操作
Rc::strong_count(&a)
获得引用计数
Rc::weak_count()
弱引用计数
- 使用场景:
- 单线程场景
- 需要在 heap 上分配数据,数据被多个部分读取,编译器无法确认那个部分最后使用
#[derive(Debug)]
enum List1 {
Cons1(i32, Box<List1>),
Nil1
}
use crate::List1::{Cons1, Nil1};
#[derive(Debug)]
enum List2 {
Cons2(i32, Rc<List2>),
Nil2
}
use crate::List2::{Cons2, Nil2};
use std::rc::Rc;
fn main() {
let a: List1 = Cons1(5,
Box::new(Cons1(10,
Box::new(Nil1))));
let b: List1 = Cons1(1, Box::new(a));
// let c: List1 = Cons(2,Box::new(a)); // 发生借用
println!("{:?}", b);
// Rc<T>
let a = Rc::new(Cons2(1,
Rc::new(Cons2(2,
Rc::new(Nil2)))));
let b = Cons2(3, Rc::clone(&a));
let c = Cons2(4, Rc::clone(&a));
println!("{:?}, {:?}, {:?}, {:?}", a, b, c, Rc::strong_count(&a));
}
内部可变性
内部可变模式(interior mutability pattern)
:不可变类型暴露出可修改其内部值的 API
- 它是 Rust 的设计模式之一
- 它允许在支持有不可变引用的前提下对数据进行修改
RefCell<T>
RefCell<T>
类型代表了其持有数据的唯一所有权
- 只会在运行时检查借用规则,否则引发 panic
- 运行时检查:
- 会延迟暴露问题,可能引发生产事故
- 因借用计数产生性能损失
RefCell<T>
只适用于单线程场景
- 同一数据的持有者只能由一个
其他
Ref<T>
和 RefMut<T>
,通过 RefCell<T>
访问:在运行时而不是编译时强制借用规则的类型
- 引用循环(reference cycles):防止内存泄漏发生,
Ref<T>
和 RefCell<T>
都可能发生
- 每个项的引用数量无法变为 0,该值不能被释放
- 防止方法:
- 开发者保证
- 通过重组数据结构规避,如将
Rc<T>
换成 Weak<T>
Cell<T>
通过复制来访问数据
Mutex<T>
实现跨线程情形的内部可变性模式