智能指针
本文大部分内容翻译自:The Rust Programming Language
1. Using Box to Point to Data on the Heap
2. Treating Smart Pointers Like Regular References with the Deref Trait
Implementing the Deref trait allows you to customize the behavior of the dereference operator *
(not to be confused with the multiplication or glob operator).
2.1. Following the Pointer to the Value
例:
1 | fn main() { |
The variable x holds an i32 value 5. We set y equal to a reference to x. We can assert that x is equal to 5. However, if we want to make an assertion about the value in y, we have to use *y to follow the reference to the value it’s pointing to (hence dereference) so the compiler can compare the actual value. Once we dereference y, we have access to the integer value y is pointing to that we can compare with 5.
2.2. Using Box Like a Reference
We can use a Box
1 | fn main() { |
The main difference between Listing 15-7 and Listing 15-6 is that here we set y to be an instance of a box pointing to a copied value of x rather than a reference pointing to the value of x.
2.3. Defining Our Own Smart Pointer
- 为了能在自定义类型上使用
*
来解引用,需要为该类型实现Deref
trait Deref
trait要求实现deref
方法deref
方法以自身类型的引用(&self)为参数,返回类型内部数据的引用deref
方法返回数据的引用、而非数据本身的原因:由于所有权系统的存在,如果返回数据本身,就会将内部数据的所有权移出自定义类型(MyBox)。而大部分情况下,我们在解引用后并不需要获取MyBox内部数据的所有权。
例:
1 | use std::ops::Deref; |
使用MyBox
:
1 | fn main() { |
没有实现Deref
trait的时候,编译器只懂得如何将&
解引用。deref
方法告诉编译器,如果想要解引用实现了Deref
trait的类型,调用我(deref
方法)就好了。deref
方法会返回一个&
引用,而对&
引用进行解引用正是编译器懂得的。
上面assert_eq!(5, *y);
中,Rust实际运行的是:
1 | *(y.deref()) |
Rust将*
替换成y.deref()
,然后再对返回的结果&self.0
进行它所熟知的、对&
解引用,即*(&self.0)
。
2.4. 函数和方法的隐式 Deref 强制转换
Deref 强制转换(deref coercions)只能作用于实现了Deref
trait的类型,它可以将这个实现了Deref
trait的类型的引用,转换成另一个类型的引用。
例如,deref coercion可以将&String
强制转换成&str
,因为String类型实现了Deref
trait,所以可以返回&str
。
当我们把一个与函数或者方法的参数类型不符的某种类型的引用作为参数传给它们时,会自动进行强制转换。这时会有一系列的deref
方法被调用,把我们提供的类型转换成参数所需的类型。
例:
1 | // hello的参数`name`的类型为`&str` |
- 上面代码使用
&m
作为参数调用hello
函数,而&m
实际上是MyBox<String>
的引用,即&MyBox<String>
。 - 由于我们为
MyBox<T>
实现了Deref
trait,所以Rust可以通过调用MyBox<T>
的deref
方法,将&MyBox<String>
转换成&String
。 - 标准库为
String
类型提供了Deref
trait的实现,对String
解引用会返回&str
,因此Rust再次调用deref
方法,将&String
转换为&str
,而&str
正与hello
函数的参数类型匹配。
如果Rust没有提供强制转换,我们就需要这样调用hello
函数:
1 | fn main() { |
(*m)
调用MyBox<String>
类型的deref
方法,将它解引用为String
类型- *m <=> *(m.deref()) <=> *(&self.0) <=> self.0 =>
String
类型
- *m <=> *(m.deref()) <=> *(&self.0) <=> self.0 =>
&
和[..]
作用于String
类型,就是从String
类型取一段字符串切片(string slice),即&str
,这个切片为整个字符串
当涉及到实现了Deref
trait的类型的解引用时,Rust会分析这些类型,并按需自动调用任意多次Deref::deref
方法,来获取与参数类型匹配的引用类型。
2.5. Deref 强制转换如何与可变性交互
我们可以使用Deref
trait来重载(override)不可变引用的*
运算符,类似地,也可以用DerefMut
trait来重载可变引用的*
运算符。
Rust进行强制转换的3种情况:
- 将
&T
转换为&U
,其中T: Deref<Target=U>
- 将
&mut T
转换为&mut U
,其中T: DerefMut<Target=U>
- 将
&mut T
转换为&U
,其中T: Deref<Target=U>
前两种情况除了可变性之外是相同的:
- 第一种情况表明,如果有一个
&T
,其中T
实现了Deref
为U
类型,则可以直接得到&U
- 第二种情况表明,对于可变引用也有着相同的行为
第三个情况有些微妙:
- Rust可以将可变引用强转为不可变引用。但是反之是不可能的:不可变引用永远也不能强转为可变引用。因为根据借用规则,如果你有一个可变引用,那么它必须是数据的唯一引用(否则程序将无法编译)。
- 将一个可变引用转换为不可变引用永远也不会打破借用规则,而将不可变引用转换为可变引用则需要初始的不可变引用是数据唯一的不可变引用,但借用规则无法保证这一点。