rust
智能指针
Box<T>指针
- 在堆上存储数据,而指针本身位于栈上 Box<T>类型的智能指针创建的是指向堆数据的指针,初始化过程中,会将数据分配在堆上,能够有效地节省栈上有限的空间,在所有权转移的过程中,复制的仅仅是一个栈上的指针,而非实际的数据,能够提高程序的性能 示例:
fn main() {
let b = Box::new(5);
println!("b = {}",b);
}
- 允许创建递归类型 对于常见的cons list类型的数据结构(嵌套的列表),如果直接在声明结构体时进行嵌套,rust的编译器无法推断出该类型数据占用的内存大小,会在编译时报错。 示例:
//编译时将报错,无法推断出List结构体的具体大小
use crate::List::{Cons, Nil};
enum List{
Cons(i32,List),
Nil,
}
fn main(){
let list = Cons(1, Cons(2, Cons(3, Nil)));
}
此时可以使用Box<T>指针指向嵌套的列表,得到cons list类型的结构体。(指针的内存大小是已知的,但列表的大小是在进行结构体声明时未知的)
示例:
代码语言:rust复制// 此段代码不会报错,因为Box<T>为智能指针,大小固定,编译器可以推断出List类型的大小
use crate::List::{Cons, Nil};
enum List{
Cons(i32, Box<List>),
Nil
}
fn main(){
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
- 超出作用域之后,自动释放堆上数据 在超出作用域后,栈上的数据都会被逐一清除,而Box<T>智能指针在清除之前会调用其实现了的Drop trait的drop函数,清除所指向的堆上的数据。
Deref Trait 重载解引用运算符
智能指针类型的变量本身为指针类型,在使用时需要进行解引用来得到其所指向的数据。而解引用需要重载解引用运算符&。而通过实现Deref Trait,能够将类型像引用一样处理。
Deref trait需要实现其定义的deref方法,返回一个内部数据的引用。
示例:
代码语言:rust复制 //为Mybox<T>类型实现Deref trait
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &self::Target {
&self.0
}
}
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);
}
Drop Trait 自动清除数据
Drop trait 类似于c 中的析构函数,在变量离开作用域时调用,清除数据或进行一些其他操作。
实现Drop trait需要实现其drop方法,该方法在变量离开时被调用,完成指定的一些操作。(主要目的为清理该变量拥有的数据)
此外,还可通过std::mem::drop 来在作用域结束前释放变量,std::mem::drop位于prelude中,因此无需显式引入该方法。
示例:
代码语言:rust复制 //为CustomSmartPointer结构体实现Drop trait
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop (&mut self){
println!("Droping CustomSmartPointer with Data `{}`", self.data);
}
}
fn main() {
let c = CustomSmartPointer{
data: String::from("my stuff"),
};
drop(c);
let d = CustomSmartPointer{
data: String::from("other stuff"),
};
println!("CustomSmartPointer created.");
}
Rc<T>指针
Rc<T> 用于当我们希望在堆上分配一些数据供程序的多个部分读取,且无法在编译时确定程序的哪一部分会最终结束使用它的时候,如果确实知道哪部分是最后结束使用的话,可以令其成为数据的所有者,正常的所有权规则在编译时生效。
可以通过克隆Rc<T>的方式获取对堆上数据的引用,每次克隆时,引用计数增加1,当一个Rc<T>指针离开作用域时,引用计数减1,而当引用计数为0时,对应的drop方法将会被调用,堆上数据将会被清理。
注意:Rc<T>克隆的结果为不可变引用,rust不允许同时存在多个可变引用。
- 强引用 --Rc<T> Rc<T>指针为强引用,可以通过调用Rc::clone方法返回一个Rc<T>指针,会导致引用计数发生变化,当引用计数为0时,指针所指向的堆上数据将会被清理
- 弱引用 --Weak<T> Weak<T>指针为弱引用,可以通过Rc::downgrade方法返回一个*Weak<T>指针,不会导致引用计数发生变化,不会对堆上数据的清理产生影响 因为Weak<T>引用的值可能已经被丢弃了,因此需要在使用Weak<T>所指向的值时,调用Weak<T>实例的upgrade方法,返回一个Option<Rc<T>>
示例:
代码语言:rust复制 use crate::List::{Cons, Nil};
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
fn main(){
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
//Rc::clone得到的是Rc<T>类型的强引用,会影响引用计数
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));
}
RefCell<T>指针
- 内部可变性 内部可变性是rust中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则不允许的。该模式使用unsafe代码来模糊rust的可变性和借用规则。 当可以确保代码在运行时会遵守借用规则,即使是编译器无法保证的情况,可以选择使用运用了内部可变性模式的类型。
- RefCell<T>运行时检查借用规则 RerCell<T>遵循内部可变性模式,在运行时检查借用规则而非编译时。 因为RefCell<T>允许在运行时检查借用规则,因此可以在RefCell自身不可变的情况修改其内部的值。 示例:
use std::cell::RefCell;
trait Messenger {
fn send(&self, msg: &str);
}
struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}
impl Messager for MockMessenger {
fn send(&self, message: &str) {
//borrow_mut()方法获取的是RefMut<T>类型的智能指针,borrow()方法获取的是Ref<T>类型的智能指针
self.sent_message.borrow_mut().push(String::from(message));
}
}
- RefCell<T>在运行时记录借用 borrow_mut方法获取的是RefMut<T>类型的智能指针,borrow方法获取的是Ref<T>类型的智能指针 RefCell<T>记录当前有多少个活动的Ref<T>和RefMut<T>智能指针,每次调用borrow方法,RefCell<T>将不可变借用计数加一,当Ref<T>指针离开作用域时,不可变计数减一。可变借用计数规则类似不可解压计数规则。 与编译时借用规则相同:RefCell<T>在任何时刻只允许存在多个不可变借用或一个可变借用。 示例:
impl Messenger for MockMessenger {
fn send(&self, message: &str){
//代码将报错,不能同时存在两个可变借用
let mut one_borrow = self.sent_message.borrow_mut();
let mut two_borrow = self.sent_message.borrow_mut();
}
}