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.

380 lines
21 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/production-unsafe-deque/variance-and-phantomData.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="variance-and-phantomdata"><a class="header" href="#variance-and-phantomdata">Variance and PhantomData</a></h1>
<p>如果现在不做,等以后再修,会很麻烦,所以我们现在要做的是硬核布局。</p>
<p>建造 Rust collections 时,有这五个可怕的难题:</p>
<ol>
<li><a href="https://doc.rust-lang.org/nightly/nomicon/subtyping.html">Variance</a></li>
<li><a href="https://doc.rust-lang.org/nightly/nomicon/dropck.html">Drop Check</a></li>
<li><a href="https://doc.rust-lang.org/nightly/std/ptr/struct.NonNull.html">NonNull Optimizations</a></li>
<li><a href="https://doc.rust-lang.org/nightly/nomicon/vec/vec-alloc.html">The isize::MAX Allocation Rule</a></li>
<li><a href="https://doc.rust-lang.org/nightly/nomicon/vec/vec-zsts.html">Zero-Sized Types</a></li>
</ol>
<p>幸好后面2个对我们来说都不是问题。</p>
<p>我们可以把第三个问题变成我们的问题,但这带来的麻烦比它的价值更多。</p>
<p>第二个问题是我以前一直坚持认为非常重要的std 也会乱用它,但默认值是安全的,而且你需要非常努力才能注意到默认值的限制,所以不用担心这个问题。</p>
<p>所以只剩下了 Variance(型变)。</p>
<p>Rust 有子类型了。通常,<code>&amp;'big T</code><code>&amp;'small T</code> 的子类型。因为如果某些代码需要在程序的某个特定区域存活的引用,那么通常完全可以给它一个存在<em>时间更长的</em>引用。直觉上这是正确的,对吧?</p>
<p>为什么这很重要?想象一下,一些代码采用两个具有相同类型的值:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn take_two&lt;T&gt;(_val1: T, _val2: T) { }
<span class="boring">}</span></code></pre></pre>
<p>这是一些非常无聊的代码,并且我们期望它能够很好地与 T=&amp;u32 一起使用,对吧?</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn two_refs&lt;'big: 'small, 'small&gt;(
big: &amp;'big u32,
small: &amp;'small u32,
) {
take_two(big, small);
}
fn take_two&lt;T&gt;(_val1: T, _val2: T) { }
<span class="boring">}</span></code></pre></pre>
<p>是的,编译得很好!</p>
<p>现在让我们找点乐子,把它包起来:<code>std::cell::Cell</code></p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use std::cell::Cell;
fn two_refs&lt;'big: 'small, 'small&gt;(
// NOTE: these two lines changed
big: Cell&lt;&amp;'big u32&gt;,
small: Cell&lt;&amp;'small u32&gt;,
) {
take_two(big, small);
}
fn take_two&lt;T&gt;(_val1: T, _val2: T) { }
error[E0623]: lifetime mismatch
--&gt; src/main.rs:7:19
|
4 | big: Cell&lt;&amp;'big u32&gt;,
| ---------
5 | small: Cell&lt;&amp;'small u32&gt;,
| ----------- these two types are declared with different lifetimes...
6 | ) {
7 | take_two(big, small);
| ^^^^^ ...but data from `small` flows into `big` here
<span class="boring">}</span></code></pre></pre>
<p>哼???我们没有碰过生命周期,为什么编译器现在生气了!?</p>
<p>啊,好吧,生命周期的“子类型”必须非常简单,所以如果你将引用包装在任何东西中,它就会被破坏,看看 Vec</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn two_refs&lt;'big: 'small, 'small&gt;(
big: Vec&lt;&amp;'big u32&gt;,
small: Vec&lt;&amp;'small u32&gt;,
) {
take_two(big, small);
}
fn take_two&lt;T&gt;(_val1: T, _val2: T) { }
Finished dev [unoptimized + debuginfo] target(s) in 1.07s
Running `target/debug/playground`
<span class="boring">}</span></code></pre></pre>
<p>看到它没有编译成功 ——等等???Vec是魔术??????</p>
<p>是的。这种魔力就是✨<em>Variance</em>✨。</p>
<p>如果您想要所有细节,请阅读 <a href="https://doc.rust-lang.org/nightly/nomicon/subtyping.html">nomicon 关于子类型的章节</a>,但基本上子类型<em>并不总是</em>安全的。特别是,当涉及可变引用时,它就更不安全了,。因为你可能会使用诸如<code>mem::swap</code>的东西,突然哎呀,悬空指针!</p>
<p>可变引用是 <em>invariant(不变的)</em>,这意味着它们会阻止对泛型参数子类型化。因此,为了安全起见, <code>&amp;mut T</code> 在 T 上是不变的,并且 <code>Cell&lt;T&gt;</code> 在 T 上也是不变的(因为内部可变性),因为 <code>&amp;Cell&lt;T&gt;</code> 本质上就像 <code>&amp;mut T</code></p>
<p>几乎所有不是 <em>invariant</em> 的东西都是 <em>covariant(协变的)</em> ,这意味着子类型可以正常工作(也有 <em>contravariant(逆变的)</em> 的类型使子类型倒退,但它们真的很少见,没有人喜欢它们,所以我不会再提到它们)。</p>
<p>集合通常包含指向其数据的可变指针,因此你可能希望它们也是不变的,但事实上,它们并不需要不变!由于 Rust 的所有权系统,<code>Vec&lt;T&gt;</code> 在语义上等同于 <code>T</code>,这意味着它可以安全地保持<em>covariant(协变的)</em> </p>
<p>不幸的的是,下面的定义是 <em>invariant(不变的)</em>:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct LinkedList&lt;T&gt; {
front: Link&lt;T&gt;,
back: Link&lt;T&gt;,
len: usize,
}
type Link&lt;T&gt; = *mut Node&lt;T&gt;;
struct Node&lt;T&gt; {
front: Link&lt;T&gt;,
back: Link&lt;T&gt;,
elem: T,
}
<span class="boring">}</span></code></pre></pre>
<p>所以我们的类型定义中哪里惹 Rust 编译器不高兴了? <code>*mut</code></p>
<p>Rust 中的裸指针其实就是让你可以做任何事情,但它们只有一个安全特性:因为大多数人都不知道 Rust 中还有 <em>Variance(型变)</em> 和子类型,而错误地使用 <em>covariant(协变的)</em> 会非常危险,所以 <code>*mut T</code><em>invariant(不变的)</em>,因为它很有可能被 "作为" <code>&amp;mut T</code> 使用。</p>
<p>作为一个花了大量时间在 Rust 中编写集合的人,这让我感到厌烦。这就是为什么我在制作 <a href="https://doc.rust-lang.org/std/ptr/struct.NonNull.html">std::ptr::NonNull</a>, 时添加了这个小魔法:</p>
<blockquote>
<p><em>mut T 不同,<code>NonNull&lt;T&gt;</code> 在 T 上是 <em>covariant(协变的)</em>。这使得使用 <code>NonNull&lt;T&gt;</code> 构建</em>covariant(协变的)*类型成为可能,但如果在不应该是 <em>covariant(协变的)</em> 的地方中使用,则会带来不健全的风险。</p>
</blockquote>
<p>这是一个围绕着 <code>*mut T</code> 构建的类型。真的是魔法吗?让我们来看一下:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct NonNull&lt;T&gt; {
pointer: *const T,
}
impl&lt;T&gt; NonNull&lt;T&gt; {
pub unsafe fn new_unchecked(ptr: *mut T) -&gt; Self {
// SAFETY: the caller must guarantee that `ptr` is non-null.
unsafe { NonNull { pointer: ptr as *const T } }
}
}
<span class="boring">}</span></code></pre></pre>
<p>这里没有魔法NonNull 只是滥用了 *const T 是 <em>covariant(协变的)</em> 这一事实,并将其存储起来。这就是 Rust 中集合的协变方式!这可真是惨不忍睹!所以我为你做了这个 Good Pointer Type !不客气好好享受子类型吧</p>
<p>解决你所有问题的办法就是使用 NonNull然后如果你想再次使用可空指针就使用 <code>Option&lt;NonNull&lt;T&gt;&gt;</code>。我们真的要这么做吗?</p>
<p>是的!这很糟糕,但我们要做的是生产级的链表,所以我们要吃尽千辛万苦(我们可以直接使用裸*const T然后在任何地方都进行转换但我真的想看看这样做有多痛苦......为了人体工程学科学)。</p>
<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::ptr::NonNull;
// !!!This changed!!!
pub struct LinkedList&lt;T&gt; {
front: Link&lt;T&gt;,
back: Link&lt;T&gt;,
len: usize,
}
type Link&lt;T&gt; = Option&lt;NonNull&lt;Node&lt;T&gt;&gt;&gt;;
struct Node&lt;T&gt; {
front: Link&lt;T&gt;,
back: Link&lt;T&gt;,
elem: T,
}
<span class="boring">}</span></code></pre></pre>
<p>...等等,不,最后一件事。每当你使用裸指针时,你都应该添加一个 Ghost 来保护你的指针:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use std::marker::PhantomData;
pub struct LinkedList&lt;T&gt; {
front: Link&lt;T&gt;,
back: Link&lt;T&gt;,
len: usize,
/// We semantically store values of T by-value.
_boo: PhantomData&lt;T&gt;,
}
<span class="boring">}</span></code></pre></pre>
<p>在这种情况下,我认为我们<em>实际上</em>不需要 <a href="https://doc.rust-lang.org/std/marker/struct.PhantomData.html">PhantomData</a>,但每当你使用 NonNull或一般的裸指针为了安全起见你都应该始终添加它并向编译器和其他人清楚地表明你的想法你在做什么。</p>
<p>PhantomData 是我们给编译器提供一个额外的 "示例 "字段的方法,这个字段在概念上存在于你的类型中,但由于各种原因(间接、类型擦除......)并不存在。在本例中,我们使用 NonNull 是因为我们声称我们的类型 "好像 "存储了一个值 T所以我们添加了一个 PhantomData 来明确这一点。</p>
<p>...好吧,我们现在已经完成了布局!进入实际的基本功能!</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../too-many-lists/production-unsafe-deque/layout.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/production-unsafe-deque/basics.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/production-unsafe-deque/layout.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/production-unsafe-deque/basics.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>