diff --git a/docs/print.html b/docs/print.html
index 3c98186..b3d634b 100644
--- a/docs/print.html
+++ b/docs/print.html
@@ -2,7 +2,7 @@
@@ -8156,6 +8156,142 @@ commit 4f2dc564851dc04b271a2260c834643dfd86c724
Cargo 被设计为可扩展的,通过新的子命令而无须修改 Cargo 自身。如果$PATH
中有类似cargo-something
的二进制文件,就可以通过cargo something
来像 Cargo 子命令一样运行它。像这样的自定义命令也可以运行cargo --list
来展示出来,通过cargo install
向 Cargo 安装扩展并可以如内建 Cargo 工具那样运行他们是很方便的!
通过 Cargo 和 crates.io 来分享代码是使得 Rust 生态环境可以用于许多不同的任务的重要组成部分。Rust 的标准库是小而稳定的,不过 crate 易于分享和使用,并采用一个不同语言自身的时间线来提供改进。不要羞于在 crates.io 上共享对你有用的代码;因为它很有可能对别人也很有用!
+
+
+ch15-00-smart-pointers.md
+
+commit 4f2dc564851dc04b271a2260c834643dfd86c724
+
+
指针是一个常见的编程概念,它代表一个指向储存其他数据的位置。第四章学习了 Rust 的引用;他们是一类很平常的指针,以&
符号为标志并借用了他们所指向的值。智能指针(Smart pointers)是一类数据结构,他们的表现类似指针,但是也拥有额外的元数据和能力,比如说引用计数。智能指针模式起源于 C++。在 Rust 中,普通引用和智能指针的一个额外的区别是引用是一类只借用数据的指针;相反大部分情况,智能指针拥有他们指向的数据。
+
本书中已经出现过一些智能指针,虽然当时我们并不这么称呼他们。例如在某种意义上说,第八章的String
和Vec<T>
都是智能指针。他们拥有一些数据并允许你修改他们,并带有元数据(比如他们的容量)和额外的功能或保证(String
的数据总是有效的 UTF-8 编码)。智能指针区别于常规结构体的特性在于他们实现了Deref
和Drop
trait,而本章会讨论这些 trait 以及为什么对于智能指针来说他们很重要。
+
考虑到智能指针是一个在 Rust 经常被使用的通用设计模式,本章并不会覆盖所有现存的智能指针。很多库都有自己的智能指针而你也可以编写属于你自己的。这里将会讲到的是来自标准库中最常用的一些:
+
+Box<T>
,用于在堆上分配值
+Rc<T>
,一个引用计数类型,其数据可以有多个所有者
+RefCell<T>
,其本身并不是只能指针,不过它管理智能指针Ref
和RefMut
的访问,在运行时而不是在编译时执行借用规则。
+
+
同时我们还将涉及:
+
+- 内部可变性(interior mutability)模式,当一个不可变类型暴露出改变其内部值的 API,这时借用规则适用于运行时而不是编译时。
+- 引用循环,它如何会泄露内存,以及如何避免他们
+
+
让我们开始吧!
+
+
+ch15-01-box.md
+
+commit 85b2c9ac704c9dc4bbedb97209d336afb9809dc1
+
+
最简单直接的智能指针是 box,它的类型是Box<T>
。 box 允许你将一个单独的值放在堆上(第四章介绍或栈与堆)。列表 15-1 展示了如何使用 box 在堆上储存一个i32
:
+
Filename: src/main.rs
+
fn main() {
+ let b = Box::new(5);
+ println!("b = {}", b);
+}
+
+
Listing 15-1: Storing an i32
value on the heap using a
+box
+
这会打印出b = 5
。在这个例子中,我们可以像数据是储存在栈上的那样访问 box 中的数据。正如任何拥有数据所有权的值那样,当像b
这样的 box 在main
的末尾离开作用域时,它将被释放。这个释放过程作用于 box 本身(位于栈上)和它所指向的数据(位于堆上)。
+
将一个单独的值存放在堆上并不是很有意义,所以像列表 15-1 这样单独使用 box 并不常见。一个 box 的实用场景是当你希望确保类型有一个已知大小的时候。例如,考虑一下列表 15-2,它是一个用于 cons list 的枚举定义,这是一个来源于函数式编程的数据结构类型。注意它还不能编译:
+
Filename: src/main.rs
+
enum List {
+ Cons(i32, List),
+ Nil,
+}
+
+
Listing 15-2: The first attempt of defining an enum to
+represent a cons list data structure of i32
values
+
我们实现了一个只存放i32
值的 cons list。也可以选择实用第十章介绍的泛型来实现一个类型无关的 cons list。
+
+
+cons list 是一个来源于 Lisp 编程语言及其方言的数据结构。在 Lisp 中,cons
函数("construct function"的缩写)利用两个参数来构造一个新的列表,他们通常是一个单独的值和另一个列表。
+cons 函数的概念涉及到更通用的函数式编程术语;“将 x 与 y 连接”通常意味着构建一个新的容器而将 x 的元素放在新容器的开头,其后则是容器 y 的元素。
+cons list 通过递归调用cons
函数产生。代表递归的 base case 的规范名称是Nil
,它宣布列表的终止。注意这不同于第六章中的"null"或"nil"的概念,他们代表无效或缺失的值。
+
+
cons list 是一个每个元素和之后的其余部分都只包含一个值的列表。列表的其余部分由嵌套的 cons list 定义。其结尾由值Nil
表示。cons list 在 Rust 中并不常见;通常Vec<T>
是一个更好的选择。实现这个数据结构是Box<T>
实用性的一个好的例子。让我们看看为什么!
+
使用 cons list 来储存列表1, 2, 3
将看起来像这样:
+
use List::{Cons, Nil};
+
+fn main() {
+ let list = Cons(1, Cons(2, Cons(3, Nil)));
+}
+
+
第一个Cons
储存了1
和另一个List
值。这个List
是另一个包含2
的Cons
值和下一个List
值。这又是另一个存放了3
的Cons
值和最后一个值为Nil
的List
,非递归成员代表了列表的结尾。
+
如果尝试编译上面的代码,会得到如列表 15-3 所示的错误:
+
error[E0072]: recursive type `List` has infinite size
+ -->
+ |
+1 | enum List {
+ | _^ starting here...
+2 | | Cons(i32, List),
+3 | | Nil,
+4 | | }
+ | |_^ ...ending here: recursive type has infinite size
+ |
+ = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to
+ make `List` representable
+
+
Listing 15-3: The error we get when attempting to define
+a recursive enum
+
错误表明这个类型“有无限的大小”。为什么呢?因为List
的一个成员被定义为递归的:它存放了另一个相同类型的值。这意味着 Rust 无法计算为了存放List
值到底需要多少空间。让我们一点一点的看:首先了解一下 Rust 如何决定需要多少空间来存放一个非递归类型。回忆一下第六章讨论枚举定义时的列表 6-2 中定义的Message
枚举:
+
enum Message {
+ Quit,
+ Move { x: i32, y: i32 },
+ Write(String),
+ ChangeColor(i32, i32, i32),
+}
+
+
当 Rust 需要知道需要为Message
值分配多少空间时,它可以检查每一个成员并发现Message::Quit
并不需要任何空间,Message::Move
需要足够储存两个i32
值的空间,依此类推。因此,Message
值所需的最大空间等于储存其最大成员的空间大小。
+
与此相对当 Rust 编译器检查像列表 15-2 中的List
这样的递归类型时会发生什么呢。编译器尝试计算出储存一个List
枚举需要多少内存,并开始检查Cons
成员,那么Cons
需要的空间等于i32
的大小加上List
的大小。为了计算List
需要多少内存,它检查其成员,从Cons
成员开始。Cons
成员储存了一个i32
值和一个List
值,这样的计算将无限进行下去,如图 15-4 所示:
+
+
Figure 15-4: An infinite List
consisting of infinite
+Cons
variants
+
Rust 无法计算出要为定义为递归的类型分配多少空间,所以编译器给出了列表 15-3 中的错误。这个错误也包括了有用的建议:
+
= help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to
+ make `List` representable
+
+
因为Box<T>
是一个指针,我们总是知道它需要多少空间:指针需要一个usize
大小的空间。这个usize
的值将是堆数据的地址。而堆数据可以是任意大小,不过开始这个堆数据的地址总是能放进一个usize
中。所以如果将列表 15-2 的定义修改为像这里列表 15-5 中的定义,并修改main
函数为Cons
成员中的值使用Box::new
:
+
Filename: src/main.rs
+
enum List {
+ Cons(i32, Box<List>),
+ Nil,
+}
+
+use List::{Cons, Nil};
+
+fn main() {
+ let list = Cons(1,
+ Box::new(Cons(2,
+ Box::new(Cons(3,
+ Box::new(Nil))))));
+}
+
+
Listing 15-5: Definition of List
that uses Box<T>
in
+order to have a known size
+
这样编译器就能够计算出储存一个List
值需要的大小了。Rust 将会检查List
,同样的从Cons
成员开始检查。Cons
成员需要i32
的大小加上一个usize
的大小,因为 box 总是usize
大小的,不管它指向的是什么。接着 Rust 检查Nil
成员,它并储存一个值,所以Nil
并不需要任何空间。我们通过 box 打破了这无限递归的连锁。图 15-6 展示了现在Cons
成员看起来像什么:
+
+
Figure 15-6: A List
that is not infinitely sized since
+Cons
holds a Box
+
这就是 box 主要应用场景:打破无限循环的数据结构以便编译器可以知道其大小。第十七章讨论 trait 对象时我们将了解另一个 Rust 中会出现未知大小数据的情况。
+
虽然我们并不经常使用 box,他们也是一个了解智能指针模式的好的方式。Box<T>
作为智能指针经常被使用的两个方面是他们Deref
和Drop
trait 的实现。让我们研究这些 trait 如何工作以及智能指针如何利用他们。
+
+
+ch15-02-deref.md
+
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+
+
第一个智能指针相关的重要 trait 是Deref
,它允许我们重载*
,解引用运算符(不同于乘法运算符和全局引用运算符)。重载智能指针的*
方便访问其后的数据,在这个部分的稍后介绍解引用强制多态时我们会讨论方便的意义。
+
第八章的哈希 map 的“根据旧值更新一个值”部分简要的提到了解引用运算符。当时有一个可变引用,而我们希望改变这个引用所指向的值。为此,首先我们必须解引用。这是另一个使用i32
值引用的例子:
+
let mut x = 5;
+{
+ let y = &mut x;
+
+ *y += 1
+}
+
+assert_eq!(6, x);
+
+
我们使用*y
来访问可变引用y
所指向的数据,
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 96f1381..620a10e 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -76,4 +76,12 @@
- [将 crate 发布到 Crates.io](ch14-02-publishing-to-crates-io.md)
- [Cargo 工作空间](ch14-03-cargo-workspaces.md)
- [使用`cargo install`从 Crates.io 安装文件](ch14-04-installing-binaries.md)
- - [Cargo 自定义扩展命令](ch14-05-extending-cargo.md)
\ No newline at end of file
+ - [Cargo 自定义扩展命令](ch14-05-extending-cargo.md)
+
+- [智能指针](ch15-00-smart-pointers.md)
+ - [`Box