海口自助建站,丝路云网站建设,乱起封神是那个网站开发的,企业网站空间装箱是最简单直接的一种智能指针#xff0c;它的类型是BoxT。装箱使我们可以把数据存储到堆上#xff0c;并在栈上保留一个指向堆数据的指针。装箱操作常常被用于下面的场景#xff1a;
当你拥有一个无法在编译时确定大小的类型#xff0c;但又想使用这个类型的值…装箱是最简单直接的一种智能指针它的类型是BoxT。装箱使我们可以把数据存储到堆上并在栈上保留一个指向堆数据的指针。装箱操作常常被用于下面的场景
当你拥有一个无法在编译时确定大小的类型但又想使用这个类型的值时。当你需要传递大量数据的所有权但又不希望产生大量数据的复制行为时。当你希望拥有一个实现了指定trait的类型值但又不关心具体的类型时。
fn main() {let b Box::new(5);println!(b {}, *b 3);println!(b {}, b);
}常规引用就是一种类型的指针你可以将指针形象地理解为一个箭头它会指向存储在别处的某个值。
装箱类似于常规指针也可以通过解引用来获取装箱实际的值代码中*b就是如此。这个定义和Go中的unsafe.Pointer非常类似所有具体类型的指针都可以用一种类型的指针来表示。
定义递归类型
RUST必须在编译时知道每一种类型占据的空间大小但有一种递归的类型却无法在编译时被确定具体大小。比如下面例子中的链表
use crate::List::{Cons, Nil};
enum List {Cons(i32, List),Nil,
}fn main() {let list Cons(1, Cons(2, Cons(3, Nil)));
}我们尝试使用枚举来表达一个持有i32值的链表数据类型通过不断嵌套元组的形式最终组成一个列表。
但程序无法编译通过RUST认为这个类型拥有无限大小无法确认类型所占用的存储空间大小。
enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}RUST如何计算Message类型的大小为了计算Message值需要多大的存储空间RUST会遍历枚举中的每一个成员来找到需要最大空间的那个变体。Message::Quit不需要占用任何空间Message::Move需要两个存储i32值的空间以此类推。
因为指针大小是恒定的要改变这样无穷递归的情况就应该将Cons变体中存放一个BoxT而不是直接存放另外一个List值而BoxT则会指向下一个List并存储在堆上。
use crate::List::{Cons, Nil};
enum List {Cons(i32, BoxList),Nil,
}fn main() {let list Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}新的Cons变体中需要一部分存储i32的空间和一部分存储装箱指针的空间这样调整之后List值都只需要占用一个i32值加上一个装箱指针的空间。通过使用装箱我们打破了无限递归的过程进而使编译器可以计算出List值所占用的空间。
BoxT属于智能指针的一种因为它实现了Defer trait所以允许我们将BoxT的值当做引用来对待。当一个BoxT值离开作用域时因为它实现了Drop trait所以BoxT指向的堆数据会自动地被清理释放。
实现 Defer trait将类型视作引用
定义我们自己的智能指针BoxT类型最终被定义为拥有两个元素的元组结构体。结构体给Deref trait实现了defef方法该方法会借用self并返回一个指向元素第一个元素的引用。
RUST所有权系统决定了deref方法需要返回一个引用。假设deref方法使编译器直接返回了值而不是指向值的引用那么这个值就会被移除self。在大多数使用解引用运算符的场景下我们并不希望获取MyBoxT内部值的所有权。
type Target T 定义了Deref trait的一个关联类型。我们在deref方法体中返回指向第一个元素的引用进而允许调用者通过*运算符访问值。
use std::ops::Deref;struct MyBoxT(T, T);implT MyBoxT {fn new(x: T, y: T) - MyBoxT {MyBox(x, y)}
}implT Deref for MyBoxT {type Target T;fn deref(self) - Self::Target {self.0}
}fn hello(language: str) {println!(Hello, {}, language)
}fn main() {let m MyBox::new(String::from(Rust), String::from(Go));hello(m);
}在没有Deref trait的情况下编译器只能对形式的常规引用执行解引用操作。deref方法使编译器可以从任何实现了Deref的类型中获取值并能够调用deref方法来获取一个可以进行解引用的引用。
解引用转换deref coercion是RUST为函数和方法的参数提供的一种便捷特性。当某个类型T实现了Deref trait时它能够将T的引用转换为T经过Deref操作之后的引用。
函数hello接收str字符串切片类型自定义MyBoxT自动解引用为元组第一个元素的引用String。函数体需要str类型但现在传入的是String类型为什么编译依然能够通过呢
能够使用String的原因也是因为解引用操作编译器可以自动将String类型的参数强制转换为str类型。
解引用转换与可变性
使用Deref trait能够重载不可变引用的*运算符与之类似使用DerefMut trait能够重载可变引用*运算符。
RUST会在类型与trait满足下面三种情形下执行解引用转换
当T: DerefTragetU时允许T转换为U。当T: DerefMutTargetU时允许mut T转换为mut U。当T: DerefTargetU时允许mut T转换为U。
第三种情况RUST会将一个可变引用转换为一个不可变引用。但这个过程绝对不可逆也就是说不可变引用永远不可能转换为可变引用。因为按照借用规则如果存在一个可变引用那么它就必须是唯一的引用否则程序无法编译通过。
Drop trait在清理时运行代码
我们可以通过实现Drop trait来指定值离开作用域时需要运行的代码。Drop trait要求实现一个接收self可变引用作为参数的drop函数。
Go语言中的defer也有类似的能力在离开作用域时执行代码。
struct CustomSmartPointer {data: String,
}impl Drop for CustomSmartPointer {fn drop(mut self) {println!(Dropping CustomSmartPoint with data {}, self.data)}
}fn main() {let c CustomSmartPointer {data: String::from(my stuff),};let d CustomSmartPointer {data: String::from(other stuff),};// drop(c);println!(main)
}这段代码没有显示地将Drop trait引入作用域因为它已经被包含在预导入模块中。RUST会在实例离开作用域时自动调用我们编写的drop代码。因为变量的丢弃顺序与创建顺序相反所以d在c之前被丢弃。
RUST并不允许我们手动调用Drop trait的drop方法因为自动和手动会两次触发drop这种行为试图对同一个值清理两次而导致重复释放double free错误。
如果需要提前清理一个值可以调用标准库中的std::mem:drop函数来提前清理某个值。代码注释部分的代码就用来提前清理c。
使用Drop无需担心正在使用的值被意外清理掉所有权系统会保证所有引用的有效性而drop只会在确定不使用这个值时被调用一次。