为什么会有这篇文章?
在跟着官方文档学习Rust智能指针的时候,第一次感受到困惑不得甚解。例如:Smart pointers, on the other hand, are data structures that act like a pointer but also have additional metadata and capabilities,翻译过来就是:智能指针=指针+元数据和功能。在我翻阅无数资料后尝试去理解、总结一下。
从官方文档的一句话开始:Smart pointers are usually implemented using structs. Unlike an ordinary struct, smart pointers implement the Deref
and Drop
traits. 如果不理解文章开头的一句话那么这句话从技术层面定义什么是智能指针:就是实现Deref
或Drop
的结构体。我们知道Rust中通过trait来赋予类型的行为,Deref
赋予类型解引用的能力即:对于实现Deref
的某个类型引用x,可以调用*x
;Drop
赋予类型在离开作用域后执行的一些代码。这就区分了智能指针和普通指针的区别,具备一些特殊的行为。那么只需要再回答一下智能指针智能在哪里?
从技术角度来定义就是实现了Deref
或Drop
的结构体,例如官方给出的最简单直接的智能指针Box
fn main() {
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
Deref的实现如下:
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_box", issue = "92521")]
impl<T: ?Sized, A: Allocator> const Deref for Box<T, A> {
type Target = T;
fn deref(&self) -> &T {
&**self
}
}
deref方法的意义在于当编译器遇到*x
的解引用操作是会自动调用deref方法,稍后会举例验证。首先可能需要解释一下&**self
是什么操作。图解如下
注:Addr表示值在堆中的内存地址
下面来验证一下*x
解引用时调用的是deref方法,自定义一个元组结构体MyBox
// 定一个元组结构体
struct MyBox<T>(T);
// 定义构造方法
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
测试用例
fn main() {
let my_box = MyBox::new(5);
let _x = *my_box;
}
build结果
wjun :: learn/toolchain/demo ‹master*› » cargo build
Compiling demo v0.1.0 (/Users/wjun/Documents/Program/Rust/learn/toolchain/demo)
error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
--> demo/src/main.rs:3:14
|
3 | let _x = *my_box;
| ^^^^^^^
For more information about this error, try `rustc --explain E0614`.
error: could not compile `demo` due to previous error
当前类型不能被解引用,下面尝试实现Deref
// 为 MyBox 实现 Deref
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
注:返回值&Self::Target和Box源码中返回值&T是等价的,这里是编译器自动生成的
再次build没有报错。
上一节我们实现了一个自定义智能指针MyBox
,下面来探讨智能在哪里;首先定义一个普通的结构体
struct User {
name: &'static str,
}
impl User {
fn name(&self) {
println!("{}", &self.name)
}
}
然后使用智能指针进行包装
fn where_smart_pointer() {
let user = User { name: "王一川" };
let my_box = MyBox::new(user);
my_box.name();
}
这时候细心的小伙伴发现问题了,这能编译通过?MyBox哪来的name方法,name方法只在User中定义的。这就是智能指针智能的其中一点
可以自动解引用,提高开发效率
其调用逻辑是:当遇到.
操作调用的时候,会触发自动解引用,上一节MyBox中deref逻辑是返回&T,即user的引用那么自然就可以调用name方法了。
注:这里是自动解引用,要区别于*操作是强制解引用,是自动和手动的区别
再看一个例子
fn print_str(line: &str) {
println!("{}", line);
}
一个简单的逻辑,打印字符串字面量,相信大家学习Rust的时候一定做过这样子的调用
fn where_smart_pointer() {
let line = String::from("王一川");
print_str(&line);
}
要求&str但传入&String,之前的解释大多是str可以看成字符串切片,str等价于String[…],所以编译器可以识别。这种解释也不能算错,但官方在智能指针中说:we’ve already encountered a few smart pointers in this book, including String
and Vec
,String和Vec也是智能指针,可以看到String中对deref方法的实现
#[stable(feature = "rust1", since = "1.0.0")]
impl ops::Deref for String {
type Target = str;
#[inline]
fn deref(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.vec) }
}
}
也就是说如果对String类型解引用可以获得&str,那么这一切就解释通了,作为函数参数会自动触发解引用操作
同理Vec的deref方法的实现也是如此
#[stable(feature = "rust1", since = "1.0.0")]
impl<T, A: Allocator> ops::Deref for Vec<T, A> {
type Target = [T];
#[inline]
fn deref(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.as_ptr(), self.len) }
}
}
上述是Deref
带来的智能,那么Drop
带来的只能就是自动的内存管理,即
可以自动化管理内存,安全无忧
但是我们Rust不允许我们手动调用drop方法,因为这样就造成了double free,这块在官方文档中解释的比较详细就不做赘述了,相信到这里大家对智能指针的理解应该会清晰很多,这时候在回过头看官方文档就不会很懵。
总结一下: