写点什么

5 分钟速读之 Rust 权威指南(二十五)Deref

用户头像
码生笔谈
关注
发布于: 5 小时前
5分钟速读之Rust权威指南(二十五)Deref

通过 Deref trait 为智能指针实现解引用

实现 Deref trait 可以自定义解引用运算符*的行为。通过实现 Deref,可以将智能指针视作常规引用来进行处理。

解引用的使用

与引用比较:


let x = 5;let y = &x;assert_eq!(5, x);assert_eq!(5, y); // error, 不能将引用与整数型对比assert_eq!(5, *y); // 使用解引用运算符来跳转到引用指向的值:x
复制代码


利用引用改变原值:


let mut a = 1;let b = &mut a;*b = 2; // b是可变引用a,使用解引用运算符跳转到可变变量a,将a重新赋值为2println!("{}", a); // 2
复制代码


在循环中使用:


let mut arr = [1;5];for item in arr.iter_mut() {  *item *= 2 // 使用解引用运算符跳转到成员每一项指向的值,再乘2}println!("{:?}", arr); // [2, 2, 2, 2, 2]
复制代码


Box<T>支持解引用运算符:


let x = 5;let y = Box::new(x);assert_eq!(5, x);assert_eq!(5, y); // error, 不能将Box引用与整数型对比assert_eq!(5, *y); // 使用解引用运算符来跳转到Box引用指向的值:x
复制代码

自定义智能指针

实现一个类似 Box 的自定义指针:


struct MyBox<T>(T); // MyBox是一个具有单个成员的元组结构体impl<T> MyBox<T> {  fn new(value: T) -> MyBox<T> {    Self(value)  }}
复制代码


像 Box<T>一样解引用:


let x = 5;let y = MyBox::new(x);assert_eq!(5, y); // error, 不能将MyBox引用与整数型对比assert_eq!(5, *y); // error,MyBox类型不能解引用
复制代码


通过实现 Deref trait 来使类型可解引用:


use std::ops::Deref;impl<T> Deref for MyBox<T> {  type Target = T; // 类型标注后边会有介绍,可先忽略  fn deref(&self) -> &T { // 返回成员的引用    &self.0  }}assert_eq!(5, *y); // 事实上*y会被编译成 *(y.deref())
复制代码

*号的行为

运算符包含两个行为:一个朴素的解引用 + deref()


首先通过 deref 获得一个引用,再使用朴素的解引用方式取到值,之所以 deref 返回的是一个引用,是因为如果返回一个值,那么这个值将被从 self 上移出,在大多数使用解引用运算符的场景下,我们并不希望获得 MyBox<T>内部值的所有权。可以将 deref 函数理解成:获取用于"解引用"的"引用类型数据"

函数和方法的隐式解引用转换

解引用转换(deref coercion)是 Rust 为函数和方法的参数提供的一种便捷特性。


加入类型 T 实现了 Deref trait,它能够将"T 的引用"转换为"T 经过 Deref 操作后生成的引用"。当我们将"某个类型的值引用"作为参数传递给函数或方法,但传入的类型与参数类型不一致时,解引用转换就会自动发生。编译器会插入一系列的 deref 方法调用来将我们提供的类型转换为参数所需的类型。




解引用 String:


fn hello(s: &str) {  println!("hello {}", s)}let s = String::from("world");hello(s); // error,预期一个&str,却传入了Stringhello(&s); // hello world,rust会自动将使用&String的deref,解引用后得到&str
复制代码


解引用 MyBox:


let m = MyBox(s);hello(m); // error,预期一个&str,却传入了MyBox(String)hello(&m); // hello world,对&MyBox(String)解引用得到&String,继续对&String解引用得到&str
复制代码


如果没有自动解引用功能只能手动解引用:


let a = &((*m)[..]);// 首先将MyBox<String>进行解引用得到String,// 然后,通过&和[..]来获取包含整个String的字符串切片以便匹配hello函数的签名hello(a); // hello world
复制代码

解引用转换与可变性

使用 Deref trait 能够重载不可变引用的运算符。与之类似,使用 DerefMut trait 能够重载可变引用的运算符。




通过实现 DerefMut trait 来使可变类型可解引用:


use std::ops::DerefMut;impl<T> DerefMut for MyBox<T> {  fn deref_mut(&mut self) -> &mut T {    &mut self.0  }}
复制代码


实现 DerefMut trait 的前提是必须已经实现了 Deref trait




使用可变类型解引用:


let s = String::from("world");let mut m = MyBox::new(s);fn hi(s: &mut str) {  println!("hello {}", s)}hi(&mut m); // 自动使用deref_mut解可变引用hi(&m); // error,不允许将不可变引用转为可变引用
复制代码

总结:

Rust 会在类型与 trait 满足下面 3 种情形时执行解引用转换:


  • 当 T: Deref<Target=U>时,允许 &T 转换为 &U。

  • 当 T: DerefMut<Target=U>时,允许 &mut T 转换为 &mut U。

  • 当 T: Deref<Target=U>时,允许 &mut T 转换为 &U。

为什么 &mut T 可以转换为 &U,而 &T 不能转换为 &mut U:

按照借用规则,如果存在一个可变引用,那么它就必须是唯一的引用(否则程序将无法通过编译)。将一个可变引用转换为不可变引用肯定不会破坏借用规则,但将一个不可变引用转换为可变引用则要求这个引用必须是唯一的,而借用规则无法保证这一点。

发布于: 5 小时前阅读数: 2
用户头像

码生笔谈

关注

欢迎关注「码生笔谈」公众号 2018.09.09 加入

前端、Rust干货分享,一起成为更好的Javascripter & Rustacean

评论

发布
暂无评论
5分钟速读之Rust权威指南(二十五)Deref