diff --git a/docs/ch04-01-what-is-ownership.html b/docs/ch04-01-what-is-ownership.html
index 04f732f..1106852 100644
--- a/docs/ch04-01-what-is-ownership.html
+++ b/docs/ch04-01-what-is-ownership.html
@@ -245,7 +245,7 @@ let y = x;
println!("x = {}, y = {}", x, y);
-
他们似乎与我们刚刚学到的内容向抵触:没有调用clone
,不过x
依然有效且没有被移动到y
中。
+他们似乎与我们刚刚学到的内容相抵触:没有调用clone
,不过x
依然有效且没有被移动到y
中。
原因是像整型这样的在编译时已知大小的类型被整个储存在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量y
后使x
无效。换句话说,这里没有深浅拷贝的区别,所以调用clone
并不会与通常的浅拷贝有什么不同,我们可以不用管它。
Rust 有一个叫做Copy
trait 的特殊注解,可以用在类似整型这样的储存在栈上的类型(第十章详细讲解 trait)。如果一个类型拥有Copy
trait,一个旧的变量在(重新)赋值后仍然可用。Rust 不允许自身或其任何部分实现了Drop
trait 的类型使用Copy
trait。如果我们对其值离开作用域时需要特殊处理的类型使用Copy
注解,将会出现一个编译时错误。
那么什么类型是Copy
的呢?可以查看给定类型的文档来确认,不过作为一个通用的规则,任何简单标量值的组合可以是Copy
的,任何不需要分配内存或类似形式资源的类型是Copy
的,如下是一些Copy
的类型:
diff --git a/docs/ch04-02-references-and-borrowing.html b/docs/ch04-02-references-and-borrowing.html
index 6529d44..c3a1e7e 100644
--- a/docs/ch04-02-references-and-borrowing.html
+++ b/docs/ch04-02-references-and-borrowing.html
@@ -262,7 +262,7 @@ for it to be borrowed from.
简要的概括一下对引用的讨论:
-- 特定时间,只能拥有如下中的一个:
+- 在任意给定时间,只能拥有如下中的一个:
- 一个可变引用。
diff --git a/docs/ch15-03-drop.html b/docs/ch15-03-drop.html
index aecad1f..0a4f9f5 100644
--- a/docs/ch15-03-drop.html
+++ b/docs/ch15-03-drop.html
@@ -103,6 +103,33 @@ the CustomSmartPointer
.
Wait for it...
Dropping CustomSmartPointer!
+被打印到屏幕上,它展示了 Rust 在实例离开作用域时自动调用了drop
。
+可以使用std::mem::drop
函数来在值离开作用域之前丢弃它。这通常是不必要的;整个Drop
trait 的要点在于它自动的帮我们处理清理工作。在第十六章讲到并发时我们会看到一个需要在离开作用域之前丢弃值的例子。现在知道这是可能的即可,std::mem::drop
位于 prelude 中所以可以如列表 15-9 所示直接调用drop
:
+Filename: src/main.rs
+
fn main() {
+ let c = CustomSmartPointer { data: String::from("some data") };
+ println!("CustomSmartPointer created.");
+ drop(c);
+ println!("Wait for it...");
+}
+
+Listing 15-9: Calling std::mem::drop
to explicitly drop
+a value before it goes out of scope
+运行这段代码会打印出如下内容,因为Dropping CustomSmartPointer!
在CustomSmartPointer created.
和Wait for it...
之间被打印出来,表明析构代码被执行了:
+CustomSmartPointer created.
+Dropping CustomSmartPointer!
+Wait for it...
+
+注意不允许直接调用我们定义的drop
方法:如果将列表 15-9 中的drop(c)
替换为c.drop()
,会得到一个编译错误表明explicit destructor calls not allowed
。不允许直接调用Drop::drop
的原因是 Rust 在值离开作用域时会自动插入Drop::drop
,这样就会丢弃值两次。丢弃一个值两次可能会造成错误或破坏内存,所以 Rust 就不允许这么做。相应的可以调用std::mem::drop
,它的定义是:
+pub mod std {
+ pub mod mem {
+ pub fn drop<T>(x: T) { }
+ }
+}
+
+这个函数对于T
是泛型的,所以可以传递任何值。这个函数的函数体并没有任何实际内容,所以它也不会利用其参数。这个空函数的作用在于drop
获取其参数的所有权,它意味着在这个函数结尾x
离开作用域时x
会被丢弃。
+使用Drop
trait 实现指定的代码在很多方面都使得清理值变得方便和安全:比如可以使用它来创建我们自己的内存分配器!通过Drop
trait 和 Rust 所有权系统,就无需担心之后清理代码,因为 Rust 会自动考虑这些问题。如果代码在值仍被使用时就清理它会出现编译错误,因为所有权系统确保了引用总是有效的,这也就保证了drop
只会在值不再被使用时被调用一次。
+现在我们学习了Box<T>
和一些智能指针的特性,让我们聊聊一些其他标准库中定义的拥有各种实用功能的智能指针。
diff --git a/docs/ch15-04-rc.html b/docs/ch15-04-rc.html
index 7ae633c..187f2ae 100644
--- a/docs/ch15-04-rc.html
+++ b/docs/ch15-04-rc.html
@@ -67,7 +67,106 @@
-
+
+
+ch15-04-rc.md
+
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+
+
大部分情况下所有权是非常明确的:可以准确的知道哪个变量拥有某个值。然而并不总是如此;有时确实可能需要多个所有者。为此,Rust 有一个叫做Rc<T>
的类型。它的名字是引用计数(reference counting)的缩写。引用计数意味着它记录一个值引用的数量来知晓这个值是否仍在被使用。如果这个值有零个引用,就知道可以在没有有效引用的前提下清理这个值。
+
根据现实生活场景来想象的话,它就像一个客厅的电视。当一个人进来看电视时,他打开电视。其他人也会进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候关掉了电视,正在看电视人肯定会抓狂的!
+
Rc<T>
用于当我们希望在堆上分配一些内存供程序的多个部分读取,而且无法在编译时确定程序的那一部分会最后结束使用它。如果我们知道的话那么常规的所有权规则会在编译时强制起作用。
+
注意Rc<T>
只能用于单线程场景;下一章并发会涉及到如何在多线程程序中进行引用计数。如果尝试在多线程中使用Rc<T>
则会得到一个编译错误。
+
+
让我们回到列表 15-5 中的 cons list 例子。在列表 15-11 中尝试使用Box<T>
定义的List
。首先创建了一个包含 5 接着是 10 的列表实例。之后我们想要创建另外两个列表:一个以 3 开始并后接第一个包含 5 和 10 的列表,另一个以 4 开始其后也是第一个列表。换句话说,我们希望这两个列表共享第三个列表的所有权,概念上类似于图 15-10:
+
+
Figure 15-10: Two lists, b
and c
, sharing ownership
+of a third list, a
+
尝试使用Box<T>
定义的List
并不能工作,如列表 15-11 所示:
+
Filename: src/main.rs
+
enum List {
+ Cons(i32, Box<List>),
+ Nil,
+}
+
+use List::{Cons, Nil};
+
+fn main() {
+ let a = Cons(5,
+ Box::new(Cons(10,
+ Box::new(Nil))));
+ let b = Cons(3, Box::new(a));
+ let c = Cons(4, Box::new(a));
+}
+
+
Listing 15-11: Having two lists using Box<T>
that try
+to share ownership of a third list won't work
+
编译会得出如下错误:
+
error[E0382]: use of moved value: `a`
+ --> src/main.rs:13:30
+ |
+12 | let b = Cons(3, Box::new(a));
+ | - value moved here
+13 | let c = Cons(4, Box::new(a));
+ | ^ value used here after move
+ |
+ = note: move occurs because `a` has type `List`, which does not
+ implement the `Copy` trait
+
+
Cons
成员拥有其储存的数据,所以当创建b
列表时将a
的所有权移动到了b
。接着当再次尝使用a
创建c
时,这不被允许因为a
的所有权已经被移动。
+
相反可以改变Cons
的定义来存放一个引用,不过接着必须指定生命周期参数,而且也必须以使得列表的每一个元素都与列表本身存在的一样久那样构造列表的元素。否则借用检查器甚至都不会允许我们编译代码。
+
如列表 15-12 所示,可以将List
的定义从Box<T>
改为Rc<T>
:
+
Filename: src/main.rs
+
enum List {
+ Cons(i32, Rc<List>),
+ Nil,
+}
+
+use List::{Cons, Nil};
+use std::rc::Rc;
+
+fn main() {
+ let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
+ let b = Cons(3, a.clone());
+ let c = Cons(4, a.clone());
+}
+
+
Listing 15-12: A definition of List
that uses
+Rc<T>
+
注意必须为Rc
增加use
语句因为它不在 prelude 中。在main
中创建了存放 5 和 10 的列表并将其存放在一个叫做a
的新的Rc
中。接着当创建b
和c
时,我们对a
调用了clone
方法。
+
+
之前我们见过clone
方法,当时使用它来创建某些数据的完整拷贝。但是对于Rc<T>
来说,它并不创建一个完整的拷贝。Rc<T>
存放了引用计数,也就是说,一个存在多少个克隆的数量。让我们像列表 15-13 那样在创建c
时增加一个内部作用域,并在不同的位置打印出关联函数Rc::strong_count
的结果。Rc::strong_count
返回传递给它的Rc
值的引用计数,而在本章的稍后部分介绍避免引用循环时讲到它为什么叫做strong_count
。
+
Filename: src/main.rs
+
# enum List {
+# Cons(i32, Rc<List>),
+# Nil,
+# }
+#
+# use List::{Cons, Nil};
+# use std::rc::Rc;
+#
+fn main() {
+ let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
+ println!("rc = {}", Rc::strong_count(&a));
+ let b = Cons(3, a.clone());
+ println!("rc after creating b = {}", Rc::strong_count(&a));
+ {
+ let c = Cons(4, a.clone());
+ println!("rc after creating c = {}", Rc::strong_count(&a));
+ }
+ println!("rc after c goes out of scope = {}", Rc::strong_count(&a));
+}
+
+
Listing 15-13: Printing out the reference count
+
这会打印出:
+
rc = 1
+rc after creating b = 2
+rc after creating c = 3
+rc after c goes out of scope = 2
+
+
不难看出a
的初始引用计数是一。接着每次调用clone
,计数会加一。当c
离开作用域时,计数减一,这发生在Rc<T>
的Drop
trait 实现中。这个例子中不能看到的是当b
接着是a
在main
函数的结尾离开作用域时,包含 5 和 10 的列表的引用计数会是 0,这时列表将被丢弃。这个策略允许拥有多个所有者,而引用计数会确保任何所有者存在时这个值保持有效。
+
在本部分的开始,我们说Rc<T>
只允许程序的多个部分读取Rc<T>
中T
的不可变引用。如果Rc<T>
允许一个可变引用,我们将遇到第四章讨论的借用规则所不允许的问题:两个指向同一位置的可变借用会导致数据竞争和不一致。不过可变数据是非常有用的!在下一部分,我们将讨论内部可变性模式和RefCell<T>
类型,它可以与Rc<T>
结合使用来处理不可变性的限制。
+
diff --git a/docs/ch15-05-interior-mutability.html b/docs/ch15-05-interior-mutability.html
index baff764..226197c 100644
--- a/docs/ch15-05-interior-mutability.html
+++ b/docs/ch15-05-interior-mutability.html
@@ -67,7 +67,32 @@
-
+
+
+ch15-05-interior-mutability.md
+
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+
+
内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用unsafe
代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的unsafe
代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
+
让我们通过遵循内部可变性模式的RefCell<T>
类型来开始探索。
+
+
不同于Rc<T>
,RefCell<T>
代表其数据的唯一的所有权。那么是什么让RefCell<T>
不同于像Box<T>
这样的类型呢?回忆一下第四章所学的借用规则:
+
+- 在任意给定时间,只能拥有如下中的一个:
+
+
+
+- 引用必须总是有效的。
+
+
对于引用和Box<T>
,借用规则的不可变性作用于编译时。对于RefCell<T>
,这些不可变性作用于运行时。对于引用,如果违反这些规则,会得到一个编译错误。而对于RefCell<T>
,违反这些规则会panic!
。
+
Rust 编译器执行的静态分析时天生保守的。代码的一些属性则不可能通过分析代码发现:其中最著名的就是停机问题(停机问题),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。
+
因为一些分析是不可能的,Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不变,但不会带来灾难。RefCell<T>
正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。
+
类似于Rc<T>
,RefCell<T>
只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用RefCell<T>
的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用RefCell<T>
,会得到一个编译错误。
+
对于引用,可以使用&
和&mut
语法来分别创建不可变和可变的引用。不过对于RefCell<T>
,我们使用borrow
和borrow_mut
方法,它是RefCell<T>
拥有的安全 API 的一部分。borrow
返回Ref
类型的智能指针,而borrow_mut
返回RefMut
类型的智能指针。这两个类型实现了Deref
所以可以被当作常规引用处理。Ref
和RefMut
动态的借用所有权,而他们的Drop
实现也动态的释放借用。
+
diff --git a/docs/print.html b/docs/print.html
index 6c9fe9f..fe26d18 100644
--- a/docs/print.html
+++ b/docs/print.html
@@ -1783,7 +1783,7 @@ let y = x;
println!("x = {}, y = {}", x, y);
-他们似乎与我们刚刚学到的内容向抵触:没有调用clone
,不过x
依然有效且没有被移动到y
中。
+他们似乎与我们刚刚学到的内容相抵触:没有调用clone
,不过x
依然有效且没有被移动到y
中。
原因是像整型这样的在编译时已知大小的类型被整个储存在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量y
后使x
无效。换句话说,这里没有深浅拷贝的区别,所以调用clone
并不会与通常的浅拷贝有什么不同,我们可以不用管它。
Rust 有一个叫做Copy
trait 的特殊注解,可以用在类似整型这样的储存在栈上的类型(第十章详细讲解 trait)。如果一个类型拥有Copy
trait,一个旧的变量在(重新)赋值后仍然可用。Rust 不允许自身或其任何部分实现了Drop
trait 的类型使用Copy
trait。如果我们对其值离开作用域时需要特殊处理的类型使用Copy
注解,将会出现一个编译时错误。
那么什么类型是Copy
的呢?可以查看给定类型的文档来确认,不过作为一个通用的规则,任何简单标量值的组合可以是Copy
的,任何不需要分配内存或类似形式资源的类型是Copy
的,如下是一些Copy
的类型:
@@ -2072,7 +2072,7 @@ for it to be borrowed from.
简要的概括一下对引用的讨论:
-- 特定时间,只能拥有如下中的一个:
+- 在任意给定时间,只能拥有如下中的一个:
- 一个可变引用。
@@ -8391,6 +8391,157 @@ the CustomSmartPointer
.
Wait for it...
Dropping CustomSmartPointer!
+被打印到屏幕上,它展示了 Rust 在实例离开作用域时自动调用了drop
。
+可以使用std::mem::drop
函数来在值离开作用域之前丢弃它。这通常是不必要的;整个Drop
trait 的要点在于它自动的帮我们处理清理工作。在第十六章讲到并发时我们会看到一个需要在离开作用域之前丢弃值的例子。现在知道这是可能的即可,std::mem::drop
位于 prelude 中所以可以如列表 15-9 所示直接调用drop
:
+Filename: src/main.rs
+
fn main() {
+ let c = CustomSmartPointer { data: String::from("some data") };
+ println!("CustomSmartPointer created.");
+ drop(c);
+ println!("Wait for it...");
+}
+
+Listing 15-9: Calling std::mem::drop
to explicitly drop
+a value before it goes out of scope
+运行这段代码会打印出如下内容,因为Dropping CustomSmartPointer!
在CustomSmartPointer created.
和Wait for it...
之间被打印出来,表明析构代码被执行了:
+CustomSmartPointer created.
+Dropping CustomSmartPointer!
+Wait for it...
+
+注意不允许直接调用我们定义的drop
方法:如果将列表 15-9 中的drop(c)
替换为c.drop()
,会得到一个编译错误表明explicit destructor calls not allowed
。不允许直接调用Drop::drop
的原因是 Rust 在值离开作用域时会自动插入Drop::drop
,这样就会丢弃值两次。丢弃一个值两次可能会造成错误或破坏内存,所以 Rust 就不允许这么做。相应的可以调用std::mem::drop
,它的定义是:
+pub mod std {
+ pub mod mem {
+ pub fn drop<T>(x: T) { }
+ }
+}
+
+这个函数对于T
是泛型的,所以可以传递任何值。这个函数的函数体并没有任何实际内容,所以它也不会利用其参数。这个空函数的作用在于drop
获取其参数的所有权,它意味着在这个函数结尾x
离开作用域时x
会被丢弃。
+使用Drop
trait 实现指定的代码在很多方面都使得清理值变得方便和安全:比如可以使用它来创建我们自己的内存分配器!通过Drop
trait 和 Rust 所有权系统,就无需担心之后清理代码,因为 Rust 会自动考虑这些问题。如果代码在值仍被使用时就清理它会出现编译错误,因为所有权系统确保了引用总是有效的,这也就保证了drop
只会在值不再被使用时被调用一次。
+现在我们学习了Box<T>
和一些智能指针的特性,让我们聊聊一些其他标准库中定义的拥有各种实用功能的智能指针。
+
+
+ch15-04-rc.md
+
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+
+大部分情况下所有权是非常明确的:可以准确的知道哪个变量拥有某个值。然而并不总是如此;有时确实可能需要多个所有者。为此,Rust 有一个叫做Rc<T>
的类型。它的名字是引用计数(reference counting)的缩写。引用计数意味着它记录一个值引用的数量来知晓这个值是否仍在被使用。如果这个值有零个引用,就知道可以在没有有效引用的前提下清理这个值。
+根据现实生活场景来想象的话,它就像一个客厅的电视。当一个人进来看电视时,他打开电视。其他人也会进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候关掉了电视,正在看电视人肯定会抓狂的!
+Rc<T>
用于当我们希望在堆上分配一些内存供程序的多个部分读取,而且无法在编译时确定程序的那一部分会最后结束使用它。如果我们知道的话那么常规的所有权规则会在编译时强制起作用。
+注意Rc<T>
只能用于单线程场景;下一章并发会涉及到如何在多线程程序中进行引用计数。如果尝试在多线程中使用Rc<T>
则会得到一个编译错误。
+
+让我们回到列表 15-5 中的 cons list 例子。在列表 15-11 中尝试使用Box<T>
定义的List
。首先创建了一个包含 5 接着是 10 的列表实例。之后我们想要创建另外两个列表:一个以 3 开始并后接第一个包含 5 和 10 的列表,另一个以 4 开始其后也是第一个列表。换句话说,我们希望这两个列表共享第三个列表的所有权,概念上类似于图 15-10:
+
+Figure 15-10: Two lists, b
and c
, sharing ownership
+of a third list, a
+尝试使用Box<T>
定义的List
并不能工作,如列表 15-11 所示:
+Filename: src/main.rs
+enum List {
+ Cons(i32, Box<List>),
+ Nil,
+}
+
+use List::{Cons, Nil};
+
+fn main() {
+ let a = Cons(5,
+ Box::new(Cons(10,
+ Box::new(Nil))));
+ let b = Cons(3, Box::new(a));
+ let c = Cons(4, Box::new(a));
+}
+
+Listing 15-11: Having two lists using Box<T>
that try
+to share ownership of a third list won't work
+编译会得出如下错误:
+error[E0382]: use of moved value: `a`
+ --> src/main.rs:13:30
+ |
+12 | let b = Cons(3, Box::new(a));
+ | - value moved here
+13 | let c = Cons(4, Box::new(a));
+ | ^ value used here after move
+ |
+ = note: move occurs because `a` has type `List`, which does not
+ implement the `Copy` trait
+
+Cons
成员拥有其储存的数据,所以当创建b
列表时将a
的所有权移动到了b
。接着当再次尝使用a
创建c
时,这不被允许因为a
的所有权已经被移动。
+相反可以改变Cons
的定义来存放一个引用,不过接着必须指定生命周期参数,而且也必须以使得列表的每一个元素都与列表本身存在的一样久那样构造列表的元素。否则借用检查器甚至都不会允许我们编译代码。
+如列表 15-12 所示,可以将List
的定义从Box<T>
改为Rc<T>
:
+Filename: src/main.rs
+enum List {
+ Cons(i32, Rc<List>),
+ Nil,
+}
+
+use List::{Cons, Nil};
+use std::rc::Rc;
+
+fn main() {
+ let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
+ let b = Cons(3, a.clone());
+ let c = Cons(4, a.clone());
+}
+
+Listing 15-12: A definition of List
that uses
+Rc<T>
+注意必须为Rc
增加use
语句因为它不在 prelude 中。在main
中创建了存放 5 和 10 的列表并将其存放在一个叫做a
的新的Rc
中。接着当创建b
和c
时,我们对a
调用了clone
方法。
+
+之前我们见过clone
方法,当时使用它来创建某些数据的完整拷贝。但是对于Rc<T>
来说,它并不创建一个完整的拷贝。Rc<T>
存放了引用计数,也就是说,一个存在多少个克隆的数量。让我们像列表 15-13 那样在创建c
时增加一个内部作用域,并在不同的位置打印出关联函数Rc::strong_count
的结果。Rc::strong_count
返回传递给它的Rc
值的引用计数,而在本章的稍后部分介绍避免引用循环时讲到它为什么叫做strong_count
。
+Filename: src/main.rs
+# enum List {
+# Cons(i32, Rc<List>),
+# Nil,
+# }
+#
+# use List::{Cons, Nil};
+# use std::rc::Rc;
+#
+fn main() {
+ let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
+ println!("rc = {}", Rc::strong_count(&a));
+ let b = Cons(3, a.clone());
+ println!("rc after creating b = {}", Rc::strong_count(&a));
+ {
+ let c = Cons(4, a.clone());
+ println!("rc after creating c = {}", Rc::strong_count(&a));
+ }
+ println!("rc after c goes out of scope = {}", Rc::strong_count(&a));
+}
+
+Listing 15-13: Printing out the reference count
+这会打印出:
+rc = 1
+rc after creating b = 2
+rc after creating c = 3
+rc after c goes out of scope = 2
+
+不难看出a
的初始引用计数是一。接着每次调用clone
,计数会加一。当c
离开作用域时,计数减一,这发生在Rc<T>
的Drop
trait 实现中。这个例子中不能看到的是当b
接着是a
在main
函数的结尾离开作用域时,包含 5 和 10 的列表的引用计数会是 0,这时列表将被丢弃。这个策略允许拥有多个所有者,而引用计数会确保任何所有者存在时这个值保持有效。
+在本部分的开始,我们说Rc<T>
只允许程序的多个部分读取Rc<T>
中T
的不可变引用。如果Rc<T>
允许一个可变引用,我们将遇到第四章讨论的借用规则所不允许的问题:两个指向同一位置的可变借用会导致数据竞争和不一致。不过可变数据是非常有用的!在下一部分,我们将讨论内部可变性模式和RefCell<T>
类型,它可以与Rc<T>
结合使用来处理不可变性的限制。
+
+
+ch15-05-interior-mutability.md
+
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+
+内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用unsafe
代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的unsafe
代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
+让我们通过遵循内部可变性模式的RefCell<T>
类型来开始探索。
+
+不同于Rc<T>
,RefCell<T>
代表其数据的唯一的所有权。那么是什么让RefCell<T>
不同于像Box<T>
这样的类型呢?回忆一下第四章所学的借用规则:
+
+- 在任意给定时间,只能拥有如下中的一个:
+
+
+
+- 引用必须总是有效的。
+
+对于引用和Box<T>
,借用规则的不可变性作用于编译时。对于RefCell<T>
,这些不可变性作用于运行时。对于引用,如果违反这些规则,会得到一个编译错误。而对于RefCell<T>
,违反这些规则会panic!
。
+Rust 编译器执行的静态分析时天生保守的。代码的一些属性则不可能通过分析代码发现:其中最著名的就是停机问题(停机问题),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。
+因为一些分析是不可能的,Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不变,但不会带来灾难。RefCell<T>
正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。
+类似于Rc<T>
,RefCell<T>
只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用RefCell<T>
的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用RefCell<T>
,会得到一个编译错误。
+对于引用,可以使用&
和&mut
语法来分别创建不可变和可变的引用。不过对于RefCell<T>
,我们使用borrow
和borrow_mut
方法,它是RefCell<T>
拥有的安全 API 的一部分。borrow
返回Ref
类型的智能指针,而borrow_mut
返回RefMut
类型的智能指针。这两个类型实现了Deref
所以可以被当作常规引用处理。Ref
和RefMut
动态的借用所有权,而他们的Drop
实现也动态的释放借用。
diff --git a/src/ch04-02-references-and-borrowing.md b/src/ch04-02-references-and-borrowing.md
index 5324646..6605e31 100644
--- a/src/ch04-02-references-and-borrowing.md
+++ b/src/ch04-02-references-and-borrowing.md
@@ -274,7 +274,7 @@ fn no_dangle() -> String {
简要的概括一下对引用的讨论:
-1. 特定时间,**只能**拥有如下中的一个:
+1. 在任意给定时间,**只能**拥有如下中的一个:
* 一个可变引用。
* 任意属性的不可变引用。
2. 引用必须总是有效的。
diff --git a/src/ch15-03-drop.md b/src/ch15-03-drop.md
index de65fbf..fe6b06c 100644
--- a/src/ch15-03-drop.md
+++ b/src/ch15-03-drop.md
@@ -46,3 +46,44 @@ Wait for it...
Dropping CustomSmartPointer!
```
+被打印到屏幕上,它展示了 Rust 在实例离开作用域时自动调用了`drop`。
+
+可以使用`std::mem::drop`函数来在值离开作用域之前丢弃它。这通常是不必要的;整个`Drop` trait 的要点在于它自动的帮我们处理清理工作。在第十六章讲到并发时我们会看到一个需要在离开作用域之前丢弃值的例子。现在知道这是可能的即可,`std::mem::drop`位于 prelude 中所以可以如列表 15-9 所示直接调用`drop`:
+
+Filename: src/main.rs
+
+```rust,ignore
+fn main() {
+ let c = CustomSmartPointer { data: String::from("some data") };
+ println!("CustomSmartPointer created.");
+ drop(c);
+ println!("Wait for it...");
+}
+```
+
+Listing 15-9: Calling `std::mem::drop` to explicitly drop
+a value before it goes out of scope
+
+运行这段代码会打印出如下内容,因为`Dropping CustomSmartPointer!`在`CustomSmartPointer created.`和`Wait for it...`之间被打印出来,表明析构代码被执行了:
+
+```
+CustomSmartPointer created.
+Dropping CustomSmartPointer!
+Wait for it...
+```
+
+注意不允许直接调用我们定义的`drop`方法:如果将列表 15-9 中的`drop(c)`替换为`c.drop()`,会得到一个编译错误表明`explicit destructor calls not allowed`。不允许直接调用`Drop::drop`的原因是 Rust 在值离开作用域时会自动插入`Drop::drop`,这样就会丢弃值两次。丢弃一个值两次可能会造成错误或破坏内存,所以 Rust 就不允许这么做。相应的可以调用`std::mem::drop`,它的定义是:
+
+```rust
+pub mod std {
+ pub mod mem {
+ pub fn drop(x: T) { }
+ }
+}
+```
+
+这个函数对于`T`是泛型的,所以可以传递任何值。这个函数的函数体并没有任何实际内容,所以它也不会利用其参数。这个空函数的作用在于`drop`获取其参数的所有权,它意味着在这个函数结尾`x`离开作用域时`x`会被丢弃。
+
+使用`Drop` trait 实现指定的代码在很多方面都使得清理值变得方便和安全:比如可以使用它来创建我们自己的内存分配器!通过`Drop` trait 和 Rust 所有权系统,就无需担心之后清理代码,因为 Rust 会自动考虑这些问题。如果代码在值仍被使用时就清理它会出现编译错误,因为所有权系统确保了引用总是有效的,这也就保证了`drop`只会在值不再被使用时被调用一次。
+
+现在我们学习了`Box`和一些智能指针的特性,让我们聊聊一些其他标准库中定义的拥有各种实用功能的智能指针。
\ No newline at end of file
diff --git a/src/ch15-04-rc.md b/src/ch15-04-rc.md
index e69de29..882f8a3 100644
--- a/src/ch15-04-rc.md
+++ b/src/ch15-04-rc.md
@@ -0,0 +1,133 @@
+## `Rc` 引用计数智能指针
+
+> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-04-rc.md)
+>
+> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+
+大部分情况下所有权是非常明确的:可以准确的知道哪个变量拥有某个值。然而并不总是如此;有时确实可能需要多个所有者。为此,Rust 有一个叫做`Rc`的类型。它的名字是**引用计数**(*reference counting*)的缩写。引用计数意味着它记录一个值引用的数量来知晓这个值是否仍在被使用。如果这个值有零个引用,就知道可以在没有有效引用的前提下清理这个值。
+
+根据现实生活场景来想象的话,它就像一个客厅的电视。当一个人进来看电视时,他打开电视。其他人也会进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候关掉了电视,正在看电视人肯定会抓狂的!
+
+`Rc`用于当我们希望在堆上分配一些内存供程序的多个部分读取,而且无法在编译时确定程序的那一部分会最后结束使用它。如果我们知道的话那么常规的所有权规则会在编译时强制起作用。
+
+注意`Rc`只能用于单线程场景;下一章并发会涉及到如何在多线程程序中进行引用计数。如果尝试在多线程中使用`Rc`则会得到一个编译错误。
+
+### 使用`Rc`分享数据
+
+让我们回到列表 15-5 中的 cons list 例子。在列表 15-11 中尝试使用`Box`定义的`List`。首先创建了一个包含 5 接着是 10 的列表实例。之后我们想要创建另外两个列表:一个以 3 开始并后接第一个包含 5 和 10 的列表,另一个以 4 开始其后**也**是第一个列表。换句话说,我们希望这两个列表共享第三个列表的所有权,概念上类似于图 15-10:
+
+
+
+Figure 15-10: Two lists, `b` and `c`, sharing ownership
+of a third list, `a`
+
+尝试使用`Box`定义的`List`并不能工作,如列表 15-11 所示:
+
+Filename: src/main.rs
+
+```rust,ignore
+enum List {
+ Cons(i32, Box),
+ Nil,
+}
+
+use List::{Cons, Nil};
+
+fn main() {
+ let a = Cons(5,
+ Box::new(Cons(10,
+ Box::new(Nil))));
+ let b = Cons(3, Box::new(a));
+ let c = Cons(4, Box::new(a));
+}
+```
+
+Listing 15-11: Having two lists using `Box` that try
+to share ownership of a third list won't work
+
+编译会得出如下错误:
+
+```
+error[E0382]: use of moved value: `a`
+ --> src/main.rs:13:30
+ |
+12 | let b = Cons(3, Box::new(a));
+ | - value moved here
+13 | let c = Cons(4, Box::new(a));
+ | ^ value used here after move
+ |
+ = note: move occurs because `a` has type `List`, which does not
+ implement the `Copy` trait
+```
+
+`Cons`成员拥有其储存的数据,所以当创建`b`列表时将`a`的所有权移动到了`b`。接着当再次尝使用`a`创建`c`时,这不被允许因为`a`的所有权已经被移动。
+
+相反可以改变`Cons`的定义来存放一个引用,不过接着必须指定生命周期参数,而且也必须以使得列表的每一个元素都与列表本身存在的一样久那样构造列表的元素。否则借用检查器甚至都不会允许我们编译代码。
+
+如列表 15-12 所示,可以将`List`的定义从`Box`改为`Rc`:
+
+Filename: src/main.rs
+
+```rust
+enum List {
+ Cons(i32, Rc),
+ Nil,
+}
+
+use List::{Cons, Nil};
+use std::rc::Rc;
+
+fn main() {
+ let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
+ let b = Cons(3, a.clone());
+ let c = Cons(4, a.clone());
+}
+```
+
+Listing 15-12: A definition of `List` that uses
+`Rc`
+
+注意必须为`Rc`增加`use`语句因为它不在 prelude 中。在`main`中创建了存放 5 和 10 的列表并将其存放在一个叫做`a`的新的`Rc`中。接着当创建`b`和`c`时,我们对`a`调用了`clone`方法。
+
+### 克隆`Rc`会增加引用计数
+
+之前我们见过`clone`方法,当时使用它来创建某些数据的完整拷贝。但是对于`Rc`来说,它并不创建一个完整的拷贝。`Rc`存放了**引用计数**,也就是说,一个存在多少个克隆的数量。让我们像列表 15-13 那样在创建`c`时增加一个内部作用域,并在不同的位置打印出关联函数`Rc::strong_count`的结果。`Rc::strong_count`返回传递给它的`Rc`值的引用计数,而在本章的稍后部分介绍避免引用循环时讲到它为什么叫做`strong_count`。
+
+Filename: src/main.rs
+
+```rust
+# enum List {
+# Cons(i32, Rc),
+# Nil,
+# }
+#
+# use List::{Cons, Nil};
+# use std::rc::Rc;
+#
+fn main() {
+ let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
+ println!("rc = {}", Rc::strong_count(&a));
+ let b = Cons(3, a.clone());
+ println!("rc after creating b = {}", Rc::strong_count(&a));
+ {
+ let c = Cons(4, a.clone());
+ println!("rc after creating c = {}", Rc::strong_count(&a));
+ }
+ println!("rc after c goes out of scope = {}", Rc::strong_count(&a));
+}
+```
+
+Listing 15-13: Printing out the reference count
+
+这会打印出:
+
+```
+rc = 1
+rc after creating b = 2
+rc after creating c = 3
+rc after c goes out of scope = 2
+```
+
+不难看出`a`的初始引用计数是一。接着每次调用`clone`,计数会加一。当`c`离开作用域时,计数减一,这发生在`Rc`的`Drop` trait 实现中。这个例子中不能看到的是当`b`接着是`a`在`main`函数的结尾离开作用域时,包含 5 和 10 的列表的引用计数会是 0,这时列表将被丢弃。这个策略允许拥有多个所有者,而引用计数会确保任何所有者存在时这个值保持有效。
+
+在本部分的开始,我们说`Rc`只允许程序的多个部分读取`Rc`中`T`的不可变引用。如果`Rc`允许一个可变引用,我们将遇到第四章讨论的借用规则所不允许的问题:两个指向同一位置的可变借用会导致数据竞争和不一致。不过可变数据是非常有用的!在下一部分,我们将讨论内部可变性模式和`RefCell`类型,它可以与`Rc`结合使用来处理不可变性的限制。
\ No newline at end of file
diff --git a/src/ch15-05-interior-mutability.md b/src/ch15-05-interior-mutability.md
index e69de29..5883378 100644
--- a/src/ch15-05-interior-mutability.md
+++ b/src/ch15-05-interior-mutability.md
@@ -0,0 +1,29 @@
+## `RefCell`和内部可变性模式
+
+> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-05-interior-mutability.md)
+>
+> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+
+**内部可变性**(*Interior mutability*)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用`unsafe`代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的`unsafe`代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
+
+让我们通过遵循内部可变性模式的`RefCell`类型来开始探索。
+
+### `RefCell`拥有内部可变性
+
+不同于`Rc`,`RefCell`代表其数据的唯一的所有权。那么是什么让`RefCell`不同于像`Box`这样的类型呢?回忆一下第四章所学的借用规则:
+
+1. 在任意给定时间,**只能**拥有如下中的一个:
+ * 一个可变引用。
+ * 任意属性的不可变引用。
+2. 引用必须总是有效的。
+
+对于引用和`Box`,借用规则的不可变性作用于编译时。对于`RefCell`,这些不可变性作用于**运行时**。对于引用,如果违反这些规则,会得到一个编译错误。而对于`RefCell`,违反这些规则会`panic!`。
+
+Rust 编译器执行的静态分析时天生保守的。代码的一些属性则不可能通过分析代码发现:其中最著名的就是停机问题(停机问题),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。
+
+因为一些分析是不可能的,Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不变,但不会带来灾难。`RefCell`正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。
+
+类似于`Rc`,`RefCell`只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用`RefCell`的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用`RefCell`,会得到一个编译错误。
+
+对于引用,可以使用`&`和`&mut`语法来分别创建不可变和可变的引用。不过对于`RefCell`,我们使用`borrow`和`borrow_mut`方法,它是`RefCell`拥有的安全 API 的一部分。`borrow`返回`Ref`类型的智能指针,而`borrow_mut`返回`RefMut`类型的智能指针。这两个类型实现了`Deref`所以可以被当作常规引用处理。`Ref`和`RefMut`动态的借用所有权,而他们的`Drop`实现也动态的释放借用。
+