rust 智能指针(二)

简介: rust 智能指针

rust 智能指针(一)https://developer.aliyun.com/article/1391759


为智能指针实现 Deref 特征

现在来为 MyBox 实现 Deref 特征,以支持 * 解引用操作符:

use std::ops::Deref;
impl<T> Deref for MyBox<T> {
    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: Deref,可以将 &T 转换成 &U,也就是我们之前看到的例子
  • 当 T: DerefMut,可以将 &mut T 转换成 &mut U
  • 当 T: Deref,可以将 &mut T 转换成 &U
    来看一个关于 DerefMut 的例子:
struct MyBox<T> {
    v: T,
}
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox { v: x }
    }
}
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.v
    }
}
use std::ops::DerefMut;
impl<T> DerefMut for MyBox<T> {
    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 只能用于同一线程内部,想要用于线程之间的对象共享,你需要使用 Arc
  • Rc 是一个智能指针,实现了 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);
}
相关文章
|
21天前
|
安全 程序员 编译器
C++中的RAII(资源获取即初始化)与智能指针
C++中的RAII(资源获取即初始化)与智能指针
27 0
|
21天前
|
安全 程序员 C++
C++中的智能指针:从原始指针到现代内存管理
C++中的智能指针:从原始指针到现代内存管理
20 0
|
21天前
|
C++
C++:一文读懂智能指针
C++:一文读懂智能指针
59 0
|
21天前
|
消息中间件 Kubernetes NoSQL
智能指针的种类以及使用场景
智能指针的种类以及使用场景
|
21天前
|
安全 C++
c++11新特性——智能指针详解
c++11新特性——智能指针详解
|
21天前
|
安全 C++ 容器
C++中的智能指针:自动内存管理的利器
C++中的智能指针:自动内存管理的利器
27 0
|
21天前
|
设计模式 Rust Java
【一起学Rust | 设计模式】习惯语法——默认特质、集合智能指针、析构函数
【一起学Rust | 设计模式】习惯语法——默认特质、集合智能指针、析构函数
62 0
|
21天前
|
存储 Rust C++
C++智能指针
【2月更文挑战第14天】介绍C++智能指针
26 0
|
21天前
|
算法 安全 C++
C++ Effective Modern Pointer (智能指针模块)
C++ Effective Modern Pointer (智能指针模块)