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.

391 lines
22 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>newtype 和 类型别名 - 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/advance/into-types/custom-type.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="深入-rust-类型"><a class="header" href="#深入-rust-类型">深入 Rust 类型</a></h1>
<p>弱弱地、不负责任地说Rust 的学习难度之恶名,可能有一半来源于 Rust 的类型系统,而其中一半的一半则来自于本章节的内容。在本章,我们将重点学习如何创建自定义类型,以及了解何为动态大小的类型。</p>
<h2 id="newtype"><a class="header" href="#newtype">newtype</a></h2>
<p>何为 <code>newtype</code>?简单来说,就是使用<a href="https://course.rs/basic/compound-type/struct.html#%E5%85%83%E7%BB%84%E7%BB%93%E6%9E%84%E4%BD%93tuple-struct">元组结构体</a>的方式将已有的类型包裹起来:<code>struct Meters(u32);</code>,那么此处 <code>Meters</code> 就是一个 <code>newtype</code></p>
<p>为何需要 <code>newtype</code>Rust 这多如繁星的 Old 类型满足不了我们吗?这是因为:</p>
<ul>
<li>自定义类型可以让我们给出更有意义和可读性的类型名,例如与其使用 <code>u32</code> 作为距离的单位类型,我们可以使用 <code>Meters</code>,它的可读性要好得多</li>
<li>对于某些场景,只有 <code>newtype</code> 可以很好地解决</li>
<li>隐藏内部类型的细节</li>
</ul>
<p>一箩筐的理由~~ 让我们先从第二点讲起。</p>
<h4 id="为外部类型实现外部特征"><a class="header" href="#为外部类型实现外部特征">为外部类型实现外部特征</a></h4>
<p>在之前的章节中,我们有讲过,如果在外部类型上实现外部特征必须使用 <code>newtype</code> 的方式,否则你就得遵循孤儿规则:要为类型 <code>A</code> 实现特征 <code>T</code>,那么 <code>A</code> 或者 <code>T</code> 必须至少有一个在当前的作用范围内。</p>
<p>例如,如果想使用 <code>println!("{}", v)</code> 的方式去格式化输出一个动态数组 <code>Vec</code>,以期给用户提供更加清晰可读的内容,那么就需要为 <code>Vec</code> 实现 <code>Display</code> 特征,但是这里有一个问题: <code>Vec</code> 类型定义在标准库中,<code>Display</code> 亦然,这时就可以祭出大杀器 <code>newtype</code> 来解决:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fmt;
struct Wrapper(Vec&lt;String&gt;);
impl fmt::Display for Wrapper {
fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}</code></pre></pre>
<p>如上所示,使用元组结构体语法 <code>struct Wrapper(Vec&lt;String&gt;)</code> 创建了一个 <code>newtype</code> Wrapper然后为它实现 <code>Display</code> 特征,最终实现了对 <code>Vec</code> 动态数组的格式化输出。</p>
<h4 id="更好的可读性及类型异化"><a class="header" href="#更好的可读性及类型异化">更好的可读性及类型异化</a></h4>
<p>首先,更好的可读性不等于更少的代码(如果你学过 Scala相信会深有体会其次下面的例子只是一个示例未必能体现出更好的可读性</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::ops::Add;
use std::fmt;
struct Meters(u32);
impl fmt::Display for Meters {
fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {
write!(f, "目标地点距离你{}米", self.0)
}
}
impl Add for Meters {
type Output = Self;
fn add(self, other: Meters) -&gt; Self {
Self(self.0 + other.0)
}
}
fn main() {
let d = calculate_distance(Meters(10), Meters(20));
println!("{}", d);
}
fn calculate_distance(d1: Meters, d2: Meters) -&gt; Meters {
d1 + d2
}</code></pre></pre>
<p>上面代码创建了一个 <code>newtype</code> Meters为其实现 <code>Display</code><code>Add</code> 特征,接着对两个距离进行求和计算,最终打印出该距离:</p>
<pre><code class="language-console">目标地点距离你30米
</code></pre>
<p>事实上,除了可读性外,还有一个极大的优点:如果给 <code>calculate_distance</code> 传一个其它的类型,例如 <code>struct MilliMeters(u32);</code>,该代码将无法编译。尽管 <code>Meters</code><code>MilliMeters</code> 都是对 <code>u32</code> 类型的简单包装,但是<strong>它们是不同的类型</strong></p>
<h4 id="隐藏内部类型的细节"><a class="header" href="#隐藏内部类型的细节">隐藏内部类型的细节</a></h4>
<p>众所周知Rust 的类型有很多自定义的方法,假如我们把某个类型传给了用户,但是又不想用户调用这些方法,就可以使用 <code>newtype</code></p>
<pre><pre class="playground"><code class="language-rust edition2021">struct Meters(u32);
fn main() {
let i: u32 = 2;
assert_eq!(i.pow(2), 4);
let n = Meters(i);
// 下面的代码将报错,因为`Meters`类型上没有`pow`方法
// assert_eq!(n.pow(2), 4);
}</code></pre></pre>
<p>不过需要偷偷告诉你的是,这种方式实际上是掩耳盗铃,因为用户依然可以通过 <code>n.0.pow(2)</code> 的方式来调用内部类型的方法 :)</p>
<h2 id="类型别名type-alias"><a class="header" href="#类型别名type-alias">类型别名(Type Alias)</a></h2>
<p>除了使用 <code>newtype</code>,我们还可以使用一个更传统的方式来创建新类型:类型别名</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>type Meters = u32
<span class="boring">}</span></code></pre></pre>
<p>嗯,不得不说,类型别名的方式看起来比 <code>newtype</code> 顺眼的多,而且跟其它语言的使用方式几乎一致,但是:
<strong>类型别名并不是一个独立的全新的类型,而是某一个类型的别名</strong>,因此编译器依然会把 <code>Meters</code><code>u32</code> 来使用:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>type Meters = u32;
let x: u32 = 5;
let y: Meters = 5;
println!("x + y = {}", x + y);
<span class="boring">}</span></code></pre></pre>
<p>上面的代码将顺利编译通过,但是如果你使用 <code>newtype</code> 模式,该代码将无情报错,简单做个总结:</p>
<ul>
<li>类型别名仅仅是别名,只是为了让可读性更好,并不是全新的类型,<code>newtype</code> 才是!</li>
<li>类型别名无法实现<em>为外部类型实现外部特征</em>等功能,而 <code>newtype</code> 可以</li>
</ul>
<p>类型别名除了让类型可读性更好,还能<strong>减少模版代码的使用</strong></p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let f: Box&lt;dyn Fn() + Send + 'static&gt; = Box::new(|| println!("hi"));
fn takes_long_type(f: Box&lt;dyn Fn() + Send + 'static&gt;) {
// --snip--
}
fn returns_long_type() -&gt; Box&lt;dyn Fn() + Send + 'static&gt; {
// --snip--
}
<span class="boring">}</span></code></pre></pre>
<p><code>f</code> 是一个令人眼花缭乱的类型 <code>Box&lt;dyn Fn() + Send + 'static&gt;</code>,如果仔细看,会发现其实只有一个 <code>Send</code> 特征不认识,<code>Send</code> 是什么在这里不重要,你只需理解,<code>f</code> 就是一个 <code>Box&lt;dyn T&gt;</code> 类型的特征对象,实现了 <code>Fn()</code><code> Send</code> 特征,同时生命周期为 <code>'static</code></p>
<p>因为 <code>f</code> 的类型贼长,导致了后面我们在使用它时,到处都充斥这些不太优美的类型标注,好在类型别名可解君忧:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>type Thunk = Box&lt;dyn Fn() + Send + 'static&gt;;
let f: Thunk = Box::new(|| println!("hi"));
fn takes_long_type(f: Thunk) {
// --snip--
}
fn returns_long_type() -&gt; Thunk {
// --snip--
}
<span class="boring">}</span></code></pre></pre>
<p>Bang是不是立刻大幅简化了我们的使用。喝着奶茶、哼着歌、我写起代码撩起妹何其快哉</p>
<p>在标准库中,类型别名应用最广的就是简化 <code>Result&lt;T, E&gt;</code> 枚举。</p>
<p>例如在 <code>std::io</code> 库中,它定义了自己的 <code>Error</code> 类型:<code>std::io::Error</code>,那么如果要使用该 <code>Result</code> 就要用这样的语法:<code>std::result::Result&lt;T, std::io::Error&gt;;</code>,想象一下代码中充斥着这样的东东是一种什么感受?颤抖吧。。。</p>
<p>由于使用 <code>std::io</code> 库时,它的所有错误类型都是 <code>std::io::Error</code>,那么我们完全可以把该错误对用户隐藏起来,只在内部使用即可,因此就可以使用类型别名来简化实现:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>type Result&lt;T&gt; = std::result::Result&lt;T, std::io::Error&gt;;
<span class="boring">}</span></code></pre></pre>
<p>Bingo这样一来其它库只需要使用 <code>std::io::Result&lt;T&gt;</code> 即可替代冗长的 <code>std::result::Result&lt;T, std::io::Error&gt;</code> 类型。</p>
<p>更香的是,由于它只是别名,因此我们可以用它来调用真实类型的所有方法,甚至包括 <code>?</code> 符号!</p>
<h2 id="永不返回类型"><a class="header" href="#永不返回类型">!永不返回类型</a></h2>
<p><a href="https://course.rs/basic/base-type/function.html#%E6%B0%B8%E4%B8%8D%E8%BF%94%E5%9B%9E%E7%9A%84%E5%87%BD%E6%95%B0">函数</a>那章,曾经介绍过 <code>!</code> 类型:<code>!</code> 用来说明一个函数永不返回任何值,当时可能体会不深,没事,在学习了更多手法后,保证你有全新的体验:</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
let i = 2;
let v = match i {
0..=3 =&gt; i,
_ =&gt; println!("不合规定的值:{}", i)
};
}</code></pre></pre>
<p>上面函数,会报出一个编译错误:</p>
<pre><code class="language-console">error[E0308]: `match` arms have incompatible types // match的分支类型不同
--&gt; src/main.rs:5:13
|
3 | let v = match i {
| _____________-
4 | | 0..3 =&gt; i,
| | - this is found to be of type `{integer}` // 该分支返回整数类型
5 | | _ =&gt; println!("不合规定的值:{}", i)
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected integer, found `()` // 该分支返回()单元类型
6 | | };
| |_____- `match` arms have incompatible types
</code></pre>
<p>原因很简单: 要赋值给 <code>v</code>,就必须保证 <code>match</code> 的各个分支返回的值是同一个类型,但是上面一个分支返回数值、另一个分支返回元类型 <code>()</code>,自然会出错。</p>
<p>既然 <code>println</code> 不行,那再试试 <code>panic</code></p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
let i = 2;
let v = match i {
0..=3 =&gt; i,
_ =&gt; panic!("不合规定的值:{}", i)
};
}</code></pre></pre>
<p>神奇的事发生了,此处 <code>panic</code> 竟然通过了编译。难道这两个宏拥有不同的返回类型?</p>
<p>你猜的没错:<code>panic</code> 的返回值是 <code>!</code>,代表它决不会返回任何值,既然没有任何返回值,那自然不会存在分支类型不匹配的情况。</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../advance/into-types/converse.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="../../advance/into-types/sized.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="../../advance/into-types/converse.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="../../advance/into-types/sized.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>