相关概念
- 指针:一个变量在内存中包含的是一个地址(指向其他数据)
Rust
中最常用的指针就是引用(&
)
智能指针
智能指针是一种数据结构
- 行为和指针相似
- 但是有额外的元数据和功能
与引用的不同
引用 | 智能指针 |
---|---|
只借用数据 | 很多时候都拥有他所指向的数据 |
例子
String
和Vec<T>
- 都拥有一片内存区域,且允许用户对其操作
- 还拥有元数据(例如容量等)
- 提供额外的功能保障(string会保障所有字符都符合utf-8编码规范)
实现
智能指针通常使用struct
实现,并且实现了Deref
和Drop
这两个trait
;
Deref trait
:允许智能指针struct
的实例像引用一样使用Drop trait
:允许自定义当智能指针实例走出作用域时的代码
标准库中的智能指针
Box<T>
: 在heap
中分配值Rc<T>
:启用多重所有权的引用计数类型Ref<T>
和RefMut<T>
,通过RefCell<T>
访问: 在运行时而不是编译时强制借用规则的类型
Box
Box
- 允许在
heap
中存储数据(而不是stack
) stack
上是指向heap
数据的指针- 没有额外的功能和性能开销
实现了
Deref trait
和Drop trait
使用场景
- 在编译时,某类型的大小无法确定。但使用该类型时,上下文却需要知道它的确切大小
- 当你有大量数据,想移交所有权,但需要确保在操作时数据不会被复制
- 使用某个值时,你只关心他是否实现了特定的
trait
,而不关心它的具体类型使用方法
fn main() {
let val = Box::new(12);
println!("{}", val);
}
使用Box赋能递归类型
- 在编译时,Rust需要知道一个类型所占的空间大小
- 而递归的类型大小无法在编译时确定
上面这幅图就是一个递归的示例,当Cons无限递归下去的时候,每个i32
类型的数据是确定的,但是由于不知道需要递归多少层,所以就会导致无法确定这个递归类型需要多少的内存空间。
这时就需要Box
来赋能这种递归类型。
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1,
Box::new(Cons(2,
Box::new(Cons(3,
Box::new(Nil)
)
)
)
));
println!("【 list 】==> {:?}", list);
}
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
Box
- 只提供了间接存储和heap分配的功能
- 没有性能开销,也没有额外的性能损耗
Deref Trait
如果实现了Deref Trait
那么就可以自定义解引用(*
)的行为。
通过实现Deref Trait
,智能指针就可以像普通引用一样来处理。
解引用运算符*
- 常规的指针也是一种引用。 ```rust let x = 5; let y = &x;
assert_eq!(x, 5); assert_eq!(x, *y); // y是x的引用,当我们想知道y的值是多少的时候,就需要解引用
当我们不进行解引用时,代码就会painc!
```rust
assert_eq!(x, y);
意思就是没有实现 整型到引用整型的一个方法
而BoxDeref Trait
的,所以我们把上面代码改造一下,也是可以的。
let x = 5;
let y = Box::new(x);
assert_eq!(x, 5);
assert_eq!(x, *y); // y是x的引用,当我们想知道y的值是多少的时候,就需要解引用
这里需要知道的关键点就是:因为**Box<T>**
实现了**Deref Trait**
所以智能指针可以像普通指针一样来解引用.
实现自定义智能指针
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(x, 5);
// 此时这里会报错,因为我们的自定义智能指针没有实现Deref
assert_eq!(x, *y);
}
// 实现自定义智能指针
struct MyBox<T>(T); // 定义MyBox的元组
// 为MyBox实现构造方法
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
我们来实现一个Deref Trait
use std::ops::Deref;
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(x, 5);
assert_eq!(x, *y);
}
// 实现自定义智能指针
struct MyBox<T>(T); // 定义MyBox的元组
// 为MyBox实现构造方法
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
// 实现Deref Trait
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
这里主要是为了加强 Deref Trait的印象,就是为了让智能指针能够像普通的指针一样可以进行解引用*. 还有一点就死: Box
其实就一个元组结构体 **tuple struct**
从上面的例子中我们还可以了解到一个关键点就是:
解引用操作***y**
其实就相当于 ***( y.deref() )**
。
函数和方法的隐式解引用转化(Deref Coercion)
- 隐式解引用转化是为函数和方法提供的一些便捷特性
假设T
实现了Deref Trait
:
Deref Coercion
可以把T
的引用 ====>T
经过Deref
操作之后生成的引用
假设我们有这样一种场景: 当我们传递给函数一个参数x,这个x是某个类型的引用。 但是呢这个参数x不符合函数定义时的参数类型。 这时
Deref Coercion
就会自动发生 编译器会对deref
进行一系列调用,最终转化为所需要的参数类型(这个过程在编译时完成,所以不会对运行时产生额外的开销)
( 那么会出现无法转换的情况吗?)
use std::ops::Deref;
fn hello(s: &str) {
println!("【 s 】==> {:?}", s);
}
fn main() {
let my_box = MyBox::new(String::from("hello"));
// 这里我们定义的方法的参数类型为&str
// 但是我们传入的是 &MyBox<String>
// 因为MyBox实现了Deref
// 所以 deref &MyBox<String> => String;
// 因为String也实现了Deref,返回的是一个切片类型
// 所以 deref String => &str
hello(&my_box);
}
// 实现自定义智能指针
struct MyBox<T>(T); // 定义MyBox的元组
// 为MyBox实现构造方法
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
// 实现Deref Trait
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
**Deref Coercion**
:编译器会对**deref**
进行一系列调用,最终转化为所需要的参数类型(这个过程在编译时完成,所以不会对运行时产生额外的开销)