(转)Rust: Rust的 Deref 运算符
Rust的 Deref 运算符
http://mp.weixin.qq.com/s/G28XE1rfX0nT6zIi86ji0Q
原创 2016-07-16 Rust编程公众号 Rust编程
“解引用(Deref)”是“引用(Ref)”的反操作。比如说,我们有引用类型let p: &T;,那么可以用*符号执行解引用操作,let v: T = *p;。如果p的类型是&T, 那么*p的类型就是T。
自定义解引用
解引用操作,可以被自定义。方法是,实现标准库中的std::ops::Deref和std::ops::DerefMut这两个 trait。
Deref的定义如下所示,DerefMut的唯一区别是返回的是&mut型引用,都是类似的,因此不过多做介绍了。
pub trait Deref {type Target: ?Sized;fn deref(&self) -> &Self::Target;
}pub trait DerefMut: Deref {fn deref_mut(&mut self) -> &mut Self::Target;
}
这个 trait 有一个关联类型 Target,代表解引用之后的目标类型。
比如说,标准库中,实现了String向str的解引用转换:
impl ops::Deref for String {type Target = str;#[inline]fn deref(&self) -> &str {unsafe { str::from_utf8_unchecked(&self.vec) }}
}
请大家注意这里的类型,deref() 方法返回的类型是 &Target,而不是 Target。如果说有变量s的类型为String,*s 的类型并不等于 s.deref() 的类型。*s的类型实际上是 Target,即str。&*s的类型为&str。而 s.deref() 的类型为 &Target,即 &str。它们的关系为:
s : String
&s : &String
Target : str
s.deref() : &str
*s : str
&*s : &str
标准库中,有许多我们常见的类型,实现了这个 Deref 操作符。比如 Vec、String、Box、Rc、Arc等。 它们都支持“解引用”这个操作。从某种意义上来说,它们都可以算做特种形式的“指针”,(像胖指针一样,是带有额外元数据的指针)。
&[T]是指针,指向一个数组切片;
&str是“指针”,指向一个字符串切片;
它们不仅包含了指向数据的指针,还携带了所指向的数据的长度信息,但它们对指向的数组/字符串切片没有所有权,不负责内存空间的分配和释放。
Box是“指针”,指向一个在堆上分配的对象;
Vec是“指针”,指向一组同类型的顺序排列的堆上分配的对象;
String是“指针”,指向的是一个堆上分配的字节数组,其中保存的内容是合法的 utf8 字符序列。
它们都对所指向的内容拥有所有权,管理着它们所指向的内存空间的分配和释放。
Rc和Arc也算是某种形式的“指针”,它们提供的是一种“共享”的所有权,当所有的引用计数指针都销毁之后,它们所指向的内存空间才会被释放。
自定义解引用操作符,可以让用户自行定义各种各样的“智能指针”,完成各种各样的任务。再配合上编译器的“自动”解引用机制,非常有用。下面我们讲解什么是“自动解引用”。
自动解引用
Rust的设计理念一向是“显式比隐式好”。代码应该尽可能地将它的行为明显地表达出来,避免在看不见的地方“自动”帮我们做一些事情。
凡事都有例外。Rust中最容易被初学者误解的一个“隐式”行为就是这个“自动解引用”。什么是自动解引用呢,下面用一个示例来说明:
fn main() {let s = "hello";println!("length: {}", s.len());println!("length: {}", (&s).len());println!("length: {}", (&&&&&&&&&&&&&s).len());
}
编译发现,可以编译成功。我们知道,len这个方法的签名是:
fn len(&self) -> usize
它接受的参数是&str,因此我们可以用 UFCS 语法这么调用:
println!(“length: {}”, str::len(&s));
但是,我们如果使用&&&&&&&&&&str类型来调用成员方法,也是可以的。原因就是,Rust编译器帮我们做了隐式的 deref 调用,当它找不到这个成员方法的时候,它会自动尝试使用deref方法后再找该方法,一直循环下去。编译器在&&&str类型里面找不到len方法,就尝试将它deref,变成&&str类型,再寻找len方法,还是没找到,那么继续deref,变成&str,现在找到len方法了,于是就调用这个方法。
自动deref的规则是,如果类型T可以解引用为U,即T: Deref,则&T可以转为&U。
自动解引用的用处
用Rc这个“智能指针”举例。Rc实现了Deref:
impl<T: ?Sized> Deref for Rc<T> {type Target = T;#[inline(always)]fn deref(&self) -> &T {&self.inner().value}
}
它的 Target 类型是它的泛型参数 T。这么设计有什么好处呢,我们看下面的用法:
use std::rc::Rc;fn main() {let s = Rc::new(String::from("hello"));println!("{:?}", s.bytes());
}
我们创建了一个指向String类型的Rc指针,并调用了bytes()方法。这里是不是有点奇怪?
Rc类型本身并没有bytes()方法,所以编译器会尝试自动deref,试试s.deref().bytes()。String类型其实也没有bytes()方法,但是String可以继续deref,于是再试试s.deref().deref().bytes()。这次在str类型中,找到了bytes()方法,于是编译通过。
我们实际上通过Rc类型的变量,调用了str类型的方法,让这个智能指针像个透明的存在。这就是自动Deref的意义。
实际上以下写法在编译器看起来是一样的:
use std::rc::Rc;
use std::ops::Deref;fn main() {let s = Rc::new(String::from("hello"));println!("length: {}", s.len());println!("length: {}", s.deref().len());println!("length: {}", s.deref().deref().len());println!("length: {}", (*s).len());println!("length: {}", (&*s).len());println!("length: {}", (&**s).len());
}
注意:我们可以写let p = &*s;,它可以创建一个指向内部String的指针。这种写法不等于
let tmp = *s;
let x = &tmp;
因为这个tmp的存在,它表达的是move语义。也不等于
let x = &{*s};
这个大括号引入了新的 scope,同样也是move语义。
有时候需要手动处理
如果说,智能指针中的方法与它内部成员的方法冲突了怎么办呢?编译器会优先调用当前最匹配的类型,而不会执行自动 deref,这种情况下,我们就只能手动 deref 来表达我们的需求了。
比如说,Rc类型和String类型都有clone方法,但是它们执行的任务不同。Rc::clone()做的是把引用计数指针复制一份,把引用计数加1。String::clone()做的是把字符串复制一份。示例如下:
use std::rc::Rc;
use std::ops::Deref;fn type_of(_: ()) { }fn main() {let s = Rc::new(Rc::new(String::from("hello")));let s1 = s.clone(); // (1)//type_of(s1);let ps1 = (*s).clone(); // (2)//type_of(ps1);let pps1 = (**s).clone(); // (3)//type_of(pps1);
}
在以上的代码中,位置(1)处,s1的类型为Rc
fn main() {let s = String::new();match &s {"" => {}_ => {}}
}
这段代码编译会发生错误,错误信息为:
mismatched types:expected `&collections::string::String`,found `&'static str`
match 后面的变量类型是 &String,匹配分支的变量类型为 &str,这种情况下就需要我们手工完成类型转换了。为了将&String类型转换为&str类型,手工类型转换的话有哪些办法呢?
参考答案:
match s.as_ref()。 这个方法是最通用最直观的办法。
match s.borrow()。为了使用这个方法,我们必须引入Borrow trait。也就是需要加上代码use std::borrow::Borrow;。
match s.deref()。 这个方法通过主动调用deref()方法,达到类型转换的目的。此时我们需要引入Deref trait方可以通过编译,即加上代码use std::ops::Deref;。
match &*s。 我们可以通过*s运算符,也可以强制调用deref()方法,与上面的做法一样。
match &s[..]。这个方案也是可以的,这里利用了String重载的Index操作。
总结
Rust中允许一部分运算符可以由用户自定义行为,类似其它语言中的“操作符重载”。其中解引用是一个非常重要的操作符,它允许重载。
而需要提醒大家注意的是,取引用操作符,如 & &mut 等,是不允许重载的。因此,取引用& 和 解引用* 并非对称互补关系。*&T的类型一定是T,而&*T 的类型未必就是 T。
更重要的是,读者需要理解,在某些情况下,编译器帮我们插入了自动 deref 的调用,简化代码。
(转)Rust: Rust的 Deref 运算符相关推荐
- [rust] Rust与C++20编码习惯对照
Rust 文章目录 Rust 差异 所有权 字符串 类型结构 生命周期 常量 基本类型 构造类型 String Vec HashMap File 泛型 Trait 编译时多态 运行时多态 函数 Lam ...
- Rust 基础(三)
六.枚举和模式匹配 在本章中,我们将研究enumeration,也称为enum. 枚举允许通过枚举可能的变体来定义类型. 首先,将定义和使用枚举来展示枚举如何与数据一起编码意义.接下来,将探索一个特别 ...
- 8万字带你入门Rust
Rust ?? 学习建议: 先从 整体出发,不要让自己陷入到细节中去 和自己已知的知识建立联系 rust 和go一样采用 组合的手段实现代码复用,不要深思为什么不是继承 学会阅读源码,从源码中学习 R ...
- gm怎么刷东西 rust_Rust语言:解引用详述,搞不明白这个概念,趁早放弃Rust
Rust是内存安全的,对新手来说,最大的困难是可恶的编译器,在其他语言上面叱咤风云,偏偏被Rust搞到崩溃.所以,大家都戏谑道,Rust是面向编译器编程. 和编译器做斗争的过程中,遇到最多的是,变量所 ...
- Rust实战系列-基本语法
本文是<Rust in action>学习总结系列的第二部分,更多内容请看已发布文章: 一.Rust实战系列-Rust介绍 " 主要介绍 Rust 的语法.基本类型和数据结构,通 ...
- Rust 五分钟了解,三十分种入门
Rust 快速入门 初始化项目 基础 变量 - 常量 数据类型 函数 注释 控制流 所有权 移动 克隆 所有权与函数 返回值与作用域 引用与借用 可变引用 Slice 类型 其他类型的 slice 结 ...
- 回溯 Rust 2020:正成为最受欢迎的编程语言
[CSDN 编者按]在 2020 年,Rust 的 Github Star 数达到了 51K,Reddit Fans 则涨至 125 K,全年合并 PR 有 8114 个.这些数据无不在说明,Rust ...
- RUST直接升钢指令_[译]参照TypeScript学习Rust-part-1
[译]参照TypeScript学习Rust-1 · 前端在线regx.vip 对于前端,笔者比较认可Rust作为前端开发技术栈投资的,本文系列翻译旨在分享.学习Rust这门语言. Rust常常被认为 ...
- Redox随笔(2)-用Rust语言编写的类UNIX操作系统
与其他操作系统相比,Redox如何 我们与其他操作系统有很多共同之处. 由于 Redox syscall接口是Unix-y.例如,我们有open, pipe, pipe2, lseek, read, ...
- rust 编程入门_面向初学者的Rust –最受欢迎的编程语言入门
rust 编程入门 Rust has been voted Stack Overflow's most loved programming language for five years in a r ...
最新文章
- 26岁应届博士被聘985博导!入职半年实现学院顶会论文零的突破
- C#开发Unity游戏教程之游戏对象的行为逻辑方法
- Flink从入门到精通100篇(十一)-Java SPI 机制在 Flink SQL 中的应用
- VError - Found 0 matching services的根源分析
- [CareerCup] 4.7 Lowest Common Ancestor of a Binary Search Tree 二叉树的最小共同父节点
- MySQL数据库之MyISAM与InnoDB的区别
- sql server一对多怎么查询_Vlookup函数查找最后一个值和一对多查询
- SQLite连接C#笔记
- Linux音频驱动-WAV文件格式分析
- Jeecg-Boot前后端分离版
- 通达信公式-当天成交量不大于百日均成交量比例
- Ant Design - Anchor
- C语言编程基础,手机购物程序的设计
- vue html if,vue中v-if使用方法详解
- 联合几位大佬给大家送110本技术书籍!包邮到家!!
- Matlab中table类型使用技巧
- 【C语言知识梳理之分支语句】
- python爬取视频自动播放_求助该网站如何让它能自动播放下一个视频。。。醉了,要挂80个课时...
- 数字图像频谱的中心化
- centos服务器部署