那么,这个bug 该如何修正呢?为什么&long类型的指针可以向&short类型赋值, 而 &Cell 类型的变量不能向&Cell 类型的变量赋值?因为对于具有内 部可变性特点的Cell 类型而言,它里面本来是要保存&long型指针的.
结果我们给了它 一 个 &short 型指针,那么在后面取出指针使用的时候,这个指针所指向的内容已经销毁, 就出现了野指针。这个bug 的解决方案是,禁止具有内部可变性的类型,针对生命周期参数 具有"协变/逆变"特性。这个功能是通过标准库中的UnsafeCell 类型实现的:
js
#[lang ="unsafe_cell"]
#[stable(feature ="rust1",since ="1.0.0")]
pub struct UnsafeCell<T:?Sized>{
value:T,
}
请注意这个类型上面的标记#[lang=.]。这个标记意味着这个类型是个特殊类型, 是被编译器特别照顾的类型。这个类型的说明文档需要特别提示读一下:
js
The core primitive for interior mutability in Rust.
UnsafeCell<T>is a type that wraps some T and indicates unsafe interior operations on the wrapped type.Types with an UnsafeCell<T>field are considered to have an 'unsafe interior'.The UnsafeCell<T>type is the only legal way to obtain aliasable data that is considered mutable.In general,transmuting an &T type into an &mut T is considered undefined behavior.
Types like Cell<T>and RefCell<T>use this type to wrap their internal data.
所有具有内部可变性特点的类型都必须基于UnsafeCell 来实现,否则必然出现各种 问题。这个类型是唯一合法的将&T 类型转为&mut T类型的办法。绝对不允许把&T 直接转换为&mutT 而获得可变性。这是未定义行为。
大家可以自行读一下Cell 和 RefCell 的源码,可以发现,它们能够正常工作的关键 在于它们都是基于UnsafeCell 实现的,而UnsafeCell 本身是编译器特殊照顾的类型。 所以我们说"内部可变性"这个概念是Rust 语言提供的一个核心概念,而不是通过库模拟出 来 的 。
实际上,上面那个CellV2 示例也正说明了写unsafe 代码的困难之处。许多时候,我们 的确需要使用unsafe 代码来完成功能,比如调用C 代码写出来的库等。但是却有可能一不小 心违反了Rust 编译器的规则。比如,你没读过上面这段文档的话,不大可能知道简单地通过 裸指针强制类型转换实现&T 到 &mut T的类型转换是错误的。这么做会在编译器的生命周 期静态检查过程中制造出一个漏洞,而且这个漏洞用简单的测试代码测不出来,只有在某些 复杂场景下才会导致内存不安全。Rust 代码中写unsafe 代码最困难的地方其实就在这样的细 节中,有些人在没有完全理解掌握Rust 的 safe 代码和 unsafe 代码之间的界限的情况下,乱 写 unsafe 代码,这是不负责任的。本书后面还会有一章专门讲解unsafe 关键字。
"解引用"(Deref) 是"取引用"(Ref) 的反操作。
取引用,我们有&、&mut 等操作符, 对应的,解引用,我们有*操作符,跟C 语言是一样的。示例如下:
js
fn main(){
let v1 =1;
let p=&v1;// 取引用操作
let v2 =*p; / / 解引用操作
println!("{}{}",v1,v2);
}
比如说,我们有引用类型p:&i32;, 那么可以用*符号执行解引用操作。上例中,v1的类型是i32,p 的类型是&i32,*p 的类型又返回i32。
自定义解引用
解引用操作可以被自定义。方法是,实现标准库中的std::ops::Deref 或 者 std::ops::DerefMut 这两个trait。Deref的定义如下所示。DerefMut的唯一区别是返回的是&mut 型引用都是类似的,因 此不过多介绍了。
js
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 的解引用转换:
js
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。

以上关系有点绕。关键是要理解,*expr 的类型是Target,而 deref()方法返回的 类型却是&Target。标准库中有许多我们常见的类型实现了这个Deref 操作符。比如Vec 、String、Box、Rc、Arc 等。它们都支持"解引用"操作。从某种意义上来说,它们都 可以算做特种形式的"指针"(像胖指针一样,是带有额外元数据的指针,只是元数据不限制 在 usize 范围内了)。我们可以把这些类型都称为"智能指针"。
比如我们可以这样理解这几个类型:
- Box是"指针",指向一个在堆上分配的对象;
- Vec是"指针",指向一组同类型的顺序排列的堆上分配的对象,且携带有当前 缓存空间总大小和元素个数大小的元数据;
- String是"指针",指向的是一个堆上分配的字节数组,其中保存的内容是合法的 utf8 字符序列。且携带有当前缓存空间总大小和字符串实际长度的元数据。
- 以上几个类型都对所指向的内容拥有所有权,管理着它们所指向的内存空间的分配和 释放。
- Rc 和 Arc 也是某种形式的、携带了额外元数据的"指针",它们提供的是一 种"共享"的所有权,当所有的引用计数指针都销毁之后,它们所指向的内存空间才 会被释放。
自定义解引用操作符可以让用户自行定义各种各样的"智能指针",完成各种各样的任 务。再配合上编译器的"自动"解引用机制,非常有用。下面我们讲解什么是"自动解引用"。
自动解引用
Rust 提供的"自动解引用"机制,是在某些场景下"隐式地""自动地"帮我们做了一些 事情。什么是自动解引用呢?下面用一个示例来说明:
js
fn main(){
let s ="hello";
println!("length:{}",s.len());
println!("length:{}",(&s).len());
println!("length:{}",(&&&&&&&&&&&&&s).len());
}
编译,成功。查文档我们可以知道,len() 这个方法的签名是:
js
fn len(&self)->usize
它接受的receiver 参数是&str, 因此我们可以用UFCS 语法调用:
js
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:
JS
impl<T:?Sized>Deref for Rc<T>{
type Target =T;
#[inline(always)]
fn deref(&self)->&T{
&self.inner().value
}
}
它的Target 类型是它的泛型参数T 。这么设计有什么好处呢?我们看下面的用法:
js
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 的意义。
实际上以下写法在编译器看起来是一样的:
js
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());
}
这就是为什么 String 需要实现Deref trait,是为了让&String 类型的变量可以在必 要的时候自动转换为&str 类型。所以String 类型的变量可以直接调用str类型的方法。 比如:
js
let s =String::from("hello");
let len =s.bytes();
虽 然s 的类型是String, 但它在调用bytes() 方法的时候,编译器会自动查找并转换 为s.deref().bytes() 调用。所以String 类型的变量就可以直接调用str 类型的方法了。
同 理 :Vec 类型也实现了Deref trait, 目标类型是[T],&Vec 类型的变量就可 以在必要的时候自动转换为&[T] 数组切片类型; RC 类型也实现了Deref trait, 目 标 类 型 是T,Rc 类型的变量就可以直接调用T 类型的方法。