响应式网站建设的好处,珠海网页设计公司,服装品牌建设网站的目的,wordpress更新缓存的插件智能指针
Box
Box 的使用场景
由于 Box 是简单的封装#xff0c;除了将值存储在堆上外#xff0c;并没有其它性能上的损耗。而性能和功能往往是鱼和熊掌#xff0c;因此 Box 相比其它智能指针#xff0c;功能较为单一#xff0c;可以在以下场景中使用它#xff1a;
特…智能指针
Box
Box 的使用场景
由于 Box 是简单的封装除了将值存储在堆上外并没有其它性能上的损耗。而性能和功能往往是鱼和熊掌因此 Box 相比其它智能指针功能较为单一可以在以下场景中使用它
特意的将数据分配在堆上数据较大时又不想在转移所有权时进行数据拷贝类型的大小在编译期无法确定但是我们又需要固定大小的类型时特征对象用于说明对象实现了一个特征而不是某个特定的类型
Box 堆对象分配
因为 Box 允许你将一个值分配到堆上然后在栈上保留一个智能指针指向堆上的数据。
当栈上数据转移所有权时实际上是把数据拷贝了一份最终新旧变量各自拥有不同的数据因此所有权并未转移。
fn main() {let a Box::new(3);println!(a {}, a); // a 3// 下面一行代码将报错// let b a 1; // cannot add {integer} to Box{integer}//正确//let b *a 1
}
}将动态大小类型变为 Sized 固定大小类型
Rust 需要在编译时知道类型占用多少空间如果一种类型在编译时无法知道具体的大小那么被称为动态大小类型 DST。
其中一种无法在编译时知道大小的类型是递归类型在类型定义中又使用到了自身或者说该类型的值的一部分可以是相同类型的其它值这种值的嵌套理论上可以无限进行下去所以 Rust 不知道递归类型需要多少空间
enum List {Cons(i32, List),Nil,
}以上就是函数式语言中常见的 Cons List它的每个节点包含一个 i32 值还包含了一个新的 List因此这种嵌套可以无限进行下去Rust 认为该类型是一个 DST 类型并给予报错
error[E0072]: recursive type List has infinite size //递归类型 List 拥有无限长的大小-- src/main.rs:3:1|
3 | enum List {| ^^^^^^^^^ recursive type has infinite size
4 | Cons(i32, List),| ---- recursive without indirection此时若想解决这个问题就可以使用我们的 Box
enum List {Cons(i32, BoxList),Nil,
}只需要将 List 存储到堆上然后使用一个智能指针指向它即可完成从 DST 到 Sized 类型(固定大小类型)的华丽转变。
Box 中还提供了一个非常有用的关联函数Box::leak它可以消费掉 Box 并且强制目标值从内存中泄漏读者可能会觉得这有啥用啊
fn main() {let s gen_static_str();println!({}, s);
}fn gen_static_str() - static str{let mut s String::new();s.push_str(hello, world);Box::leak(s.into_boxed_str())
}特征对象
在 Rust 中想实现不同类型组成的数组只有两个办法枚举和特征对象前者限制较多因此后者往往是最常用的解决办法。 trait Draw {fn draw(self);
}struct Button {id: u32,
}
impl Draw for Button {fn draw(self) {println!(这是屏幕上第{}号按钮, self.id)}
}struct Select {id: u32,
}impl Draw for Select {fn draw(self) {println!(这个选择框贼难用{}, self.id)}
}fn main() {let elems: VecBoxdyn Draw vec![Box::new(Button { id: 1 }), Box::new(Select { id: 2 })];for e in elems {e.draw()}
}
Box::leak
Box 中还提供了一个非常有用的关联函数Box::leak它可以消费掉 Box 并且强制目标值从内存中泄漏读者可能会觉得这有啥用啊
其实还真有点用例如你可以把一个 String 类型变成一个 static 生命周期的 str 类型
fn main() {let s gen_static_str();println!({}, s);
}fn gen_static_str() - static str{let mut s String::new();s.push_str(hello, world);Box::leak(s.into_boxed_str())
}Deref 解引用
智能指针的名称来源主要就在于它实现了 Deref 和 Drop 特征这两个特征可以智能地帮助我们节省使用上的负担
Deref 可以让智能指针像引用那样工作这样你就可以写出同时支持智能指针和引用的代码例如 *T Drop 允许你指定智能指针超出作用域后自动执行的代码
常规引用的解引用。
常规引用是一个指针类型包含了目标数据存储的内存地址。对常规引用使用 * 操作符就可以通过解引用的方式获取到内存地址对应的数据值
fn main() {let x 5;let y x;assert_eq!(5, x);assert_eq!(5, *y);
}这里 y 就是一个常规引用包含了值 5 所在的内存地址然后通过解引用 *y我们获取到了值 5。如果你试图执行 assert_eq!(5, y);代码就会无情报错因为你无法将一个引用与一个数值做比较
error[E0277]: cant compare {integer} with {integer} //无法将{integer} 与{integer}进行比较-- src/main.rs:6:5|
6 | assert_eq!(5, y);| ^^^^^^^^^^^^^^^^^ no implementation for {integer} {integer}| help: the trait PartialEq{integer} is not implemented for {integer}// 你需要为{integer}实现用于比较的特征PartialEq{integer}智能指针解引用
实现 Deref 后的智能指针结构体就可以像普通引用一样通过 * 进行解引用例如 Box 智能指针
fn main() {let x Box::new(1);let sum *x 1;
}智能指针 x 被 * 解引用为 i32 类型的值 1然后再进行求和。
定义自己的智能指针
由于 Box 本身很简单并没有包含类如长度、最大长度等信息因此用一个元组结构体即可。
struct MyBoxT(T);implT MyBoxT {fn new(x: T) - MyBoxT {MyBox(x)}
}跟 Box 一样我们的智能指针也持有一个 T 类型的值然后使用关联函数 MyBox::new 来创建智能指针。由于还未实现 Deref 特征此时使用 * 肯定会报错
fn main() {let y MyBox::new(5);assert_eq!(5, *y);
}运行后报错如下
error[E0614]: type MyBox{integer} cannot be dereferenced-- src/main.rs:12:19|
12 | assert_eq!(5, *y);| ^^为智能指针实现 Deref 特征
现在来为 MyBox 实现 Deref 特征以支持 * 解引用操作符
use std::ops::Deref;implT Deref for MyBoxT {type Target T;fn deref(self) - Self::Target {self.0}
}很简单当解引用 MyBox 智能指针时返回元组结构体中的元素 self.0有几点要注意的
在 Deref 特征中声明了关联类型 Target在之前章节中介绍过关联类型主要是为了提升代码可读性 deref 返回的是一个常规引用可以被 * 进行解引用
* 背后的原理
当我们对智能指针 Box 进行解引用时实际上 Rust 为我们调用了以下方法
*(y.deref())首先调用 deref 方法返回值的常规引用然后通过 * 对常规引用进行解引用最终获取到目标值。
要注意的是* 不会无限递归替换从 *y 到 *(y.deref()) 只会发生一次。
函数和方法中的隐式 Deref 转换
对于函数和方法的传参Rust 提供了一个极其有用的隐式转换Deref 转换。若一个类型实现了 Deref 特征那它的引用在传给函数或方法时会根据参数签名来决定是否进行隐式的 Deref 转换例如
fn main() {let s String::from(hello world);display(s)
}fn display(s: str) {println!({},s);
}以上代码有几点值得注意
String 实现了 Deref 特征可以在需要时自动被转换为 str 类型
s 是一个 String 类型当它被传给 display 函数时自动通过 Deref 转换成了 str必须使用 s 的方式来触发 Deref(仅引用类型的实参才会触发自动解引用)
连续的隐式 Deref 转换
Deref 可以支持连续的隐式转换直到找到适合的形式为止
fn main() {let s MyBox::new(String::from(hello world));display(s)
}fn display(s: str) {println!({},s);
}这里我们使用了之前自定义的智能指针 MyBox并将其通过连续的隐式转换变成 str 类型首先 MyBox 被 Deref 成 String 类型结果并不能满足 display 函数参数的要求编译器发现 String 还可以继续 Deref 成 str最终成功的匹配了函数参数。
引用归一化
Rust 编译器实际上只能对 v 形式的引用进行解引用操作那么问题来了如果是一个智能指针或者 v 类型的呢 该如何对这两个进行解引用
答案是Rust 会在解引用时自动把智能指针和 v 做引用归一化操作转换成 v 形式最终再对 v 进行解引用
把智能指针比如在库中定义的Box、Rc、Arc、Cow 等从结构体脱壳为内部的引用类型也就是转成结构体内部的 v 把多重例如 v归一成 v
三种 Deref 转换
实际上 Rust 还支持将一个可变的引用转换成另一个可变的引用以及将一个可变引用转换成不可变的引用规则如下
当 T: DerefTargetU可以将 T 转换成 U也就是我们之前看到的例子当 T: DerefMutTargetU可以将 mut T 转换成 mut U当 T: DerefTargetU可以将 mut T 转换成 U 来看一个关于 DerefMut 的例子
struct MyBoxT {v: T,
}implT MyBoxT {fn new(x: T) - MyBoxT {MyBox { v: x }}
}use std::ops::Deref;implT Deref for MyBoxT {type Target T;fn deref(self) - Self::Target {self.v}
}use std::ops::DerefMut;implT DerefMut for MyBoxT {fn deref_mut(mut self) - mut Self::Target {mut self.v}
}fn main() {let mut s MyBox::new(String::from(hello, ));display(mut s)
}fn display(s: mut String) {s.push_str(world);println!({}, s);
}以上代码有几点值得注意:
Rc 与 Arc
引用计数(reference counting)顾名思义通过记录一个数据被引用的次数来确定该数据是否正在被使用。当引用次数归零时就代表该数据不再被使用因此可以被清理释放。
use std::rc::Rc;
fn main() {
let a Rc::new(String::from(hello, world));
let b Rc::clone(a);
assert_eq!(2, Rc::strong_count(a));
assert_eq!(Rc::strong_count(a), Rc::strong_count(b))}
以上代码我们使用 Rc::new 创建了一个新的 Rc 智能指针并赋给变量 a该指针指向底层的字符串数据。智能指针 Rc 在创建时还会将引用计数加 1此时获取引用计数的关联函数 Rc::strong_count 返回的值将是 1。Rc::clone接着我们又使用 Rc::clone 克隆了一份智能指针 Rc并将该智能指针的引用计数增加到 2。由于 a 和 b 是同一个智能指针的两个副本因此通过它们两个获取引用计数的结果都是 2。不要被 clone 字样所迷惑以为所有的 clone 都是深拷贝。这里的 clone 仅仅复制了智能指针并增加了引用计数并没有克隆底层数据因此 a 和 b 是共享了底层的字符串 s这种复制效率是非常高的。
Rc 简单总结
Rc/Arc 是不可变引用你无法修改它指向的值只能进行读取如果要修改需要配合内部可变性 RefCell 或互斥锁 Mutex一旦最后一个拥有者消失则资源会自动被回收这个生命周期是在编译期就确定下来的Rc 只能用于同一线程内部想要用于线程之间的对象共享你需要使用 ArcRc 是一个智能指针实现了 Deref 特征因此你无需先解开 Rc 指针再使用里面的 T而是可以直接使用 T
Arc
Arc 是 Atomic Rc 的缩写顾名思义原子化的 Rc 智能指针。它能保证我们的数据能够安全的在线程间共享即可. Arc 和 Rc 拥有完全一样的 API修改起来很简单
use std::sync::Arc;use std::thread;
fn main() {
let s Arc::new(String::from(多线程漫游者));
for _ in 0..10 {
let s Arc::clone(s);
let handle thread::spawn(move || { println!({}, s) }); }}Cell 和 RefCell
因此 Rust 提供了 Cell 和 RefCell 用于内部可变性简而言之可以在拥有不可变引用的同时修改目标数据。
Cell
Cell 和 RefCell 在功能上没有区别区别在于 Cell 适用于 T 实现 Copy 的情况.
use std::cell::Cell;
fn main() {let c Cell::new(asdf);let one c.get();c.set(qwer);let two c.get();println!({},{}, one, two);
}RefCell
由于 Cell 类型针对的是实现了 Copy 特征的值类型因此在实际开发中Cell 使用的并不多因为我们要解决的往往是可变、不可变引用共存导致的问题此时就需要借助于 RefCell 来达成目的。
所有权、借用规则与这些智能指针做一个对比
Rust 规则智能指针带来的额外规则一个数据只有一个所有者Rc/Arc让一个数据可以拥有多个所有者要么多个不可变借用要么一个可变借用RefCell实现编译期可变、不可变引用共存违背规则导致编译错误违背规则导致运行时panic
use std::cell::RefCell;fn main() {let s RefCell::new(String::from(hello, world));let s1 s.borrow();let s2 s.borrow_mut();println!({},{}, s1, s2);
}Rc RefCell 组合使用
Rust 中一个常见的组合就是 Rc 和 RefCell 在一起使用前者可以实现一个数据拥有多个所有者后者可以实现数据的可变性 use std::cell::RefCell;
use std::rc::Rc;
fn main() {let s Rc::new(RefCell::new(我很善变还拥有多个主人.to_string()));let s1 s.clone();let s2 s.clone();// let mut s2 s.borrow_mut();s2.borrow_mut().push_str(, oh yeah!);println!({:?}\n{:?}\n{:?}, s, s1, s2);
}