本文是对Programming Rust 第5章的总结,部分代码与图片来自此书
引用类型
rust有三种指针类型
引用裸指针智能指针
其中引用是一种非拥有指针,即不需要对被引用对象的生命周期负责,相反,引用的生命周期不能超过被引用者的生命周期
很多时候我们并不想转移值的所有权,仅仅需要访问值的某些数据,这个时候就需要使用引用了。
引用分为两种类型:
共享引用可以读取被引用对象但是不能修改。同时可以存在多个共享引用,共享引用本身是Copy类型可变引用可以读取并且修改被引用对象,同时只能存在一个可变引用, 并且可变引用存在的时候, 不能存在任何共享引用,可变引用不是Copy类型
需要说明的是, 可变引用存在的时候, 不但是共享引用不能存在, Owner自己也不能访问和修改被引用对象
使用引用
以下是使用引用的一些示例
| |
隐式解引用/隐式借引用
这里需要注意的是由于解引用,借引用是非常常见的操作,而且编码起来显得非常繁琐,所以rust对.操作符的左值做了隐式解引用与隐式借引用的操作
| |
嵌套引用
rust中引用本身也是一个左值, 所以可以对引用再引用,比如
| |
有个值得注意的点是在The book中有一段是这么说的
Note that the
*operator is replaced with a call to thederefmethod and then a call to the*operator just once, each time we use a*in our code
上面的代码表面,对于引用,一个*操作符可以直接解引用到多个层级之上的类型。
我认为这是由于当我指定了类型之后, rust编译器会根据目标类型进行Deref Coercion, 我姑且叫它强制类型转换,强制类型转换会无限的将一个引用转成它可以转换到的引用,如果一个类型符合 T: Deref<Target=U>
引用比较/计算
引用比较的是他们背后的值, 引用只能在相同类型直接做比较
引用运算可以计算一层引用的背后的值, 多层引用则不能相加
| |
胖指针
rust有两种常见的胖指针
- 对
slice的引用, 额外包含length信息 - 对
trait object的引用, 额外包含trait实现地址,即虚表
胖指针名字的来源是因为这种指针包含两个字长,一般的指针只包含一个。
指向unszie type的指针都是胖指针
引用安全
引用在rust中是内存安全的, 引用不会产生悬空指针的问题,rust通过lifetime来解决这个问题,以下这个段代码展示了违背生命周期的情况
| |
x在引用r之前被释放了, 这种情况是无法通过编译的, rust对于生命周期的限制如下:
假如我们有一个变量v的引用&v, 使变量r=&v, 我们必须满足
变量v的生命周期必须包含&v的生命周期
&v的生命周期必须包含r的生命周期
这两种情况任意一个不满足都会产生悬空指针。
在我看来r是v的一个引用,只需要满足v的生命周期包含r的生命周期即可
引用作为函数入参
一个带lifetime的函数是这样子的
| |
这里f<'a> ‘a是作为函数的参数存在, 我们可以认为是 对于任意生命周期都适用,大部分情况下, 一个实参的周期是大于函数调用的。但是如果函数中存在这种情况,就不行了
| |
STASH的生命周期是全局的, 而根据上面的规则2,p的生命周期必须包含’static。显然对于任意生命周来说这是不满足的。这个时候只有将形参改成’static才能满足这个条件
| |
以上的函数是正确的, 但是适用范围也更小了。
引用作为函数返回
以下是一个返回引用的函数
| |
我们忽略了生命周期, 当一个函数只有一个引用作为参数的时候,如果它的返回值包含引用, rust会认为它们有同样的生命周期。现在考虑以下的代码
| |
根据上面的规则1,2, 必须满足parabola的生命周期必须大于s的生命周期, 这段代码中parabola要比s更早drop,所以是不能通过编译的
结构体包含引用
结构体必须包含生命周期参数,不然是无法通过编译的
| |
省略生命周期参数
省略生命周期有以下几种情况:
- 函数不返回引用的情况下, 不需要显示指定
生命周期 - 如果函数只有一个参数,那么也不需要指定生命周期, 因为rust为认为任何返回引用的
生命周期都与此参数相同 - 如果函数是一个类型的方法,并且形参是引用类型
self(&self, &mut self), rust会认为返回引用的生命周期与此相同
后续
rust的引用是一个根本又复杂的话题,这里还有很多东西没有讨论清楚, 比如强制类型转换, 比如重借用等等, 后续还需要进一步的研究