在 C 语言里,指针的概念就是存储地址的变量。
而 rust 智能指针就是在指针的基础上添加了一些功能。
Box<T>
#
允许将数据存储在堆而不是栈上,然后在栈上保留一个指针指向堆上的数据。
- 特意的将数据分配在堆上
// 此时 1 分配在堆上let n = Box::new(1);
- 当栈上数据较大时,又不想在转移所有权时进行数据拷贝
let arr = [0;1000];// 因为 arr 分配到了栈上,此处发生了深复制let arr2 = arr;
let arr = Box::new([0;1000]);// 没有深复制,发生了所有权转移,arr 不能在访问let arr2 = arr;
- 将动态大小类型变为 Sized 固定大小类型
enum List { Cons(i32, List), Nil,}
以上代码,编译时会报错,因为 Rust 需要在编译时知道类型占用多少空间,而 List 的 Cons 可以无限嵌套。
enum List { Cons(i32, Box<List>), Nil,}
因为 Box 只包含一个指针指向数据和一个指针指向类型,是已知大小的,所以通过。
TODO
- 特征对象
实现不同类型组成的数组
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: Vec<Box<dyn Draw>> = vec![Box::new(Button { id: 1 }), Box::new(Select { id: 2 })];
for e in elems { e.draw() }}
Box::leak
消费掉 Box 并且强制目标值从内存中泄漏(变成全局的值)
TODO 待验证
Box::from_raw
可以收回来
Rc<T>
引用计数(reference counting)#
由于 rust 所有权规则,变量只能有一个所有者,而 Rc 可以使变量有多个所有者。
Rc 通过克隆 RcBox
结构体的指针来实现多个所有者。而 RcBox
包含以下字段:
#[repr(C)]struct RcBox<T: ?Sized> { strong: Cell<usize>, // value 强引用所有者的个数,当为零时清除变量 weak: Cell<usize>, // value 弱引用所有者的个数,不影响变量的清除 value: T,}
let rc = Rc::new("");
// 获取强引用计数Rc::strong_count(&rc);
// 获取弱引用计数Rc::weak_count(&rc);
Weak<T>
#
let rc = Rc::new("a")let weak = Rc::downgrade(&rc)
通过 Rc::downgrade
来生成值的弱应用
Arc (atomic reference counting)#
Rc 的升级版,支持多线程,因为 Rc 需要管理引用计数,但是该计数器并没有使用任何并发原语,因此无法实现原子化的计数操作,最终会导致计数错误。
Cell<T>
#
提供内部可变性的容器(没有额外的性能损耗),不指定 mut 也可以实现修改变量
let c = Cell::new("a");let value = c.get();
// 改变了 c 内部的值c.set("b")
RefCell<T>
#
同 Cell,但是 T 不需要实现 Clone
Cow 写时复制(Copy on Write)#
适用于读多写少的场景。先借用变量进行读操作,当进行写操作时,先对变量进行复制操作后,再进行写操作。
pub enum Cow<'a, B>Where B: 'a + ToOwned + 'a + ?Sized{ Borrowed(&'a + B), // 引用 Owned(<B as ToOwned>::Owned) // 所有者}
// 返回变量的可变引用。当为引用无所有权时,先进行复制后再返回可变引用fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned;
// 转移所有权。当为引用,无所有权时,先进行复制后再返回fn into_owned(self) -> <B as ToOwned>::Owned;
Deref DerefMut#
Deref#
struct MyBox<T>(T);
impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) }}
impl<T> Deref for MyBox<T> { type Target = T; // 关联类型 fn deref(&self) -> &T { &self.0 }}
fn deref_test() { let x = 5; let y = MyBox::new(x); assert_eq!(5, x); assert_eq!(5, *y); // *y 会被 Rust 隐式地展开为 *(y.deref())}
当我们将某个特定类型的值引用作为参数传递给函数,但是传入的类型与参数类型不一致时,会自动发生解引用转换
fn hello(name: &str) { println!("Hello, {}", name);}
fn deref_test() { let m = MyBox::new(String::from("Rust")); hello(&m);}
这里将 MyBox<String>
值的引用传入 hello 函数。因为 MyBox 实现了 Deref,所以 Rust 通过 deref 来将 &MyBox<String>
转换为 &String,而标准库的 String 实现了 Deref,所以 Rust 可以继续调用 deref 来将 &String 转换为 &str,并最终与 hello 函数的定义相匹配。
DerefMut#
Pin、Unpin#
Pin#
Pin
会保证实现了 !Unpin
的对象永远不会被移动
为什么需要 Pin
:
pub struct Data { data: usize,}pub struct SelfRef { data: Data, data_ref: *const Data,}impl SelfRef { fn new(data: Data) -> Self { Self { data, data_ref: std::ptr::null(), } }
fn init(&mut self) { self.data_ref = &self.data; }}
// 利用裸指针规避借用检查,去构造一个不受限的自引用结构。fn init() { let mut self_ref = SelfRef::new(Data { data: 1 }); self_ref.init();}
// 在栈上移动自引用结构fn test() -> SelfRef { let mut self_ref = SelfRef::new(Data { data: 1 }); self_ref.init(); self_ref}fn stack_move() { let self_ref = test(); // data_ref 会指向错乱的数据
// 原因:SelfRef 移动后,data 字段地址自然也更改了,但是 data_ref 还是指向原来的地址, // 地址在 test 函数调用栈上,test 函数调用完毕,栈已经释放,data_ref 成了悬空指针。}
// 在堆上分配自引用结构fn test() -> Box<SelfRef> { let mut self_ref = Box::new(SelfRef::new(Data { data: 1 })); self_ref.init(); self_ref}fn heap_move() { let self_ref = test(); // data_ref 指向正确的数据
// 原因:SelfRef 分配在堆上,移动 Box<SelfRef> 只会移动指向 Box<SelfRef> 的指针 // 而 SelfRef 在堆上, 所以数据并没有发生移动。
// 使用 Option::take 和 mem::swap 可能也导致堆上的数据错乱。}
Pin<T>
为什么可以保证自引用结构的安全
当 T
没有实现 Unpin
时,禁止使用者获取 &mut T
,获取不到T
也就无法移动了,也就安全了。
Pin<T>
的实现代码中,获取 &mut T
的合法途径 get_mut
与 deref_mut
都分别要求了 T:Unpin
、T: DerefMut<Target: Unpin>
。
Drop#
允许我们在变量离开作用域的时候执行某些自定义操作。