From 4b082223e0260e301fc5a1fc9b18fc9e1ce10805 Mon Sep 17 00:00:00 2001 From: KaiserY Date: Fri, 17 Mar 2017 00:00:53 +0800 Subject: [PATCH] wip add ch15-06 --- docs/ch15-05-interior-mutability.html | 109 ++++++++++++++- docs/ch15-06-reference-cycles.html | 81 ++++++++++- docs/print.html | 188 +++++++++++++++++++++++++- src/ch15-05-interior-mutability.md | 145 +++++++++++++++++++- src/ch15-06-reference-cycles.md | 94 +++++++++++++ 5 files changed, 613 insertions(+), 4 deletions(-) diff --git a/docs/ch15-05-interior-mutability.html b/docs/ch15-05-interior-mutability.html index 226197c..a3bdf7a 100644 --- a/docs/ch15-05-interior-mutability.html +++ b/docs/ch15-05-interior-mutability.html @@ -73,7 +73,7 @@
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

-

内部可变性Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用unsafe代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的unsafe代码将被封装进安全的 API 中,而外部类型仍然是不可变的。

+

内部可变性Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用unsafe代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的unsafe代码将被封装进安全的 API 中,而外部类型仍然是不可变的。

让我们通过遵循内部可变性模式的RefCell<T>类型来开始探索。

RefCell<T>拥有内部可变性

不同于Rc<T>RefCell<T>代表其数据的唯一的所有权。那么是什么让RefCell<T>不同于像Box<T>这样的类型呢?回忆一下第四章所学的借用规则:

@@ -92,6 +92,113 @@ commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

因为一些分析是不可能的,Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不变,但不会带来灾难。RefCell<T>正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。

类似于Rc<T>RefCell<T>只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用RefCell<T>的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用RefCell<T>,会得到一个编译错误。

对于引用,可以使用&&mut语法来分别创建不可变和可变的引用。不过对于RefCell<T>,我们使用borrowborrow_mut方法,它是RefCell<T>拥有的安全 API 的一部分。borrow返回Ref类型的智能指针,而borrow_mut返回RefMut类型的智能指针。这两个类型实现了Deref所以可以被当作常规引用处理。RefRefMut动态的借用所有权,而他们的Drop实现也动态的释放借用。

+

列表 15-14 展示了如何使用RefCell<T>来使函数不可变的和可变的借用它的参数。注意data变量使用let data而不是let mut data来声明为不可变的,而a_fn_that_mutably_borrows则允许可变的借用数据并修改它!

+

Filename: src/main.rs

+
use std::cell::RefCell;
+
+fn a_fn_that_immutably_borrows(a: &i32) {
+    println!("a is {}", a);
+}
+
+fn a_fn_that_mutably_borrows(b: &mut i32) {
+    *b += 1;
+}
+
+fn demo(r: &RefCell<i32>) {
+    a_fn_that_immutably_borrows(&r.borrow());
+    a_fn_that_mutably_borrows(&mut r.borrow_mut());
+    a_fn_that_immutably_borrows(&r.borrow());
+}
+
+fn main() {
+    let data = RefCell::new(5);
+    demo(&data);
+}
+
+

Listing 15-14: Using RefCell<T>, borrow, and +borrow_mut

+

这个例子打印出:

+
a is 5
+a is 6
+
+

main函数中,我们新声明了一个包含值 5 的RefCell<T>,并储存在变量data中,声明时并没有使用mut关键字。接着使用data的一个不可变引用来调用demo函数:对于main函数而言data是不可变的!

+

demo函数中,通过调用borrow方法来获取到RefCell<T>中值的不可变引用,并使用这个不可变引用调用了a_fn_that_immutably_borrows函数。更为有趣的是,可以通过borrow_mut方法来获取RefCell<T>中值的可变引用,而a_fn_that_mutably_borrows函数就允许修改这个值。可以看到下一次调用a_fn_that_immutably_borrows时打印出的值是 6 而不是 5。

+

RefCell<T>在运行时检查借用规则

+

回忆一下第四章因为借用规则,尝试使用常规引用在同一作用域中创建两个可变引用的代码无法编译:

+
let mut s = String::from("hello");
+
+let r1 = &mut s;
+let r2 = &mut s;
+
+

这会得到一个编译错误:

+
error[E0499]: cannot borrow `s` as mutable more than once at a time
+ -->
+  |
+5 |     let r1 = &mut s;
+  |                   - first mutable borrow occurs here
+6 |     let r2 = &mut s;
+  |                   ^ second mutable borrow occurs here
+7 | }
+  | - first borrow ends here
+
+

与此相反,使用RefCell<T>并在同一作用域调用两次borrow_mut的代码是可以编译的,不过它会在运行时 panic。如下代码:

+
use std::cell::RefCell;
+
+fn main() {
+    let s = RefCell::new(String::from("hello"));
+
+    let r1 = s.borrow_mut();
+    let r2 = s.borrow_mut();
+}
+
+

能够编译不过在cargo run运行时会出现如下错误:

+
    Finished dev [unoptimized + debuginfo] target(s) in 0.83 secs
+     Running `target/debug/refcell`
+thread 'main' panicked at 'already borrowed: BorrowMutError',
+/stable-dist-rustc/build/src/libcore/result.rs:868
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+

这个运行时BorrowMutError类似于编译错误:它表明已经可变的借用过s一次了,所以不允许再次借用它。我们并没有绕过借用规则,只是选择让 Rust 在运行时而不是编译时执行他们。你可以选择在任何时候任何地方使用RefCell<T>,不过除了不得不编写很多RefCell之外,最终还是可能会发现其中的问题(可能是在生产环境而不是开发环境)。另外,在运行时检查借用规则有性能惩罚。

+

结合Rc<T>RefCell<T>来拥有多个可变数据所有者

+

那么为什么要权衡考虑选择引入RefCell<T>呢?好吧,还记得我们说过Rc<T>只能拥有一个T的不可变引用吗?考虑到RefCell<T>是不可变的,但是拥有内部可变性,可以将Rc<T>RefCell<T>结合来创造一个既有引用计数又可变的类型。列表 15-15 展示了一个这么做的例子,再次回到列表 15-5 中的 cons list。在这个例子中,不同于在 cons list 中储存i32值,我们储存一个Rc<RefCell<i32>>值。希望储存这个类型是因为其可以拥有不属于列表一部分的这个值的所有者(Rc<T>提供的多个所有者功能),而且还可以改变内部的i32值(RefCell<T>提供的内部可变性功能):

+

Filename: src/main.rs

+
#[derive(Debug)]
+enum List {
+    Cons(Rc<RefCell<i32>>, Rc<List>),
+    Nil,
+}
+
+use List::{Cons, Nil};
+use std::rc::Rc;
+use std::cell::RefCell;
+
+fn main() {
+    let value = Rc::new(RefCell::new(5));
+
+    let a = Cons(value.clone(), Rc::new(Nil));
+    let shared_list = Rc::new(a);
+
+    let b = Cons(Rc::new(RefCell::new(6)), shared_list.clone());
+    let c = Cons(Rc::new(RefCell::new(10)), shared_list.clone());
+
+    *value.borrow_mut() += 10;
+
+    println!("shared_list after = {:?}", shared_list);
+    println!("b after = {:?}", b);
+    println!("c after = {:?}", c);
+}
+
+

Listing 15-15: Using Rc<RefCell<i32>> to create a +List that we can mutate

+

我们创建了一个值,它是Rc<RefCell<i32>>的实例。将其储存在变量value中因为我们希望之后能直接访问它。接着在a中创建了一个拥有存放了value值的Cons成员的List,而且value需要被克隆因为我们希望除了a之外还拥有value的所有权。接着将a封装进Rc<T>中这样就可以创建都引用a的有着不同开头的列表bc,类似列表 15-12 中所做的那样。

+

一旦创建了shared_listbc,接下来就可以通过解引用Rc<T>和对RefCell调用borrow_mut来将 10 与 5 相加了。

+

当打印出shared_listbc时,可以看到他们都拥有被修改的值 15:

+
shared_list after = Cons(RefCell { value: 15 }, Nil)
+b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
+c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
+
+

这是非常巧妙的!通过使用RefCell<T>,我们可以拥有一个表面上不可变的List,不过可以使用RefCell<T>中提供内部可变性的方法来在需要时修改数据。RefCell<T>的运行时借用规则检查也确实保护我们免于出现数据竞争,而且我们也决定牺牲一些速度来换取数据结构的灵活性。

+

RefCell<T>并不是标准库中唯一提供内部可变性的类型。Cell<T>有点类似,不过不同于RefCell<T>那样提供内部值的引用,其值被拷贝进和拷贝出Cell<T>Mutex<T>提供线程间安全的内部可变性,下一章并发会讨论它的应用。请查看标准库来获取更多细节和不同类型的区别。

diff --git a/docs/ch15-06-reference-cycles.html b/docs/ch15-06-reference-cycles.html index cda577a..2ede239 100644 --- a/docs/ch15-06-reference-cycles.html +++ b/docs/ch15-06-reference-cycles.html @@ -67,7 +67,86 @@
- +

引用循环和内存泄漏是安全的

+
+

ch15-06-reference-cycles.md +
+commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894

+
+

我们讨论过 Rust 做出的一些保证,例如永远也不会遇到一个空值,而且数据竞争也会在编译时被阻止。Rust 的内存安全保证也使其更难以制造从不被清理的内存,这被称为内存泄露。然而 Rust 并不是不可能出现内存泄漏,避免内存泄露不是 Rust 的保证之一。换句话说,内存泄露是安全的。

+

在使用Rc<T>RefCell<T>时,有可能创建循环引用,这时各个项相互引用并形成环。这是不好的因为每一项的引用计数将永远也到不了 0,其值也永远也不会被丢弃。让我们看看这是如何发生的以及如何避免它。

+

在列表 15-16 中,我们将使用列表 15-5 中List定义的另一个变体。我们将回到储存i32值作为Cons成员的第一个元素。现在Cons成员的第二个元素是RefCell<Rc<List>>:这时就不能修改i32值了,但是能够修改Cons成员指向的哪个List。还需要增加一个tail方法来方便我们在拥有一个Cons成员时访问第二个项:

+

Filename: src/main.rs

+
#[derive(Debug)]
+enum List {
+    Cons(i32, RefCell<Rc<List>>),
+    Nil,
+}
+
+impl List {
+    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
+        match *self {
+            Cons(_, ref item) => Some(item),
+            Nil => None,
+        }
+    }
+}
+
+

Listing 15-16: A cons list definition that holds a +RefCell so that we can modify what a Cons variant is referring to

+

接下来,在列表 15-17 中,我们将在变量a中创建一个List值,其内部是一个5, Nil的列表。接着在变量b创建一个值 10 和指向a中列表的List值。最后修改a指向b而不是Nil,这会创建一个循环:

+

Filename: src/main.rs

+
# #[derive(Debug)]
+# enum List {
+#     Cons(i32, RefCell<Rc<List>>),
+#     Nil,
+# }
+#
+# impl List {
+#     fn tail(&self) -> Option<&RefCell<Rc<List>>> {
+#         match *self {
+#             Cons(_, ref item) => Some(item),
+#             Nil => None,
+#         }
+#     }
+# }
+#
+use List::{Cons, Nil};
+use std::rc::Rc;
+use std::cell::RefCell;
+
+fn main() {
+
+    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
+
+    println!("a initial rc count = {}", Rc::strong_count(&a));
+    println!("a next item = {:?}", a.tail());
+
+    let b = Rc::new(Cons(10, RefCell::new(a.clone())));
+
+    println!("a rc count after b creation = {}", Rc::strong_count(&a));
+    println!("b initial rc count = {}", Rc::strong_count(&b));
+    println!("b next item = {:?}", b.tail());
+
+    if let Some(ref link) = a.tail() {
+        *link.borrow_mut() = b.clone();
+    }
+
+    println!("b rc count after changing a = {}", Rc::strong_count(&b));
+    println!("a rc count after changing a = {}", Rc::strong_count(&a));
+
+    // Uncomment the next line to see that we have a cycle; it will
+    // overflow the stack
+    // println!("a next item = {:?}", a.tail());
+}
+
+

Listing 15-17: Creating a reference cycle of two List +values pointing to each other

+

使用tail方法来获取aRefCell的引用,并将其放入变量link中。接着对RefCell使用borrow_mut方法将其中的值从存放Nil值的Rc改为b中的Rc。这创建了一个看起来像图 15-18 所示的引用循环:

+

Reference cycle of lists

+

Figure 15-18: A reference cycle of lists a and b +pointing to each other

+
diff --git a/docs/print.html b/docs/print.html index fe26d18..8398e6d 100644 --- a/docs/print.html +++ b/docs/print.html @@ -8523,7 +8523,7 @@ rc after c goes out of scope = 2
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

-

内部可变性Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用unsafe代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的unsafe代码将被封装进安全的 API 中,而外部类型仍然是不可变的。

+

内部可变性Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用unsafe代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的unsafe代码将被封装进安全的 API 中,而外部类型仍然是不可变的。

让我们通过遵循内部可变性模式的RefCell<T>类型来开始探索。

RefCell<T>拥有内部可变性

不同于Rc<T>RefCell<T>代表其数据的唯一的所有权。那么是什么让RefCell<T>不同于像Box<T>这样的类型呢?回忆一下第四章所学的借用规则:

@@ -8542,6 +8542,192 @@ commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

因为一些分析是不可能的,Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不变,但不会带来灾难。RefCell<T>正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。

类似于Rc<T>RefCell<T>只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用RefCell<T>的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用RefCell<T>,会得到一个编译错误。

对于引用,可以使用&&mut语法来分别创建不可变和可变的引用。不过对于RefCell<T>,我们使用borrowborrow_mut方法,它是RefCell<T>拥有的安全 API 的一部分。borrow返回Ref类型的智能指针,而borrow_mut返回RefMut类型的智能指针。这两个类型实现了Deref所以可以被当作常规引用处理。RefRefMut动态的借用所有权,而他们的Drop实现也动态的释放借用。

+

列表 15-14 展示了如何使用RefCell<T>来使函数不可变的和可变的借用它的参数。注意data变量使用let data而不是let mut data来声明为不可变的,而a_fn_that_mutably_borrows则允许可变的借用数据并修改它!

+

Filename: src/main.rs

+
use std::cell::RefCell;
+
+fn a_fn_that_immutably_borrows(a: &i32) {
+    println!("a is {}", a);
+}
+
+fn a_fn_that_mutably_borrows(b: &mut i32) {
+    *b += 1;
+}
+
+fn demo(r: &RefCell<i32>) {
+    a_fn_that_immutably_borrows(&r.borrow());
+    a_fn_that_mutably_borrows(&mut r.borrow_mut());
+    a_fn_that_immutably_borrows(&r.borrow());
+}
+
+fn main() {
+    let data = RefCell::new(5);
+    demo(&data);
+}
+
+

Listing 15-14: Using RefCell<T>, borrow, and +borrow_mut

+

这个例子打印出:

+
a is 5
+a is 6
+
+

main函数中,我们新声明了一个包含值 5 的RefCell<T>,并储存在变量data中,声明时并没有使用mut关键字。接着使用data的一个不可变引用来调用demo函数:对于main函数而言data是不可变的!

+

demo函数中,通过调用borrow方法来获取到RefCell<T>中值的不可变引用,并使用这个不可变引用调用了a_fn_that_immutably_borrows函数。更为有趣的是,可以通过borrow_mut方法来获取RefCell<T>中值的可变引用,而a_fn_that_mutably_borrows函数就允许修改这个值。可以看到下一次调用a_fn_that_immutably_borrows时打印出的值是 6 而不是 5。

+

RefCell<T>在运行时检查借用规则

+

回忆一下第四章因为借用规则,尝试使用常规引用在同一作用域中创建两个可变引用的代码无法编译:

+
let mut s = String::from("hello");
+
+let r1 = &mut s;
+let r2 = &mut s;
+
+

这会得到一个编译错误:

+
error[E0499]: cannot borrow `s` as mutable more than once at a time
+ -->
+  |
+5 |     let r1 = &mut s;
+  |                   - first mutable borrow occurs here
+6 |     let r2 = &mut s;
+  |                   ^ second mutable borrow occurs here
+7 | }
+  | - first borrow ends here
+
+

与此相反,使用RefCell<T>并在同一作用域调用两次borrow_mut的代码是可以编译的,不过它会在运行时 panic。如下代码:

+
use std::cell::RefCell;
+
+fn main() {
+    let s = RefCell::new(String::from("hello"));
+
+    let r1 = s.borrow_mut();
+    let r2 = s.borrow_mut();
+}
+
+

能够编译不过在cargo run运行时会出现如下错误:

+
    Finished dev [unoptimized + debuginfo] target(s) in 0.83 secs
+     Running `target/debug/refcell`
+thread 'main' panicked at 'already borrowed: BorrowMutError',
+/stable-dist-rustc/build/src/libcore/result.rs:868
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+

这个运行时BorrowMutError类似于编译错误:它表明已经可变的借用过s一次了,所以不允许再次借用它。我们并没有绕过借用规则,只是选择让 Rust 在运行时而不是编译时执行他们。你可以选择在任何时候任何地方使用RefCell<T>,不过除了不得不编写很多RefCell之外,最终还是可能会发现其中的问题(可能是在生产环境而不是开发环境)。另外,在运行时检查借用规则有性能惩罚。

+

结合Rc<T>RefCell<T>来拥有多个可变数据所有者

+

那么为什么要权衡考虑选择引入RefCell<T>呢?好吧,还记得我们说过Rc<T>只能拥有一个T的不可变引用吗?考虑到RefCell<T>是不可变的,但是拥有内部可变性,可以将Rc<T>RefCell<T>结合来创造一个既有引用计数又可变的类型。列表 15-15 展示了一个这么做的例子,再次回到列表 15-5 中的 cons list。在这个例子中,不同于在 cons list 中储存i32值,我们储存一个Rc<RefCell<i32>>值。希望储存这个类型是因为其可以拥有不属于列表一部分的这个值的所有者(Rc<T>提供的多个所有者功能),而且还可以改变内部的i32值(RefCell<T>提供的内部可变性功能):

+

Filename: src/main.rs

+
#[derive(Debug)]
+enum List {
+    Cons(Rc<RefCell<i32>>, Rc<List>),
+    Nil,
+}
+
+use List::{Cons, Nil};
+use std::rc::Rc;
+use std::cell::RefCell;
+
+fn main() {
+    let value = Rc::new(RefCell::new(5));
+
+    let a = Cons(value.clone(), Rc::new(Nil));
+    let shared_list = Rc::new(a);
+
+    let b = Cons(Rc::new(RefCell::new(6)), shared_list.clone());
+    let c = Cons(Rc::new(RefCell::new(10)), shared_list.clone());
+
+    *value.borrow_mut() += 10;
+
+    println!("shared_list after = {:?}", shared_list);
+    println!("b after = {:?}", b);
+    println!("c after = {:?}", c);
+}
+
+

Listing 15-15: Using Rc<RefCell<i32>> to create a +List that we can mutate

+

我们创建了一个值,它是Rc<RefCell<i32>>的实例。将其储存在变量value中因为我们希望之后能直接访问它。接着在a中创建了一个拥有存放了value值的Cons成员的List,而且value需要被克隆因为我们希望除了a之外还拥有value的所有权。接着将a封装进Rc<T>中这样就可以创建都引用a的有着不同开头的列表bc,类似列表 15-12 中所做的那样。

+

一旦创建了shared_listbc,接下来就可以通过解引用Rc<T>和对RefCell调用borrow_mut来将 10 与 5 相加了。

+

当打印出shared_listbc时,可以看到他们都拥有被修改的值 15:

+
shared_list after = Cons(RefCell { value: 15 }, Nil)
+b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
+c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
+
+

这是非常巧妙的!通过使用RefCell<T>,我们可以拥有一个表面上不可变的List,不过可以使用RefCell<T>中提供内部可变性的方法来在需要时修改数据。RefCell<T>的运行时借用规则检查也确实保护我们免于出现数据竞争,而且我们也决定牺牲一些速度来换取数据结构的灵活性。

+

RefCell<T>并不是标准库中唯一提供内部可变性的类型。Cell<T>有点类似,不过不同于RefCell<T>那样提供内部值的引用,其值被拷贝进和拷贝出Cell<T>Mutex<T>提供线程间安全的内部可变性,下一章并发会讨论它的应用。请查看标准库来获取更多细节和不同类型的区别。

+

引用循环和内存泄漏是安全的

+
+

ch15-06-reference-cycles.md +
+commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894

+
+

我们讨论过 Rust 做出的一些保证,例如永远也不会遇到一个空值,而且数据竞争也会在编译时被阻止。Rust 的内存安全保证也使其更难以制造从不被清理的内存,这被称为内存泄露。然而 Rust 并不是不可能出现内存泄漏,避免内存泄露不是 Rust 的保证之一。换句话说,内存泄露是安全的。

+

在使用Rc<T>RefCell<T>时,有可能创建循环引用,这时各个项相互引用并形成环。这是不好的因为每一项的引用计数将永远也到不了 0,其值也永远也不会被丢弃。让我们看看这是如何发生的以及如何避免它。

+

在列表 15-16 中,我们将使用列表 15-5 中List定义的另一个变体。我们将回到储存i32值作为Cons成员的第一个元素。现在Cons成员的第二个元素是RefCell<Rc<List>>:这时就不能修改i32值了,但是能够修改Cons成员指向的哪个List。还需要增加一个tail方法来方便我们在拥有一个Cons成员时访问第二个项:

+

Filename: src/main.rs

+
#[derive(Debug)]
+enum List {
+    Cons(i32, RefCell<Rc<List>>),
+    Nil,
+}
+
+impl List {
+    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
+        match *self {
+            Cons(_, ref item) => Some(item),
+            Nil => None,
+        }
+    }
+}
+
+

Listing 15-16: A cons list definition that holds a +RefCell so that we can modify what a Cons variant is referring to

+

接下来,在列表 15-17 中,我们将在变量a中创建一个List值,其内部是一个5, Nil的列表。接着在变量b创建一个值 10 和指向a中列表的List值。最后修改a指向b而不是Nil,这会创建一个循环:

+

Filename: src/main.rs

+
# #[derive(Debug)]
+# enum List {
+#     Cons(i32, RefCell<Rc<List>>),
+#     Nil,
+# }
+#
+# impl List {
+#     fn tail(&self) -> Option<&RefCell<Rc<List>>> {
+#         match *self {
+#             Cons(_, ref item) => Some(item),
+#             Nil => None,
+#         }
+#     }
+# }
+#
+use List::{Cons, Nil};
+use std::rc::Rc;
+use std::cell::RefCell;
+
+fn main() {
+
+    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
+
+    println!("a initial rc count = {}", Rc::strong_count(&a));
+    println!("a next item = {:?}", a.tail());
+
+    let b = Rc::new(Cons(10, RefCell::new(a.clone())));
+
+    println!("a rc count after b creation = {}", Rc::strong_count(&a));
+    println!("b initial rc count = {}", Rc::strong_count(&b));
+    println!("b next item = {:?}", b.tail());
+
+    if let Some(ref link) = a.tail() {
+        *link.borrow_mut() = b.clone();
+    }
+
+    println!("b rc count after changing a = {}", Rc::strong_count(&b));
+    println!("a rc count after changing a = {}", Rc::strong_count(&a));
+
+    // Uncomment the next line to see that we have a cycle; it will
+    // overflow the stack
+    // println!("a next item = {:?}", a.tail());
+}
+
+

Listing 15-17: Creating a reference cycle of two List +values pointing to each other

+

使用tail方法来获取aRefCell的引用,并将其放入变量link中。接着对RefCell使用borrow_mut方法将其中的值从存放Nil值的Rc改为b中的Rc。这创建了一个看起来像图 15-18 所示的引用循环:

+

Reference cycle of lists

+

Figure 15-18: A reference cycle of lists a and b +pointing to each other

diff --git a/src/ch15-05-interior-mutability.md b/src/ch15-05-interior-mutability.md index 5883378..c6c2716 100644 --- a/src/ch15-05-interior-mutability.md +++ b/src/ch15-05-interior-mutability.md @@ -4,7 +4,7 @@ >
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56 -**内部可变性**(*Interior mutability*)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用`unsafe`代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的`unsafe`代码将被封装进安全的 API 中,而外部类型仍然是不可变的。 +**内部可变性**(*Interior mutability*)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用`unsafe`代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的`unsafe`代码将被封装进安全的 API 中,而外部类型仍然是不可变的。 让我们通过遵循内部可变性模式的`RefCell`类型来开始探索。 @@ -27,3 +27,146 @@ Rust 编译器执行的静态分析时天生保守的。代码的一些属性则 对于引用,可以使用`&`和`&mut`语法来分别创建不可变和可变的引用。不过对于`RefCell`,我们使用`borrow`和`borrow_mut`方法,它是`RefCell`拥有的安全 API 的一部分。`borrow`返回`Ref`类型的智能指针,而`borrow_mut`返回`RefMut`类型的智能指针。这两个类型实现了`Deref`所以可以被当作常规引用处理。`Ref`和`RefMut`动态的借用所有权,而他们的`Drop`实现也动态的释放借用。 +列表 15-14 展示了如何使用`RefCell`来使函数不可变的和可变的借用它的参数。注意`data`变量使用`let data`而不是`let mut data`来声明为不可变的,而`a_fn_that_mutably_borrows`则允许可变的借用数据并修改它! + +Filename: src/main.rs + +```rust +use std::cell::RefCell; + +fn a_fn_that_immutably_borrows(a: &i32) { + println!("a is {}", a); +} + +fn a_fn_that_mutably_borrows(b: &mut i32) { + *b += 1; +} + +fn demo(r: &RefCell) { + a_fn_that_immutably_borrows(&r.borrow()); + a_fn_that_mutably_borrows(&mut r.borrow_mut()); + a_fn_that_immutably_borrows(&r.borrow()); +} + +fn main() { + let data = RefCell::new(5); + demo(&data); +} +``` + +Listing 15-14: Using `RefCell`, `borrow`, and +`borrow_mut` + +这个例子打印出: + +``` +a is 5 +a is 6 +``` + +在`main`函数中,我们新声明了一个包含值 5 的`RefCell`,并储存在变量`data`中,声明时并没有使用`mut`关键字。接着使用`data`的一个不可变引用来调用`demo`函数:对于`main`函数而言`data`是不可变的! + +在`demo`函数中,通过调用`borrow`方法来获取到`RefCell`中值的不可变引用,并使用这个不可变引用调用了`a_fn_that_immutably_borrows`函数。更为有趣的是,可以通过`borrow_mut`方法来获取`RefCell`中值的**可变**引用,而`a_fn_that_mutably_borrows`函数就允许修改这个值。可以看到下一次调用`a_fn_that_immutably_borrows`时打印出的值是 6 而不是 5。 + +### `RefCell`在运行时检查借用规则 + +回忆一下第四章因为借用规则,尝试使用常规引用在同一作用域中创建两个可变引用的代码无法编译: + +```rust,ignore +let mut s = String::from("hello"); + +let r1 = &mut s; +let r2 = &mut s; +``` + +这会得到一个编译错误: + +``` +error[E0499]: cannot borrow `s` as mutable more than once at a time + --> + | +5 | let r1 = &mut s; + | - first mutable borrow occurs here +6 | let r2 = &mut s; + | ^ second mutable borrow occurs here +7 | } + | - first borrow ends here +``` + +与此相反,使用`RefCell`并在同一作用域调用两次`borrow_mut`的代码是**可以**编译的,不过它会在运行时 panic。如下代码: + +```rust,should_panic +use std::cell::RefCell; + +fn main() { + let s = RefCell::new(String::from("hello")); + + let r1 = s.borrow_mut(); + let r2 = s.borrow_mut(); +} +``` + +能够编译不过在`cargo run`运行时会出现如下错误: + +``` + Finished dev [unoptimized + debuginfo] target(s) in 0.83 secs + Running `target/debug/refcell` +thread 'main' panicked at 'already borrowed: BorrowMutError', +/stable-dist-rustc/build/src/libcore/result.rs:868 +note: Run with `RUST_BACKTRACE=1` for a backtrace. +``` + +这个运行时`BorrowMutError`类似于编译错误:它表明已经可变的借用过`s`一次了,所以不允许再次借用它。我们并没有绕过借用规则,只是选择让 Rust 在运行时而不是编译时执行他们。你可以选择在任何时候任何地方使用`RefCell`,不过除了不得不编写很多`RefCell`之外,最终还是可能会发现其中的问题(可能是在生产环境而不是开发环境)。另外,在运行时检查借用规则有性能惩罚。 + +### 结合`Rc`和`RefCell`来拥有多个可变数据所有者 + +那么为什么要权衡考虑选择引入`RefCell`呢?好吧,还记得我们说过`Rc`只能拥有一个`T`的不可变引用吗?考虑到`RefCell`是不可变的,但是拥有内部可变性,可以将`Rc`与`RefCell`结合来创造一个既有引用计数又可变的类型。列表 15-15 展示了一个这么做的例子,再次回到列表 15-5 中的 cons list。在这个例子中,不同于在 cons list 中储存`i32`值,我们储存一个`Rc>`值。希望储存这个类型是因为其可以拥有不属于列表一部分的这个值的所有者(`Rc`提供的多个所有者功能),而且还可以改变内部的`i32`值(`RefCell`提供的内部可变性功能): + +Filename: src/main.rs + +```rust +#[derive(Debug)] +enum List { + Cons(Rc>, Rc), + Nil, +} + +use List::{Cons, Nil}; +use std::rc::Rc; +use std::cell::RefCell; + +fn main() { + let value = Rc::new(RefCell::new(5)); + + let a = Cons(value.clone(), Rc::new(Nil)); + let shared_list = Rc::new(a); + + let b = Cons(Rc::new(RefCell::new(6)), shared_list.clone()); + let c = Cons(Rc::new(RefCell::new(10)), shared_list.clone()); + + *value.borrow_mut() += 10; + + println!("shared_list after = {:?}", shared_list); + println!("b after = {:?}", b); + println!("c after = {:?}", c); +} +``` + +Listing 15-15: Using `Rc>` to create a +`List` that we can mutate + +我们创建了一个值,它是`Rc>`的实例。将其储存在变量`value`中因为我们希望之后能直接访问它。接着在`a`中创建了一个拥有存放了`value`值的`Cons`成员的`List`,而且`value`需要被克隆因为我们希望除了`a`之外还拥有`value`的所有权。接着将`a`封装进`Rc`中这样就可以创建都引用`a`的有着不同开头的列表`b`和`c`,类似列表 15-12 中所做的那样。 + +一旦创建了`shared_list`、`b`和`c`,接下来就可以通过解引用`Rc`和对`RefCell`调用`borrow_mut`来将 10 与 5 相加了。 + +当打印出`shared_list`、`b`和`c`时,可以看到他们都拥有被修改的值 15: + +``` +shared_list after = Cons(RefCell { value: 15 }, Nil) +b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil)) +c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil)) +``` + +这是非常巧妙的!通过使用`RefCell`,我们可以拥有一个表面上不可变的`List`,不过可以使用`RefCell`中提供内部可变性的方法来在需要时修改数据。`RefCell`的运行时借用规则检查也确实保护我们免于出现数据竞争,而且我们也决定牺牲一些速度来换取数据结构的灵活性。 + +`RefCell`并不是标准库中唯一提供内部可变性的类型。`Cell`有点类似,不过不同于`RefCell`那样提供内部值的引用,其值被拷贝进和拷贝出`Cell`。`Mutex`提供线程间安全的内部可变性,下一章并发会讨论它的应用。请查看标准库来获取更多细节和不同类型的区别。 \ No newline at end of file diff --git a/src/ch15-06-reference-cycles.md b/src/ch15-06-reference-cycles.md index e69de29..773a305 100644 --- a/src/ch15-06-reference-cycles.md +++ b/src/ch15-06-reference-cycles.md @@ -0,0 +1,94 @@ +## 引用循环和内存泄漏是安全的 + +> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-06-reference-cycles.md) +>
+> commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894 + +我们讨论过 Rust 做出的一些保证,例如永远也不会遇到一个空值,而且数据竞争也会在编译时被阻止。Rust 的内存安全保证也使其更难以制造从不被清理的内存,这被称为**内存泄露**。然而 Rust 并不是**不可能**出现内存泄漏,避免内存泄露**并**不是 Rust 的保证之一。换句话说,内存泄露是安全的。 + +在使用`Rc`和`RefCell`时,有可能创建循环引用,这时各个项相互引用并形成环。这是不好的因为每一项的引用计数将永远也到不了 0,其值也永远也不会被丢弃。让我们看看这是如何发生的以及如何避免它。 + +在列表 15-16 中,我们将使用列表 15-5 中`List`定义的另一个变体。我们将回到储存`i32`值作为`Cons`成员的第一个元素。现在`Cons`成员的第二个元素是`RefCell>`:这时就不能修改`i32`值了,但是能够修改`Cons`成员指向的哪个`List`。还需要增加一个`tail`方法来方便我们在拥有一个`Cons`成员时访问第二个项: + +Filename: src/main.rs + +```rust,ignore +#[derive(Debug)] +enum List { + Cons(i32, RefCell>), + Nil, +} + +impl List { + fn tail(&self) -> Option<&RefCell>> { + match *self { + Cons(_, ref item) => Some(item), + Nil => None, + } + } +} +``` + +Listing 15-16: A cons list definition that holds a +`RefCell` so that we can modify what a `Cons` variant is referring to + +接下来,在列表 15-17 中,我们将在变量`a`中创建一个`List`值,其内部是一个`5, Nil`的列表。接着在变量`b`创建一个值 10 和指向`a`中列表的`List`值。最后修改`a`指向`b`而不是`Nil`,这会创建一个循环: + +Filename: src/main.rs + +```rust +# #[derive(Debug)] +# enum List { +# Cons(i32, RefCell>), +# Nil, +# } +# +# impl List { +# fn tail(&self) -> Option<&RefCell>> { +# match *self { +# Cons(_, ref item) => Some(item), +# Nil => None, +# } +# } +# } +# +use List::{Cons, Nil}; +use std::rc::Rc; +use std::cell::RefCell; + +fn main() { + + let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); + + println!("a initial rc count = {}", Rc::strong_count(&a)); + println!("a next item = {:?}", a.tail()); + + let b = Rc::new(Cons(10, RefCell::new(a.clone()))); + + println!("a rc count after b creation = {}", Rc::strong_count(&a)); + println!("b initial rc count = {}", Rc::strong_count(&b)); + println!("b next item = {:?}", b.tail()); + + if let Some(ref link) = a.tail() { + *link.borrow_mut() = b.clone(); + } + + println!("b rc count after changing a = {}", Rc::strong_count(&b)); + println!("a rc count after changing a = {}", Rc::strong_count(&a)); + + // Uncomment the next line to see that we have a cycle; it will + // overflow the stack + // println!("a next item = {:?}", a.tail()); +} +``` + +Listing 15-17: Creating a reference cycle of two `List` +values pointing to each other + +使用`tail`方法来获取`a`中`RefCell`的引用,并将其放入变量`link`中。接着对`RefCell`使用`borrow_mut`方法将其中的值从存放`Nil`值的`Rc`改为`b`中的`Rc`。这创建了一个看起来像图 15-18 所示的引用循环: + +Reference cycle of lists + +Figure 15-18: A reference cycle of lists `a` and `b` +pointing to each other +