You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

512 lines
25 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE HTML>
<html lang="zh-CN" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>数据布局和基本操作 - Rust语言圣经(Rust Course)</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../../favicon.svg">
<link rel="shortcut icon" href="../../favicon.png">
<link rel="stylesheet" href="../../css/variables.css">
<link rel="stylesheet" href="../../css/general.css">
<link rel="stylesheet" href="../../css/chrome.css">
<link rel="stylesheet" href="../../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="../../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="../../theme/style.css">
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "../../";
const default_light_theme = "light";
const default_dark_theme = "navy";
</script>
<!-- Start loading toc.js asap -->
<script src="../../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Rust语言圣经(Rust Course)</h1>
<div class="right-buttons">
<a href="../../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/sunface/rust-course" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/sunface/rust-course/edit/main/src/too-many-lists/deque/layout.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="数据布局和构建"><a class="header" href="#数据布局和构建">数据布局和构建</a></h1>
<p>聪明的读者应该已经想到了:让 <code>Rc</code> 可变,就需要使用 <code>RefCell</code> 的配合。关于 <code>RefCell</code> 的一切,在之前的章节都有介绍,还不熟悉的同学请移步<a href="https://course.rs/advance/smart-pointer/cell-refcell.html">这里</a></p>
<p>好了,绝世神兵在手,接下来...我们将见识一个绝世啰嗦的数据结构...如果你来自 GC 语言,那很可能就没有见识过这种阵仗。</p>
<h2 id="数据布局"><a class="header" href="#数据布局">数据布局</a></h2>
<p>双向链表意味着每一个节点将同时指向前一个和下一个节点,因此我们的数据结构可能会变成这样:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use std::rc::Rc;
use std::cell::RefCell;
pub struct List&lt;T&gt; {
head: Link&lt;T&gt;,
tail: Link&lt;T&gt;,
}
type Link&lt;T&gt; = Option&lt;Rc&lt;RefCell&lt;Node&lt;T&gt;&gt;&gt;&gt;;
struct Node&lt;T&gt; {
elem: T,
next: Link&lt;T&gt;,
prev: Link&lt;T&gt;,
}
<span class="boring">}</span></code></pre></pre>
<p>耳听忐忑心怀忐忑尝试编译下竟然顺利通过了thanks god! 接下来再来看看该如何使用它。</p>
<h2 id="构建"><a class="header" href="#构建">构建</a></h2>
<p>如果按照之前的构建方式来构建新的数据结构,会有点笨拙,因此我们先尝试将其拆分:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl&lt;T&gt; Node&lt;T&gt; {
fn new(elem: T) -&gt; Rc&lt;RefCell&lt;Self&gt;&gt; {
Rc::new(RefCell::new(Node {
elem: elem,
prev: None,
next: None,
}))
}
}
impl&lt;T&gt; List&lt;T&gt; {
pub fn new() -&gt; Self {
List { head: None, tail: None }
}
}
<span class="boring">}</span></code></pre></pre>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>&gt; cargo build
**一大堆 DEAD CODE 警告,但是好歹可以成功编译**
<span class="boring">}</span></code></pre></pre>
<h2 id="push"><a class="header" href="#push">Push</a></h2>
<p>很好,再来向链表的头部推入一个元素。由于双向链表的数据结构和操作逻辑明显更加复杂,因此相比单向链表的单行实现,双向链表的 <code>push</code> 操作也要复杂的多。</p>
<p>除此之外,我们还需要处理一些关于空链表的边界问题:对于绝大部分操作而言,可能只需要使用 <code>head</code><code>tail</code> 指针,但是对于空链表,则需要同时使用它们。</p>
<p>一个验证方法 <code>methods</code> 是否有效的办法就是看它是否能保持不变性, 每个节点都应该有两个指针指向它: 中间的节点被它前后的节点所指向,而头部的节点除了被它后面的节点所指向外,还会被链表本身所指向,尾部的节点亦是如此。</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub fn push_front(&amp;mut self, elem: T) {
let new_head = Node::new(elem);
match self.head.take() {
Some(old_head) =&gt; {
// 非空链表,将新的节点跟老的头部相链接
old_head.prev = Some(new_head.clone());
new_head.next = Some(old_head);
self.head = Some(new_head);
}
None =&gt; {
// 空链表,需要设置 tail 和 head
self.tail = Some(new_head.clone());
self.head = Some(new_head);
}
}
}
<span class="boring">}</span></code></pre></pre>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>&gt; cargo build
error[E0609]: no field `prev` on type `std::rc::Rc&lt;std::cell::RefCell&lt;fourth::Node&lt;T&gt;&gt;&gt;`
--&gt; src/fourth.rs:39:26
|
39 | old_head.prev = Some(new_head.clone()); // +1 new_head
| ^^^^ unknown field
error[E0609]: no field `next` on type `std::rc::Rc&lt;std::cell::RefCell&lt;fourth::Node&lt;T&gt;&gt;&gt;`
--&gt; src/fourth.rs:40:26
|
40 | new_head.next = Some(old_head); // +1 old_head
| ^^^^ unknown field
<span class="boring">}</span></code></pre></pre>
<p>虽然有报错,但是一切尽在掌握,今天真是万事顺利啊!</p>
<p>从报错来看,我们无法直接去访问 <code>prev</code><code>next</code>,回想一下 <code>RefCell</code> 的使用方式,修改代码如下:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub fn push_front(&amp;mut self, elem: T) {
let new_head = Node::new(elem);
match self.head.take() {
Some(old_head) =&gt; {
old_head.borrow_mut().prev = Some(new_head.clone());
new_head.borrow_mut().next = Some(old_head);
self.head = Some(new_head);
}
None =&gt; {
self.tail = Some(new_head.clone());
self.head = Some(new_head);
}
}
}
<span class="boring">}</span></code></pre></pre>
<pre><code class="language-shell">$ cargo build
warning: field is never used: `elem`
--&gt; src/fourth.rs:12:5
|
12 | elem: T,
| ^^^^^^^
|
= note: #[warn(dead_code)] on by default
</code></pre>
<p>嘿,我又可以了!既然状态神勇,那就趁热打铁,再来看看 <code>pop</code></p>
<h2 id="pop"><a class="header" href="#pop">Pop</a></h2>
<p>如果说 <code>new</code><code>push</code> 是在构建链表,那 <code>pop</code> 显然就是一个破坏者。</p>
<p>何为完美的破坏?按照构建的过程逆着来一遍就是完美的!</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub fn pop_front(&amp;mut self) -&gt; Option&lt;T&gt; {
self.head.take().map(|old_head| {
match old_head.borrow_mut().next.take() {
Some(new_head) =&gt; {
// 非空链表
new_head.borrow_mut().prev.take();
self.head = Some(new_head);
}
None =&gt; {
// 空链表
self.tail.take();
}
}
old_head.elem
})
}
<span class="boring">}</span></code></pre></pre>
<pre><code class="language-shell">$ cargo build
error[E0609]: no field `elem` on type `std::rc::Rc&lt;std::cell::RefCell&lt;fourth::Node&lt;T&gt;&gt;&gt;`
--&gt; src/fourth.rs:64:22
|
64 | old_head.elem
| ^^^^ unknown field
</code></pre>
<p>哎,怎么就不长记性呢,又是 <code>RefCell</code> 惹的祸:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub fn pop_front(&amp;mut self) -&gt; Option&lt;T&gt; {
self.head.take().map(|old_head| {
match old_head.borrow_mut().next.take() {
Some(new_head) =&gt; {
new_head.borrow_mut().prev.take();
self.head = Some(new_head);
}
None =&gt; {
self.tail.take();
}
}
old_head.borrow_mut().elem
})
}
<span class="boring">}</span></code></pre></pre>
<pre><code class="language-shell">$ cargo build
error[E0507]: cannot move out of borrowed content
--&gt; src/fourth.rs:64:13
|
64 | old_head.borrow_mut().elem
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content
</code></pre>
<p>额... 我凌乱了,看上去 <code>Box</code> 是罪魁祸首,<code>borrow_mut</code> 只能返回一个 <code>&amp;mut Node&lt;T&gt;</code>,因此无法拿走其所有权。</p>
<p>我们需要一个方法来拿走 <code>RefCell&lt;T&gt;</code> 的所有权,然后返回给我们一个 <code>T</code> 翻一翻<a href="https://doc.rust-lang.org/std/cell/struct.RefCell.html">文档</a>,可以发现下面这段内容:</p>
<blockquote>
<p><code>fn into_inner(self) -&gt; T</code></p>
</blockquote>
<blockquote>
<p>消费掉 RefCell 并返回内部的值</p>
</blockquote>
<p>喔,看上去好有安全感的方法:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>old_head.into_inner().elem
<span class="boring">}</span></code></pre></pre>
<pre><code class="language-shell">$ cargo build
error[E0507]: cannot move out of an `Rc`
--&gt; src/fourth.rs:64:13
|
64 | old_head.into_inner().elem
| ^^^^^^^^ cannot move out of an `Rc`
</code></pre>
<p>...看走眼了,没想到你浓眉大眼也会耍花枪。 <code>into_inner</code> 想要拿走 <code>RecCell</code> 的所有权,但是还有一个 <code>Rc</code> 不愿意,因为 <code>Rc&lt;T&gt;</code> 只能让我们获取内部值的不可变引用。</p>
<p>大家还记得我们之前实现 <code>Drop</code> 时用过的方法吗?在这里一样适用:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Rc::try_unwrap(old_head).unwrap().into_inner().elem
<span class="boring">}</span></code></pre></pre>
<p><code>Rc::try_unwrap</code> 返回一个 <code>Result</code>,由于我们不关心 <code>Err</code> 的情况( 如果代码合理,这里不会是 <code>Err</code> ),直接使用 <code>unwrap</code> 即可。</p>
<pre><code class="language-shell">$ cargo build
error[E0599]: no method named `unwrap` found for type `std::result::Result&lt;std::cell::RefCell&lt;fourth::Node&lt;T&gt;&gt;, std::rc::Rc&lt;std::cell::RefCell&lt;fourth::Node&lt;T&gt;&gt;&gt;&gt;` in the current scope
--&gt; src/fourth.rs:64:38
|
64 | Rc::try_unwrap(old_head).unwrap().into_inner().elem
| ^^^^^^
|
= note: the method `unwrap` exists but the following trait bounds were not satisfied:
`std::rc::Rc&lt;std::cell::RefCell&lt;fourth::Node&lt;T&gt;&gt;&gt; : std::fmt::Debug`
</code></pre>
<p>额,<code>unwrap</code> 要求目标类型是实现了 <code>Debug</code> 的,这样才能在报错时提供 <code>debug</code> 输出,而 <code>RefCell&lt;T&gt;</code> 要实现 <code>Debug</code> 需要它内部的 <code>T</code> 实现 <code>Debug</code>,而我们的 <code>Node</code> 并没有实现。</p>
<p>当然,我们可以选择为 <code>Node</code> 实现,也可以这么做:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Rc::try_unwrap(old_head).ok().unwrap().into_inner().elem
<span class="boring">}</span></code></pre></pre>
<pre><code class="language-shell">$ cargo build
</code></pre>
<p>终于成功的运行了,下面依然是惯例 - 写几个测试用例 :</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[cfg(test)]
mod test {
use super::List;
#[test]
fn basics() {
let mut list = List::new();
// Check empty list behaves right
assert_eq!(list.pop_front(), None);
// Populate list
list.push_front(1);
list.push_front(2);
list.push_front(3);
// Check normal removal
assert_eq!(list.pop_front(), Some(3));
assert_eq!(list.pop_front(), Some(2));
// Push some more just to make sure nothing's corrupted
list.push_front(4);
list.push_front(5);
// Check normal removal
assert_eq!(list.pop_front(), Some(5));
assert_eq!(list.pop_front(), Some(4));
// Check exhaustion
assert_eq!(list.pop_front(), Some(1));
assert_eq!(list.pop_front(), None);
}
}
<span class="boring">}</span></code></pre></pre>
<pre><code class="language-shell">$ cargo test
Running target/debug/lists-5c71138492ad4b4a
running 9 tests
test first::test::basics ... ok
test fourth::test::basics ... ok
test second::test::iter_mut ... ok
test second::test::basics ... ok
test fifth::test::iter_mut ... ok
test third::test::basics ... ok
test second::test::iter ... ok
test third::test::iter ... ok
test second::test::into_iter ... ok
test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured
</code></pre>
<h2 id="drop"><a class="header" href="#drop">Drop</a></h2>
<p><a href="">循环引用章节</a>,我们介绍过 <code>Rc</code> 最怕的就是引用形成循环,而双向链表恰恰如此。因此,当使用默认的实现来 <code>drop</code> 我们的链表时,两个节点会将各自的引用计数减少到 1 然后就不会继续减少,最终造成内存泄漏。</p>
<p>所以,这里最好的实现就是将每个节点 <code>pop</code> 出去,直到获得 <code>None</code>:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl&lt;T&gt; Drop for List&lt;T&gt; {
fn drop(&amp;mut self) {
while self.pop_front().is_some() {}
}
}
<span class="boring">}</span></code></pre></pre>
<p>细心的读者可能已经注意到,我们还未实现在链表尾部 <code>push</code><code>pop</code> 的操作,但由于所需的实现跟之前差别不大,因此我们会在后面直接给出,下面先来看看更有趣的。</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../too-many-lists/deque/intro.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../too-many-lists/deque/peek.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../too-many-lists/deque/intro.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../too-many-lists/deque/peek.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../../ace.js"></script>
<script src="../../editor.js"></script>
<script src="../../mode-rust.js"></script>
<script src="../../theme-dawn.js"></script>
<script src="../../theme-tomorrow_night.js"></script>
<script src="../../elasticlunr.min.js"></script>
<script src="../../mark.min.js"></script>
<script src="../../searcher.js"></script>
<script src="../../clipboard.min.js"></script>
<script src="../../highlight.js"></script>
<script src="../../book.js"></script>
<!-- Custom JS scripts -->
<script src="../../assets/custom2.js"></script>
<script src="../../assets/bigPicture.js"></script>
</div>
</body>
</html>