声明:本文假设你有一定的程序设计基础,假设你学习过一些智能指针方面的内容,所以关于智能指针的介绍,作者在这里不多说了。
目录
最简单的智能指针Box<T>
允许使用一个Box来指向堆区上的数据,就像这样
fn work_1() {
let p = Box::new(10);
}
就像C中的指针一样,智能指针也允许我们使用解引用操作符来解引用指针指向的数据
fn work_1() {
let p = Box::new(10);
let val = *p;
println!("*p={}",*p);
println!("val={}",val);
}
运行
jan@7X:~/Code/Rust/smart_pointer$ cargo run
Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
Finished dev [unoptimized + debuginfo] target(s) in 0.20s
Running `target/debug/smart_pointer`
*p=10
val=10
p本身是一个栈上的指针,指向堆区的数据。当p离开作用域的时候,Box的对对象,也就是存储在栈上的数据会被回收,这是rust的语言层面提供的,并且其指向的堆内存也会随其回收,着不是rust语言层面提供的(具体的实现方法,下面会将)。这就是所谓智能。
Deref trait
所说的deref trait,仅仅是一个trait,实现 Deref
trait 允许我们重载 解引用运算符,就像是C++中的operator*,通过这种方式实现 Deref
trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
简单的解引用
引用,或者借用,就是一种指针类型。所以,适用于C中的规则,在rust中也可以看见。就像这样。
#[allow(unused)]
fn work_2() {
let a = 10;
let ra = &a;
println!("a={},*ra={}",a,*ra);
}
Box的解引用
因为Box实现了上面所说的Deref trait所以可以使用其*来像普通指针一样访问数据。
#[allow(unused)]
fn work_3() {
let a = Box::new(5);
println!("{}",*a);
}
关于println!的说明
实际上,在使用prinln!宏的时候,编译器会为我们的引用(就是指针)自动的加上解引用符号。
就像这样,他们输出的值都是一样的,但也仅仅就是输出的值一样而已。
#[allow(unused)]
fn work_2() {
let a = 10;
let ra = &a;
println!("a={},ra={},*ra={}",a,ra,*ra);
}
对于Box来也是可行的,但是原因是因为Box实现了Displat这个trait
#[allow(unused)]
fn work_3() {
let a = Box::new(5);
println!("{}",*a);
println!("{}",a); //error
}
区分引用和变量本身并了解编译器为我们添加了哪些代码
他们并不能相比较,因为rust是强类型语言,比如,你不能用用一个i32类型和一个&i32来比较,就像这样。
fn work_4() {
let i = 32;
let ref ri = i;
assert_eq!(i,ri);
}
我们会得到一个这样的编译错误
error[E0277]: can't compare `{integer}` with `&{integer}`
--> src/main.rs:108:5
|
108 | assert_eq!(i,ri);
| ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
但是引用在一些情况却又像变量本身一样,类似C++的引用一样。比如调用结构体的方法。
#[allow(unused)]
fn get_len(s: &str) -> usize {
s.len()
// 等价于(*s).len()
// 注意区别于*s.len
}
同样的,在调用方法的时候编译器会为我们自动进行解引用。
不要将引用和借用的概念分离开
看到网上有人说借用是借用,引用是引用,让人一头雾水。其实引用和借用不是区分开来说的。我们可以看一下官方的文档是如何解释借用和引用的。
我们将创建一个引用的行为称为 借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。
从中可以看出,官方把借用描述成一种行为,为不是某个数据类型。就好比这样。
#[allow(unused)]
fn work_5() {
let i = 10; //让i绑定10,就是说现在这个10的所有权是i
let ref r1 = i; // 把i借给一个不可变的引用r1,发生借用这个行为
let r2 = &i; // 把i借给一个不可变课引用r2,发生借用这个行为
}
两种借用写法本质是相同的,只不过是语法不同,请不要将他们的概念分离开。
通常我们说一个变量的借用,是指的这个变量的引用,不指这个借用的行为。
就像我可以说人r1是i的一个借用,也可以说r1是i的引用。引用和借用在这里是同义词。
有了这些基础,我们就可以进行进一步的探索。
Rust需要编译时确定类型大小
Rust中,分配在栈上的数据总是要在编译时确定大小。
指针类型毫无疑问是一个在编译时可确定大小的类型。
递归类型无法在编译时确定大小
有关这部分,请查阅Rust官方文档,官方教程在这里讲的很好。
使用 Box<T> 指向堆上的数据 - Rust 程序设计语言 中文版
实现一个自己的智能指针
我们将编写一个自己的只能指针,确保其在堆区开辟数据,在离开作用域的时候drop。
定义
为了保证不分散注意力,这里的Box我们仅仅定义为一个简单包装了T类型的一个元组结构体
struct Box<T> (T);
否则我们还要使用全局的分配器,那也是一个很大的话题,不在本篇讨论范围内。
关联函数new
我们仅仅需要返回一个元组结构体,不再堆区开辟任何数据
struct MyBox<T> (T);
impl<T> MyBox<T> {
fn new(val: T) -> Self {
MyBox(val)
}
}
实现Deref trait
显而易见的,返回&self.0即可
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
现在这就是一个具备解引用功能的智能指针了。
如果你还不知道什么是trait的关联类型(type Target = T),请查阅有关文章或者书籍。
了解编译器又为我们实现了哪些代码
你可能会奇怪上Deref trait的返回值,解引用操作应当返回最原始的数据,而非引用。
其实,如果一个T类型实现Deref这个trait,那么在解引用的时候,编译器就会自动的为我们生成这样的代码。
let t = T::new(some_val);
*t 等价于 *(t.deref())
至于编译器为什么这样做官方有着很好的解释
Rust 将
*
运算符替换为先调用deref
方法再进行普通解引用的操作,如此我们便不用担心是否还需手动调用deref
方法了。Rust 的这个特性可以让我们写出行为一致的代码,无论是面对的是常规引用还是实现了Deref
的类型。
deref
方法返回了一个值的引用,而*(y.deref())
括号外边的普通解引用仍然必须存在的原因是因为所有权。如果deref
方法直接返回值而不是值的引用,其值(的所有权)将被移出self
。在这里以及大部分使用解引用运算符的情况下,我们并不希望获取MyBox<T>
内部值的所有权。注意,每次当我们在代码中使用
*
时,*
运算符都被替换成了先调用deref
方法再接着使用*
解引用的操作,且只会发生一次,不会对*
操作符无限递归替换
实现Drop trait
实现了drop的struct,就像实现的析构函数一样,虽然rust中没有析构函数一说,但是其行为却和析构函数类似。
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
println!("析构一些资源");
}
}
测试一下
#[allow(unused)]
fn work_6() {
let a = MyBox::new(10);
}
结果正确
jan@7X:~/Code/Rust/smart_pointer$ cargo run
Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
Finished dev [unoptimized + debuginfo] target(s) in 0.24s
Running `target/debug/smart_pointer`
析构一些资源
函数和方法的隐式解引用强制转换
或许这个标题看起来有些令人害怕,其实本质上就是特殊情况下的隐式类型转换而已。
简单的来说如果一个类型T实现了Deref trait,并且这个T的关联类型为U,那么就可以实现隐式的从T到&U的转换,因为编译器可以自动帮助我们调用deref这个方法。
最经典的例子就是&String到&str的转换,也就是字符串到字符串切片的转换。
我们来看一下这个函数,他做的功能很简单,接受一个字符串切片,并打印其长度和字符串本身。
#[allow(unused)]
fn print_str_and_len(s: &str) {
println!("str={},len={}",s,s.len());
}
fn main() {
let s = "hello";
let s1 = String::from("hello");
print_str_and_len(s);
print_str_and_len(&s1);
}
运行结果
jan@7X:~/Code/Rust/smart_pointer$ cargo run
Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
Finished dev [unoptimized + debuginfo] target(s) in 0.23s
Running `target/debug/smart_pointer`
str=hello,len=5
str=hello,len=5
为什会这样,Rust是强类型语言,一个&str怎么能接受一个&String类型?
其实这里就发生了我们所说的隐式的解引用强制转换。
我们来看一下String的源代码
#[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) }
}
}
你会发现其关联类型为str,返回一个&str类型,正好符合我们上述所说的规则。于是,&String就转换为了&str。
有趣的是,这个强制转换可以递归的进行,直到不能再解引用为止。
我们上面实现的MyBox struct实现了一个Targe为T的deref方法,所以,一个MyBox<T>就可以进行上述的转换。并且如果这个T也实现了deref的话,T又能变为&U,一直这样的递归下去。
我们可以尝试一下。
#[allow(unused)]
fn work_7() {
let s = MyBox::new(String::from("hello"));
print_str_and_len(&s);
}
运行
jan@7X:~/Code/Rust/smart_pointer$ cargo run
Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
Finished dev [unoptimized + debuginfo] target(s) in 0.21s
Running `target/debug/smart_pointer`
str=hello,len=5
析构一些资源
很好。
Box<T>的实战训练
你可以尝试着用Box在Rust中实现链表
使用Box<T>,Rust实现链表是一件非常折磨的事情,作者在这里提供一份简单的链表供读者参考。
#[derive(Debug)]
struct ListNode<T: fmt::Display> {
data: T,
next: Option<Box<ListNode<T>>>,
}
impl<T: fmt::Display> ListNode<T> {
fn new(val: T) -> ListNode<T> {
ListNode { data: val ,next: None }
}
}
#[derive(Debug)]
struct ForwardList<T: fmt::Display> {
size: u32,
head: Option<Box<ListNode<T>>>
}
impl<T: fmt::Display> ForwardList<T> {
fn new() -> ForwardList<T> {
ForwardList { size: 0, head: None }
}
fn push_front(&mut self, val: T) {
let mut node = ListNode::new(val);
if let Some(box_) = self.head.take() {
node.next = Some(box_);
}
self.head = Some(Box::new(node));
self.size += 1;
}
fn display(&self) {
print!("[");
let mut now = &self.head;
let mut first = true;
while let Some(v) = now {
match first {
true => {
print!("{}",v.data);
first = false;
},
false => print!(", {}",v.data),
}
match &v.next {
None => {
break;
},
node => {
now = &node;
}
}
}
println!("]");
}
#[allow(unused)]
fn front(&self) -> Option<&T> {
match &self.head {
None => None,
Some(node) => Some(&node.data),
}
}
fn clear(&mut self) {
self.head = None;
}
}
引用型智能指针Rc<T>
关于什么是引用计数,作者这里就不说了。现在这个环境来说,能够学习rust的,想必都有一些其他语言的基础。如果你来自C++就像作者一样,肯定不会陌生shared_ptr等系列的智能指针,Rc就和shared_ptr的概念一样,只是写法不同。
使用Rc需要use std::rc::Rc,Rc并没有被预导入。
在学习Rc前,我们应当做一些准备。
struct Foo(i32);
impl Drop for Foo {
fn drop(&mut self) {
println!("drop!");
}
}
impl Foo {
fn new(x: i32) -> Self {
Foo(x)
}
}
很简单,这是在这个struct析构的时候进行一条打印消息。
Rc的克隆行为是增加计数
#[allow(unused)]
fn work_9() {
let r = Rc::new(Foo::new(10));
let r1 = r.clone();
let r3 = Rc::clone(&r);
println!("{},{},{}",Rc::strong_count(&r),Rc::strong_count(&r1),Rc::strong_count(&r3));
}
运行结果
jan@7X:~/Code/Rust/smart_pointer$ cargo run
Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
Running `target/debug/smart_pointer`
3,3,3
drop!
Rc同样实现了Deref和Drop trait
上面的代码我们验证了Rc实现了Drop trait,同样的Rc也实现了Deref trait
#[allow(unused)]
fn work_10() {
let r = Rc::new(10);
println!("{}",b);
}
对于其他的智能指针,也都同样的实现了这两个trait,下面不再说明了。
内部可变性模式
如果说Rust中的很多知识都能和C++类比的话,那么这个内部可变性模式很难找到很好的类比。因为借用规则是Rust独有的,这个模式有何借用规则有关。
声明:此部分作者节后官方文档来说明
你可以查看官方文档
RefCell<T> 与内部可变性模式 - Rust 程序设计语言 中文版 (rustwiki.org)
作者在这里基于官方文档来进行这里的讲述
我们来看一下官方对于这个模式是如何描述的
内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用
unsafe
代码来模糊 Rust 通常的可变性和借用规则。
提取出主干就是能在unsafe中让不可变的引用也可以改变数据,类似C++中的mutable关键字?不过二者还是有区别的。
替身测试
测试替身(test double)是一个通用编程概念,它代表一个在测试中替代某个类型的类型。mock 对象 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。
虽然 Rust 中的对象与其他语言中的对象并不是一回事,Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。
如下是一个我们想要测试的场景:我们在编写一个记录某个值与最大值的差距的库,并根据当前值与最大值的差距来发送消息。例如,这个库可以用于记录用户所允许的 API 调用数量限额。
该库只提供记录与最大值的差距,以及何种情况发送什么消息的功能。使用此库的程序则期望提供实际发送消息的机制:程序可以选择记录一条消息、发送 email、发送短信等等。库本身无需知道这些细节;只需实现其提供的
Messenger
trait 即可。
我将官方文档中的代码加上注释,然后呈现在这里,可以看得更清晰一点。
//一个用来记录信息的trait
pub trait Messenger {
fn send(&self,msg: &str);
}
//检查磁盘容量的一个结构体,T是任何实现了Messenger的类型,用于send消息
#[allow(unused)]
pub struct LimitTracker<'a, T:Messenger> {
messenger: &'a T, //这是一个T类型的引用,就像一个指针一样,策略模式?
value: usize, //当前值
max: usize, //最大值
}
impl<'a,T> LimitTracker<'a,T>
where T: Messenger
{
//new 方法,自己看一下即可
pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
//这个方法能够设置val,并且在超过对应限度的时候使用messenger的send方法,发送一条信息。
//由于这个方法是默认返回类型(),如果想对这个功能进行测试,需要一些手法,下面再说
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quata!");
} else if percentage_of_max >= 0.9 {
self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger.send("Warning: You've used up over 75% of your quota!");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
//简单的一个结构体,仅仅对Vec进行了一次包装
struct MockMessenger {
sent_messenger: Vec<String>,
}
//关联函数new
impl MockMessenger {
fn new() -> Self {
MockMessenger { sent_messenger: vec![] }
}
}
//实现Messenger trait
impl Messenger for MockMessenger {
fn send(&self,msg: &str) {
//当接受到了一个消息,将其放在sent_messenger(type: Vec<String>)中
self.sent_messenger.push(String::from(msg)); //这里会出现问题
}
}
#[test]
//用于断言测试超过%75,是否发送一个消息
fn it_sends_an_over_75_percent_waring_message() {
let mock_messenger = MockMessenger::new();
//这里将messanger设置为上面的MockMessenger的一个变量,这样在每次send消息
//Vec中就会push一次,我们就可以使用len方法来断言是否成功发送消息
//这种手法就是替身测试
let mut limit_tracker = LimitTracker::new(&mock_messenger,100);
limit_tracker.set_value(100);
assert_eq!(mock_messenger.sent_messenger.len(),1);
}
}
上述的代码通过不了编译
jan@7X:~/Code/Rust/smart_pointer$ cargo test
Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
error[E0596]: cannot borrow `self.sent_messenger` as mutable, as it is behind a `&` reference
--> src/lib.rs:63:13
|
3 | fn send(&self,msg: &str);
| ----- help: consider changing that to be a mutable reference: `&mut self`
...
63 | self.sent_messenger.push(String::from(msg)); //这里会出现问题
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
For more information about this error, try `rustc --explain E0596`.
error: could not compile `smart_pointer` due to previous error
正如官方所言
不能修改
MockMessenger
来记录消息,因为send
方法获取了self
的不可变引用。我们也不能参考错误文本的建议使用&mut self
替代,因为这样send
的签名就不符合Messenger
trait 定义中的签名了(可以试着这么改,看看会出现什么错误信息)。这正是内部可变性的用武之地!我们将通过
RefCell
来储存sent_messages
,然后send
将能够修改sent_messages
并储存消息。
在介绍解决办法之前,先放松一下,看看RefCell智能指针
RefCell<T>
不同于 Rc<T>
,RefCell<T>
代表其数据的唯一的所有权。这点和Box<T>很像,但是RefCell和Box是有很大的区别的。RefCell<T>
是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。
Box对于借用规则是在编译时期检查的,如果不符合借用规则,会发生编译器的错误。
RefCell对于借用规则是在运行时期检查的,需要使用unsafe代码块,如果不符合借用规则,程序会panic。
回忆一下借用规则
- 在任意给定时刻,只能拥有一个可变引用或任意数量的不可变引用 之一(而不是两者)。
- 引用必须总是有效的。
RefCell的基本使用
使用RefCell和其他的智能指针有些不同。
首先RefCell没有实现Dispaly,没有实现Deref,但是其创建方法和其他的智能指针一样,并且同样实现了Drop。虽然说其没有实现Deref trait,但是提供了另两个方法来代替。
#[allow(unused)]
fn work_11() {
let r = RefCell::new(Foo::new(10));
println!("{}",r.borrow());
println!("{}",r.borrow_mut());
}
运行
10
10
drop!
RefCell失败的情况下
违反了借用规则的情况下程序会panic
#[allow(unused)]
fn work_12() {
let r = RefCell::new(Foo::new(10));
let b1 = r.borrow_mut();
let b2 = r.borrow_mut();
}
b1和b2是可变借用,会违反借用规则,程序panic,至于work_11中为什么程序没有painc,是因为不可变借用在用于执行完println!随之销毁,因为其是一个无名的借用,生命周期就是其所在的那行代码。
jan@7X:~/Code/Rust/smart_pointer$ cargo run
Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
Finished dev [unoptimized + debuginfo] target(s) in 0.19s
Running `target/debug/smart_pointer`
thread 'main' panicked at 'already borrowed: BorrowMutError', src/main.rs:223:16
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
drop!
RefCell是如何检查借用规则的
其有两个计数器,如果得到其一个不可变借用,相应的计数器加一,对于可变借用,也是同样。如果RefCell发现其计数器不符合借用规则,就会panic。
关于更多的RefCell用法,请查看官方文档说明
回看例子
这正是内部可变性的用武之地!我们将通过
RefCell
来储存sent_messages
,然后send
将能够修改sent_messages
并储存消息。
于是这样修改。
#[cfg(test)]
mod tests {
use super::*;
use std::{cell::RefCell, borrow::Borrow};
//简单的一个结构体,仅仅对Vec进行了一次包装
struct MockMessenger {
sent_messenger: RefCell<Vec<String>>,
}
//关联函数new
impl MockMessenger {
fn new() -> Self {
MockMessenger { sent_messenger: RefCell::new(vec![]) }
}
}
//实现Messenger trait
impl Messenger for MockMessenger {
fn send(&self,msg: &str) {
//当接受到了一个消息,将其放在sent_messenger(type: Vec<String>)中
self.sent_messenger.borrow_mut().push(String::from(msg));
}
}
#[test]
//用于断言测试超过%75,是否发送一个消息
fn it_sends_an_over_75_percent_waring_message() {
let mock_messenger = MockMessenger::new();
//这里将messanger设置为上面的MockMessenger的一个变量,这样在每次send消息
//Vec中就会push一次,我们就可以使用len方法来断言是否成功发送消息
let mut limit_tracker = LimitTracker::new(&mock_messenger,100);
limit_tracker.set_value(100);
assert_eq!(mock_messenger.sent_messenger.borrow().len(),1);
}
}
运行这个测试没有发生断言,成功。
结合Rc和RefCell来拥有多个可变数据所有者
Rc允许对相同的数据有多个所有者,RefCell允许对不可变引用改变,结合二者,我们可以时间有多个所有者并且同时课改变其值的能力。
正如官方文档提供的代码。
#[allow(unused)]
use List::{Cons,Nil};
#[allow(unused)]
fn work_13() {
let value = Rc::new(RefCell::new(5));
//5 -> Nil
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
//6 -> 5 -> Nil
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
//10 -> 5 -> Nil
let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));
//获取value的可变借用,将其加10,注意:现在有多个Rc指向value
*value.borrow_mut() += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}
不可能通过代码分析来检测隐藏的错误
我们先抛出一个问题,是否有这样的一个程序,能够验证一个程序是否能够在有限的时间运行完?答案是不可以可。这就是大名鼎鼎的停机问题 ,如果读者对停机问题感兴趣,可以去翻看离散数学经典著作黑皮书,作者已经很好的为我们解释了。
最先解开这个问题的是图灵,就是我们的计算机鼻祖。这些都是题外话了。
主要想说的是,我们不可能通过对代码分析来计算程序的运行时(仅仅是字面意思,并非语言运行所需要的环境)的一些情况。如果一个代码拿来给人看,那人一定能看出来这个程序是否能够在有限的事件运行,但是机器不行,机器无法像人一样思考。所以,即使是Rust,也不可能在编译期间检查出所有的错误。Rust的检查器是保守的,着就意味着,Rust检查器不能证明你的程序完全正确,就不会给你编译通过,即便你的程序是正确的。这时候,我们就需要告诉编译器:虽然你不能看出来我的这个程序是完全正确的,但是我自己可以保证这个程序不会出问题(当然,你不可能有十分把握),然后绕过编译器的检,着正是智能指针和unsafe出现的一部分意义。
总结
通过智能指看见了Rust是如何进行指针的操作。Rust更倾向用户使用有安全保障的智能指针,而不是使用原生的裸指针。通过智能指针我们也能看见Rust语言层面的设计哲学。
说来也有意思,如果C++给人的形象是一个放荡不羁的少年,那么Rust就是朴素忠厚的农民,如果你要使用C++的自由,就必须要承受自由的代价,通过C++现代的变化我们可以看见,C++对内存这一块推出的智能指针,对自由度做出了一些限制。你要享受Rust带来安全性,就必须要承受安全的代价。但Rust却有unsafe代码和智能指针绕过安全规则。这些值得引发我们一些思考。
如果你发现了本文章的错误,请练习作者更正
本文章并没有进行智能指针的所有说明,如果读者对这一部分感兴趣,可以查看Rust官方文档和Rust标准库