指针:是指一个包含了内存地址的变量,这个地址代表或者指向其它的数据,最常用的一种指针就是引用reference,用符号&表示,借用了指向的值
智能指针:一种结构体,不止具备指针的能力,并且包含了额外的元数据metadata和能力,比如引用计数智能指针(追踪数据的多个所有者,如果所有者为0,则数据被销毁);
冷知识:String和Vec<T>其实也是智能指针,理由是他们不光拥有一块内存允许我们操作它,而且也有元数据(比如容量)和额外的能力、保障(如String要求它的数据必须是有效的UTF-8字符)
智能指针一般用结构体struct来实现,区别与一般的struct,它还会实现Dref和Drop这两个trait,Dref解引用允许智能指针具有引用的能力,Drop允许自定义销毁变量时候的行为
Box<T>
Box<T>让数据存储在堆heap上,而不是在栈stack上,同时在栈上会有一个指针指向堆中的内存地址数据
看代码
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
println语句执行完后,b会销毁,同时b指向的堆上的数据5也会被销毁
如下代码无法编译通过:
enum List {
Cons(i32, List),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
}
报错如下:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
error[E0072]: recursive type `List` has infinite size
--> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^ recursive type has infinite size
2 | Cons(i32, List),
| ---- recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable
|
2 | Cons(i32, Box<List>),
| ^^^^ ^
error[E0391]: cycle detected when computing drop-check constraints for `List`
--> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^
|
= note: ...which again requires computing drop-check constraints for `List`, completing the cycle
= note: cycle used when computing dropck types for `Canonical { max_universe: U0, variables: [], value: ParamEnvAnd { param_env: ParamEnv { caller_bounds: [], reveal: UserFacing }, value: List } }`
Some errors have detailed explanations: E0072, E0391.
For more information about an error, try `rustc --explain E0072`.
error: could not compile `cons-list` due to 2 previous errors
报错信息的意思是infinite size无穷大小,原因是其中含有递归recursive,Rust无法判断它占用的空间大小
Rust如何计算非递归类型所占空间大小
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
枚举类型的大小,Rust会选择占用空间最大的那个
智能指针-实现了Dref trait的引用reference
如下代码编译失败!can't compare {integer}
with &{integer}
fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
}
不可以把两个不同类型一起比较,一个是integer,一个是&integer,不具备可比性
上述代码修改成:
fn main() {
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
使用Box<T>类型后,可以正常编译,测试通过
自定义智能指针 MyBox<T>
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
此行代码assert_eq!(5, *y) 还是不能通过编译,原因是MyBox<T>没有实现Dref trait,导致解引用无法获取具体的值
所以,需要新增Dref trait的实现
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
*y背后的代码其实是 *(y.deref())
函数或方法中的强制隐式解引用
fn hello(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
}
上面代码中,hello函数的参数是&str,是String的切片,由于MyBox<T>实现了Dref的trait,所以&m会解引用为String,而String又可以解引用为&str,所以可以编译通过
如果Rust没有解引用胁迫机制,hello函数的调用要写成 hello(&(*m)[..]); 可读性非常差;Rust的解引用根据需要可以执行许多次,为了要找到合适的匹配参数的引用,这个动作是在编译器,所以对程序运行时没有影响
可变性中的强制解引用
- From &T to &U when T: Deref<Target=U>
- From &mut T to &mut U when T: DerefMut<Target=U>
- From &mut T to &U when T: Deref<Target=U>
Drop trait
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
}
可以使用std::mem::drop来提前释放占用的资源
RC<T>引用计数智能指针
大部分场景下,一个变量对一个值拥有所有权,但是某些场景下,如图结构,某个节点是可以有多条边的,除非所有的边都释放了所有权,这个值才能被释放
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
a变量,第一次定义和声明的时候,引用计数为1;b定义的时候,a的引用计数加1,变为2,c定义的时候,a的引用计数又增加1,变为3,当c在{}执行完后,由于c释放,导致a的引用计数减1,变为2,然后当main函数执行完毕后,a的引用变为0,a的资源得到释放
RefCell<T> 内部可变
强制在运行时应用借用规则,应用场景如mock object
网友评论