mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 00:43:59 +08:00
wip add ch15-06
This commit is contained in:
parent
e179966c4e
commit
4b082223e0
@ -73,7 +73,7 @@
|
||||
<br>
|
||||
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56</p>
|
||||
</blockquote>
|
||||
<p><strong>内部可变性</strong>(<em>Interior mutability</em>)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用<code>unsafe</code>代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的<code>unsafe</code>代码将被封装进安全的 API 中,而外部类型仍然是不可变的。</p>
|
||||
<p><strong>内部可变性</strong>(<em>Interior mutability</em>)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用<code>unsafe</code>代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的<code>unsafe</code>代码将被封装进安全的 API 中,而外部类型仍然是不可变的。</p>
|
||||
<p>让我们通过遵循内部可变性模式的<code>RefCell<T></code>类型来开始探索。</p>
|
||||
<a class="header" href="#refcellt拥有内部可变性" name="refcellt拥有内部可变性"><h3><code>RefCell<T></code>拥有内部可变性</h3></a>
|
||||
<p>不同于<code>Rc<T></code>,<code>RefCell<T></code>代表其数据的唯一的所有权。那么是什么让<code>RefCell<T></code>不同于像<code>Box<T></code>这样的类型呢?回忆一下第四章所学的借用规则:</p>
|
||||
@ -92,6 +92,113 @@ commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56</p>
|
||||
<p>因为一些分析是不可能的,Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不变,但不会带来灾难。<code>RefCell<T></code>正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。</p>
|
||||
<p>类似于<code>Rc<T></code>,<code>RefCell<T></code>只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用<code>RefCell<T></code>的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用<code>RefCell<T></code>,会得到一个编译错误。</p>
|
||||
<p>对于引用,可以使用<code>&</code>和<code>&mut</code>语法来分别创建不可变和可变的引用。不过对于<code>RefCell<T></code>,我们使用<code>borrow</code>和<code>borrow_mut</code>方法,它是<code>RefCell<T></code>拥有的安全 API 的一部分。<code>borrow</code>返回<code>Ref</code>类型的智能指针,而<code>borrow_mut</code>返回<code>RefMut</code>类型的智能指针。这两个类型实现了<code>Deref</code>所以可以被当作常规引用处理。<code>Ref</code>和<code>RefMut</code>动态的借用所有权,而他们的<code>Drop</code>实现也动态的释放借用。</p>
|
||||
<p>列表 15-14 展示了如何使用<code>RefCell<T></code>来使函数不可变的和可变的借用它的参数。注意<code>data</code>变量使用<code>let data</code>而不是<code>let mut data</code>来声明为不可变的,而<code>a_fn_that_mutably_borrows</code>则允许可变的借用数据并修改它!</p>
|
||||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||||
<pre><code class="language-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<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);
|
||||
}
|
||||
</code></pre>
|
||||
<p><span class="caption">Listing 15-14: Using <code>RefCell<T></code>, <code>borrow</code>, and
|
||||
<code>borrow_mut</code></span></p>
|
||||
<p>这个例子打印出:</p>
|
||||
<pre><code>a is 5
|
||||
a is 6
|
||||
</code></pre>
|
||||
<p>在<code>main</code>函数中,我们新声明了一个包含值 5 的<code>RefCell<T></code>,并储存在变量<code>data</code>中,声明时并没有使用<code>mut</code>关键字。接着使用<code>data</code>的一个不可变引用来调用<code>demo</code>函数:对于<code>main</code>函数而言<code>data</code>是不可变的!</p>
|
||||
<p>在<code>demo</code>函数中,通过调用<code>borrow</code>方法来获取到<code>RefCell<T></code>中值的不可变引用,并使用这个不可变引用调用了<code>a_fn_that_immutably_borrows</code>函数。更为有趣的是,可以通过<code>borrow_mut</code>方法来获取<code>RefCell<T></code>中值的<strong>可变</strong>引用,而<code>a_fn_that_mutably_borrows</code>函数就允许修改这个值。可以看到下一次调用<code>a_fn_that_immutably_borrows</code>时打印出的值是 6 而不是 5。</p>
|
||||
<a class="header" href="#refcellt在运行时检查借用规则" name="refcellt在运行时检查借用规则"><h3><code>RefCell<T></code>在运行时检查借用规则</h3></a>
|
||||
<p>回忆一下第四章因为借用规则,尝试使用常规引用在同一作用域中创建两个可变引用的代码无法编译:</p>
|
||||
<pre><code class="language-rust,ignore">let mut s = String::from("hello");
|
||||
|
||||
let r1 = &mut s;
|
||||
let r2 = &mut s;
|
||||
</code></pre>
|
||||
<p>这会得到一个编译错误:</p>
|
||||
<pre><code>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
|
||||
</code></pre>
|
||||
<p>与此相反,使用<code>RefCell<T></code>并在同一作用域调用两次<code>borrow_mut</code>的代码是<strong>可以</strong>编译的,不过它会在运行时 panic。如下代码:</p>
|
||||
<pre><code class="language-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();
|
||||
}
|
||||
</code></pre>
|
||||
<p>能够编译不过在<code>cargo run</code>运行时会出现如下错误:</p>
|
||||
<pre><code> 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.
|
||||
</code></pre>
|
||||
<p>这个运行时<code>BorrowMutError</code>类似于编译错误:它表明已经可变的借用过<code>s</code>一次了,所以不允许再次借用它。我们并没有绕过借用规则,只是选择让 Rust 在运行时而不是编译时执行他们。你可以选择在任何时候任何地方使用<code>RefCell<T></code>,不过除了不得不编写很多<code>RefCell</code>之外,最终还是可能会发现其中的问题(可能是在生产环境而不是开发环境)。另外,在运行时检查借用规则有性能惩罚。</p>
|
||||
<a class="header" href="#结合rct和refcellt来拥有多个可变数据所有者" name="结合rct和refcellt来拥有多个可变数据所有者"><h3>结合<code>Rc<T></code>和<code>RefCell<T></code>来拥有多个可变数据所有者</h3></a>
|
||||
<p>那么为什么要权衡考虑选择引入<code>RefCell<T></code>呢?好吧,还记得我们说过<code>Rc<T></code>只能拥有一个<code>T</code>的不可变引用吗?考虑到<code>RefCell<T></code>是不可变的,但是拥有内部可变性,可以将<code>Rc<T></code>与<code>RefCell<T></code>结合来创造一个既有引用计数又可变的类型。列表 15-15 展示了一个这么做的例子,再次回到列表 15-5 中的 cons list。在这个例子中,不同于在 cons list 中储存<code>i32</code>值,我们储存一个<code>Rc<RefCell<i32>></code>值。希望储存这个类型是因为其可以拥有不属于列表一部分的这个值的所有者(<code>Rc<T></code>提供的多个所有者功能),而且还可以改变内部的<code>i32</code>值(<code>RefCell<T></code>提供的内部可变性功能):</p>
|
||||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||||
<pre><code class="language-rust">#[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);
|
||||
}
|
||||
</code></pre>
|
||||
<p><span class="caption">Listing 15-15: Using <code>Rc<RefCell<i32>></code> to create a
|
||||
<code>List</code> that we can mutate</span></p>
|
||||
<p>我们创建了一个值,它是<code>Rc<RefCell<i32>></code>的实例。将其储存在变量<code>value</code>中因为我们希望之后能直接访问它。接着在<code>a</code>中创建了一个拥有存放了<code>value</code>值的<code>Cons</code>成员的<code>List</code>,而且<code>value</code>需要被克隆因为我们希望除了<code>a</code>之外还拥有<code>value</code>的所有权。接着将<code>a</code>封装进<code>Rc<T></code>中这样就可以创建都引用<code>a</code>的有着不同开头的列表<code>b</code>和<code>c</code>,类似列表 15-12 中所做的那样。</p>
|
||||
<p>一旦创建了<code>shared_list</code>、<code>b</code>和<code>c</code>,接下来就可以通过解引用<code>Rc<T></code>和对<code>RefCell</code>调用<code>borrow_mut</code>来将 10 与 5 相加了。</p>
|
||||
<p>当打印出<code>shared_list</code>、<code>b</code>和<code>c</code>时,可以看到他们都拥有被修改的值 15:</p>
|
||||
<pre><code>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))
|
||||
</code></pre>
|
||||
<p>这是非常巧妙的!通过使用<code>RefCell<T></code>,我们可以拥有一个表面上不可变的<code>List</code>,不过可以使用<code>RefCell<T></code>中提供内部可变性的方法来在需要时修改数据。<code>RefCell<T></code>的运行时借用规则检查也确实保护我们免于出现数据竞争,而且我们也决定牺牲一些速度来换取数据结构的灵活性。</p>
|
||||
<p><code>RefCell<T></code>并不是标准库中唯一提供内部可变性的类型。<code>Cell<T></code>有点类似,不过不同于<code>RefCell<T></code>那样提供内部值的引用,其值被拷贝进和拷贝出<code>Cell<T></code>。<code>Mutex<T></code>提供线程间安全的内部可变性,下一章并发会讨论它的应用。请查看标准库来获取更多细节和不同类型的区别。</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -67,7 +67,86 @@
|
||||
</div>
|
||||
|
||||
<div id="content" class="content">
|
||||
|
||||
<a class="header" href="#引用循环和内存泄漏是安全的" name="引用循环和内存泄漏是安全的"><h2>引用循环和内存泄漏是安全的</h2></a>
|
||||
<blockquote>
|
||||
<p><a href="https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-06-reference-cycles.md">ch15-06-reference-cycles.md</a>
|
||||
<br>
|
||||
commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894</p>
|
||||
</blockquote>
|
||||
<p>我们讨论过 Rust 做出的一些保证,例如永远也不会遇到一个空值,而且数据竞争也会在编译时被阻止。Rust 的内存安全保证也使其更难以制造从不被清理的内存,这被称为<strong>内存泄露</strong>。然而 Rust 并不是<strong>不可能</strong>出现内存泄漏,避免内存泄露<strong>并</strong>不是 Rust 的保证之一。换句话说,内存泄露是安全的。</p>
|
||||
<p>在使用<code>Rc<T></code>和<code>RefCell<T></code>时,有可能创建循环引用,这时各个项相互引用并形成环。这是不好的因为每一项的引用计数将永远也到不了 0,其值也永远也不会被丢弃。让我们看看这是如何发生的以及如何避免它。</p>
|
||||
<p>在列表 15-16 中,我们将使用列表 15-5 中<code>List</code>定义的另一个变体。我们将回到储存<code>i32</code>值作为<code>Cons</code>成员的第一个元素。现在<code>Cons</code>成员的第二个元素是<code>RefCell<Rc<List>></code>:这时就不能修改<code>i32</code>值了,但是能够修改<code>Cons</code>成员指向的哪个<code>List</code>。还需要增加一个<code>tail</code>方法来方便我们在拥有一个<code>Cons</code>成员时访问第二个项:</p>
|
||||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||||
<pre><code class="language-rust,ignore">#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p><span class="caption">Listing 15-16: A cons list definition that holds a
|
||||
<code>RefCell</code> so that we can modify what a <code>Cons</code> variant is referring to</span></p>
|
||||
<p>接下来,在列表 15-17 中,我们将在变量<code>a</code>中创建一个<code>List</code>值,其内部是一个<code>5, Nil</code>的列表。接着在变量<code>b</code>创建一个值 10 和指向<code>a</code>中列表的<code>List</code>值。最后修改<code>a</code>指向<code>b</code>而不是<code>Nil</code>,这会创建一个循环:</p>
|
||||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||||
<pre><code class="language-rust"># #[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());
|
||||
}
|
||||
</code></pre>
|
||||
<p><span class="caption">Listing 15-17: Creating a reference cycle of two <code>List</code>
|
||||
values pointing to each other</span></p>
|
||||
<p>使用<code>tail</code>方法来获取<code>a</code>中<code>RefCell</code>的引用,并将其放入变量<code>link</code>中。接着对<code>RefCell</code>使用<code>borrow_mut</code>方法将其中的值从存放<code>Nil</code>值的<code>Rc</code>改为<code>b</code>中的<code>Rc</code>。这创建了一个看起来像图 15-18 所示的引用循环:</p>
|
||||
<p><img alt="Reference cycle of lists" src="img/trpl15-04.svg" class="center" style="width: 50%;" /></p>
|
||||
<p><span class="caption">Figure 15-18: A reference cycle of lists <code>a</code> and <code>b</code>
|
||||
pointing to each other</span></p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
188
docs/print.html
188
docs/print.html
@ -8523,7 +8523,7 @@ rc after c goes out of scope = 2
|
||||
<br>
|
||||
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56</p>
|
||||
</blockquote>
|
||||
<p><strong>内部可变性</strong>(<em>Interior mutability</em>)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用<code>unsafe</code>代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的<code>unsafe</code>代码将被封装进安全的 API 中,而外部类型仍然是不可变的。</p>
|
||||
<p><strong>内部可变性</strong>(<em>Interior mutability</em>)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用<code>unsafe</code>代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的<code>unsafe</code>代码将被封装进安全的 API 中,而外部类型仍然是不可变的。</p>
|
||||
<p>让我们通过遵循内部可变性模式的<code>RefCell<T></code>类型来开始探索。</p>
|
||||
<a class="header" href="#refcellt拥有内部可变性" name="refcellt拥有内部可变性"><h3><code>RefCell<T></code>拥有内部可变性</h3></a>
|
||||
<p>不同于<code>Rc<T></code>,<code>RefCell<T></code>代表其数据的唯一的所有权。那么是什么让<code>RefCell<T></code>不同于像<code>Box<T></code>这样的类型呢?回忆一下第四章所学的借用规则:</p>
|
||||
@ -8542,6 +8542,192 @@ commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56</p>
|
||||
<p>因为一些分析是不可能的,Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不变,但不会带来灾难。<code>RefCell<T></code>正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。</p>
|
||||
<p>类似于<code>Rc<T></code>,<code>RefCell<T></code>只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用<code>RefCell<T></code>的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用<code>RefCell<T></code>,会得到一个编译错误。</p>
|
||||
<p>对于引用,可以使用<code>&</code>和<code>&mut</code>语法来分别创建不可变和可变的引用。不过对于<code>RefCell<T></code>,我们使用<code>borrow</code>和<code>borrow_mut</code>方法,它是<code>RefCell<T></code>拥有的安全 API 的一部分。<code>borrow</code>返回<code>Ref</code>类型的智能指针,而<code>borrow_mut</code>返回<code>RefMut</code>类型的智能指针。这两个类型实现了<code>Deref</code>所以可以被当作常规引用处理。<code>Ref</code>和<code>RefMut</code>动态的借用所有权,而他们的<code>Drop</code>实现也动态的释放借用。</p>
|
||||
<p>列表 15-14 展示了如何使用<code>RefCell<T></code>来使函数不可变的和可变的借用它的参数。注意<code>data</code>变量使用<code>let data</code>而不是<code>let mut data</code>来声明为不可变的,而<code>a_fn_that_mutably_borrows</code>则允许可变的借用数据并修改它!</p>
|
||||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||||
<pre><code class="language-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<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);
|
||||
}
|
||||
</code></pre>
|
||||
<p><span class="caption">Listing 15-14: Using <code>RefCell<T></code>, <code>borrow</code>, and
|
||||
<code>borrow_mut</code></span></p>
|
||||
<p>这个例子打印出:</p>
|
||||
<pre><code>a is 5
|
||||
a is 6
|
||||
</code></pre>
|
||||
<p>在<code>main</code>函数中,我们新声明了一个包含值 5 的<code>RefCell<T></code>,并储存在变量<code>data</code>中,声明时并没有使用<code>mut</code>关键字。接着使用<code>data</code>的一个不可变引用来调用<code>demo</code>函数:对于<code>main</code>函数而言<code>data</code>是不可变的!</p>
|
||||
<p>在<code>demo</code>函数中,通过调用<code>borrow</code>方法来获取到<code>RefCell<T></code>中值的不可变引用,并使用这个不可变引用调用了<code>a_fn_that_immutably_borrows</code>函数。更为有趣的是,可以通过<code>borrow_mut</code>方法来获取<code>RefCell<T></code>中值的<strong>可变</strong>引用,而<code>a_fn_that_mutably_borrows</code>函数就允许修改这个值。可以看到下一次调用<code>a_fn_that_immutably_borrows</code>时打印出的值是 6 而不是 5。</p>
|
||||
<a class="header" href="#refcellt在运行时检查借用规则" name="refcellt在运行时检查借用规则"><h3><code>RefCell<T></code>在运行时检查借用规则</h3></a>
|
||||
<p>回忆一下第四章因为借用规则,尝试使用常规引用在同一作用域中创建两个可变引用的代码无法编译:</p>
|
||||
<pre><code class="language-rust,ignore">let mut s = String::from("hello");
|
||||
|
||||
let r1 = &mut s;
|
||||
let r2 = &mut s;
|
||||
</code></pre>
|
||||
<p>这会得到一个编译错误:</p>
|
||||
<pre><code>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
|
||||
</code></pre>
|
||||
<p>与此相反,使用<code>RefCell<T></code>并在同一作用域调用两次<code>borrow_mut</code>的代码是<strong>可以</strong>编译的,不过它会在运行时 panic。如下代码:</p>
|
||||
<pre><code class="language-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();
|
||||
}
|
||||
</code></pre>
|
||||
<p>能够编译不过在<code>cargo run</code>运行时会出现如下错误:</p>
|
||||
<pre><code> 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.
|
||||
</code></pre>
|
||||
<p>这个运行时<code>BorrowMutError</code>类似于编译错误:它表明已经可变的借用过<code>s</code>一次了,所以不允许再次借用它。我们并没有绕过借用规则,只是选择让 Rust 在运行时而不是编译时执行他们。你可以选择在任何时候任何地方使用<code>RefCell<T></code>,不过除了不得不编写很多<code>RefCell</code>之外,最终还是可能会发现其中的问题(可能是在生产环境而不是开发环境)。另外,在运行时检查借用规则有性能惩罚。</p>
|
||||
<a class="header" href="#结合rct和refcellt来拥有多个可变数据所有者" name="结合rct和refcellt来拥有多个可变数据所有者"><h3>结合<code>Rc<T></code>和<code>RefCell<T></code>来拥有多个可变数据所有者</h3></a>
|
||||
<p>那么为什么要权衡考虑选择引入<code>RefCell<T></code>呢?好吧,还记得我们说过<code>Rc<T></code>只能拥有一个<code>T</code>的不可变引用吗?考虑到<code>RefCell<T></code>是不可变的,但是拥有内部可变性,可以将<code>Rc<T></code>与<code>RefCell<T></code>结合来创造一个既有引用计数又可变的类型。列表 15-15 展示了一个这么做的例子,再次回到列表 15-5 中的 cons list。在这个例子中,不同于在 cons list 中储存<code>i32</code>值,我们储存一个<code>Rc<RefCell<i32>></code>值。希望储存这个类型是因为其可以拥有不属于列表一部分的这个值的所有者(<code>Rc<T></code>提供的多个所有者功能),而且还可以改变内部的<code>i32</code>值(<code>RefCell<T></code>提供的内部可变性功能):</p>
|
||||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||||
<pre><code class="language-rust">#[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);
|
||||
}
|
||||
</code></pre>
|
||||
<p><span class="caption">Listing 15-15: Using <code>Rc<RefCell<i32>></code> to create a
|
||||
<code>List</code> that we can mutate</span></p>
|
||||
<p>我们创建了一个值,它是<code>Rc<RefCell<i32>></code>的实例。将其储存在变量<code>value</code>中因为我们希望之后能直接访问它。接着在<code>a</code>中创建了一个拥有存放了<code>value</code>值的<code>Cons</code>成员的<code>List</code>,而且<code>value</code>需要被克隆因为我们希望除了<code>a</code>之外还拥有<code>value</code>的所有权。接着将<code>a</code>封装进<code>Rc<T></code>中这样就可以创建都引用<code>a</code>的有着不同开头的列表<code>b</code>和<code>c</code>,类似列表 15-12 中所做的那样。</p>
|
||||
<p>一旦创建了<code>shared_list</code>、<code>b</code>和<code>c</code>,接下来就可以通过解引用<code>Rc<T></code>和对<code>RefCell</code>调用<code>borrow_mut</code>来将 10 与 5 相加了。</p>
|
||||
<p>当打印出<code>shared_list</code>、<code>b</code>和<code>c</code>时,可以看到他们都拥有被修改的值 15:</p>
|
||||
<pre><code>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))
|
||||
</code></pre>
|
||||
<p>这是非常巧妙的!通过使用<code>RefCell<T></code>,我们可以拥有一个表面上不可变的<code>List</code>,不过可以使用<code>RefCell<T></code>中提供内部可变性的方法来在需要时修改数据。<code>RefCell<T></code>的运行时借用规则检查也确实保护我们免于出现数据竞争,而且我们也决定牺牲一些速度来换取数据结构的灵活性。</p>
|
||||
<p><code>RefCell<T></code>并不是标准库中唯一提供内部可变性的类型。<code>Cell<T></code>有点类似,不过不同于<code>RefCell<T></code>那样提供内部值的引用,其值被拷贝进和拷贝出<code>Cell<T></code>。<code>Mutex<T></code>提供线程间安全的内部可变性,下一章并发会讨论它的应用。请查看标准库来获取更多细节和不同类型的区别。</p>
|
||||
<a class="header" href="#引用循环和内存泄漏是安全的" name="引用循环和内存泄漏是安全的"><h2>引用循环和内存泄漏是安全的</h2></a>
|
||||
<blockquote>
|
||||
<p><a href="https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-06-reference-cycles.md">ch15-06-reference-cycles.md</a>
|
||||
<br>
|
||||
commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894</p>
|
||||
</blockquote>
|
||||
<p>我们讨论过 Rust 做出的一些保证,例如永远也不会遇到一个空值,而且数据竞争也会在编译时被阻止。Rust 的内存安全保证也使其更难以制造从不被清理的内存,这被称为<strong>内存泄露</strong>。然而 Rust 并不是<strong>不可能</strong>出现内存泄漏,避免内存泄露<strong>并</strong>不是 Rust 的保证之一。换句话说,内存泄露是安全的。</p>
|
||||
<p>在使用<code>Rc<T></code>和<code>RefCell<T></code>时,有可能创建循环引用,这时各个项相互引用并形成环。这是不好的因为每一项的引用计数将永远也到不了 0,其值也永远也不会被丢弃。让我们看看这是如何发生的以及如何避免它。</p>
|
||||
<p>在列表 15-16 中,我们将使用列表 15-5 中<code>List</code>定义的另一个变体。我们将回到储存<code>i32</code>值作为<code>Cons</code>成员的第一个元素。现在<code>Cons</code>成员的第二个元素是<code>RefCell<Rc<List>></code>:这时就不能修改<code>i32</code>值了,但是能够修改<code>Cons</code>成员指向的哪个<code>List</code>。还需要增加一个<code>tail</code>方法来方便我们在拥有一个<code>Cons</code>成员时访问第二个项:</p>
|
||||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||||
<pre><code class="language-rust,ignore">#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p><span class="caption">Listing 15-16: A cons list definition that holds a
|
||||
<code>RefCell</code> so that we can modify what a <code>Cons</code> variant is referring to</span></p>
|
||||
<p>接下来,在列表 15-17 中,我们将在变量<code>a</code>中创建一个<code>List</code>值,其内部是一个<code>5, Nil</code>的列表。接着在变量<code>b</code>创建一个值 10 和指向<code>a</code>中列表的<code>List</code>值。最后修改<code>a</code>指向<code>b</code>而不是<code>Nil</code>,这会创建一个循环:</p>
|
||||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||||
<pre><code class="language-rust"># #[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());
|
||||
}
|
||||
</code></pre>
|
||||
<p><span class="caption">Listing 15-17: Creating a reference cycle of two <code>List</code>
|
||||
values pointing to each other</span></p>
|
||||
<p>使用<code>tail</code>方法来获取<code>a</code>中<code>RefCell</code>的引用,并将其放入变量<code>link</code>中。接着对<code>RefCell</code>使用<code>borrow_mut</code>方法将其中的值从存放<code>Nil</code>值的<code>Rc</code>改为<code>b</code>中的<code>Rc</code>。这创建了一个看起来像图 15-18 所示的引用循环:</p>
|
||||
<p><img alt="Reference cycle of lists" src="img/trpl15-04.svg" class="center" style="width: 50%;" /></p>
|
||||
<p><span class="caption">Figure 15-18: A reference cycle of lists <code>a</code> and <code>b</code>
|
||||
pointing to each other</span></p>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
|
||||
|
||||
**内部可变性**(*Interior mutability*)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用`unsafe`代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的`unsafe`代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
|
||||
**内部可变性**(*Interior mutability*)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用`unsafe`代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的`unsafe`代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
|
||||
|
||||
让我们通过遵循内部可变性模式的`RefCell<T>`类型来开始探索。
|
||||
|
||||
@ -27,3 +27,146 @@ Rust 编译器执行的静态分析时天生保守的。代码的一些属性则
|
||||
|
||||
对于引用,可以使用`&`和`&mut`语法来分别创建不可变和可变的引用。不过对于`RefCell<T>`,我们使用`borrow`和`borrow_mut`方法,它是`RefCell<T>`拥有的安全 API 的一部分。`borrow`返回`Ref`类型的智能指针,而`borrow_mut`返回`RefMut`类型的智能指针。这两个类型实现了`Deref`所以可以被当作常规引用处理。`Ref`和`RefMut`动态的借用所有权,而他们的`Drop`实现也动态的释放借用。
|
||||
|
||||
列表 15-14 展示了如何使用`RefCell<T>`来使函数不可变的和可变的借用它的参数。注意`data`变量使用`let data`而不是`let mut data`来声明为不可变的,而`a_fn_that_mutably_borrows`则允许可变的借用数据并修改它!
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```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<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);
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">Listing 15-14: Using `RefCell<T>`, `borrow`, and
|
||||
`borrow_mut`</span>
|
||||
|
||||
这个例子打印出:
|
||||
|
||||
```
|
||||
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>`在运行时检查借用规则
|
||||
|
||||
回忆一下第四章因为借用规则,尝试使用常规引用在同一作用域中创建两个可变引用的代码无法编译:
|
||||
|
||||
```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<T>`并在同一作用域调用两次`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<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>`提供的内部可变性功能):
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
#[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);
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">Listing 15-15: Using `Rc<RefCell<i32>>` to create a
|
||||
`List` that we can mutate</span>
|
||||
|
||||
我们创建了一个值,它是`Rc<RefCell<i32>>`的实例。将其储存在变量`value`中因为我们希望之后能直接访问它。接着在`a`中创建了一个拥有存放了`value`值的`Cons`成员的`List`,而且`value`需要被克隆因为我们希望除了`a`之外还拥有`value`的所有权。接着将`a`封装进`Rc<T>`中这样就可以创建都引用`a`的有着不同开头的列表`b`和`c`,类似列表 15-12 中所做的那样。
|
||||
|
||||
一旦创建了`shared_list`、`b`和`c`,接下来就可以通过解引用`Rc<T>`和对`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<T>`,我们可以拥有一个表面上不可变的`List`,不过可以使用`RefCell<T>`中提供内部可变性的方法来在需要时修改数据。`RefCell<T>`的运行时借用规则检查也确实保护我们免于出现数据竞争,而且我们也决定牺牲一些速度来换取数据结构的灵活性。
|
||||
|
||||
`RefCell<T>`并不是标准库中唯一提供内部可变性的类型。`Cell<T>`有点类似,不过不同于`RefCell<T>`那样提供内部值的引用,其值被拷贝进和拷贝出`Cell<T>`。`Mutex<T>`提供线程间安全的内部可变性,下一章并发会讨论它的应用。请查看标准库来获取更多细节和不同类型的区别。
|
@ -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)
|
||||
> <br>
|
||||
> 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`成员时访问第二个项:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">Listing 15-16: A cons list definition that holds a
|
||||
`RefCell` so that we can modify what a `Cons` variant is referring to</span>
|
||||
|
||||
接下来,在列表 15-17 中,我们将在变量`a`中创建一个`List`值,其内部是一个`5, Nil`的列表。接着在变量`b`创建一个值 10 和指向`a`中列表的`List`值。最后修改`a`指向`b`而不是`Nil`,这会创建一个循环:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
# #[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());
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">Listing 15-17: Creating a reference cycle of two `List`
|
||||
values pointing to each other</span>
|
||||
|
||||
使用`tail`方法来获取`a`中`RefCell`的引用,并将其放入变量`link`中。接着对`RefCell`使用`borrow_mut`方法将其中的值从存放`Nil`值的`Rc`改为`b`中的`Rc`。这创建了一个看起来像图 15-18 所示的引用循环:
|
||||
|
||||
<img alt="Reference cycle of lists" src="img/trpl15-04.svg" class="center" style="width: 50%;" />
|
||||
|
||||
<span class="caption">Figure 15-18: A reference cycle of lists `a` and `b`
|
||||
pointing to each other</span>
|
||||
|
Loading…
Reference in New Issue
Block a user