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.
trpl-zh-cn/docs/ch15-02-deref.html

198 lines
20 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="en">
<head>
<meta charset="UTF-8">
<title>`Deref` Trait 允许通过引用访问数据 - Rust 程序设计语言 简体中文版</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="Rust 程序设计语言 简体中文版">
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="">
<link rel="stylesheet" href="book.css">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
<link rel="shortcut icon" href="favicon.png">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<!-- MathJax -->
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Fetch JQuery from CDN but have a local fallback -->
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
if (typeof jQuery == 'undefined') {
document.write(unescape("%3Cscript src='jquery.js'%3E%3C/script%3E"));
}
</script>
</head>
<body class="light">
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme = localStorage.getItem('theme');
if (theme == null) { theme = 'light'; }
$('body').removeClass().addClass(theme);
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var sidebar = localStorage.getItem('sidebar');
if (sidebar === "hidden") { $("html").addClass("sidebar-hidden") }
else if (sidebar === "visible") { $("html").addClass("sidebar-visible") }
</script>
<div id="sidebar" class="sidebar">
<ul class="chapter"><li><a href="ch01-00-introduction.html"><strong>1.</strong> 介绍</a></li><li><ul class="section"><li><a href="ch01-01-installation.html"><strong>1.1.</strong> 安装</a></li><li><a href="ch01-02-hello-world.html"><strong>1.2.</strong> Hello, World!</a></li></ul></li><li><a href="ch02-00-guessing-game-tutorial.html"><strong>2.</strong> 猜猜看教程</a></li><li><a href="ch03-00-common-programming-concepts.html"><strong>3.</strong> 通用编程概念</a></li><li><ul class="section"><li><a href="ch03-01-variables-and-mutability.html"><strong>3.1.</strong> 变量和可变性</a></li><li><a href="ch03-02-data-types.html"><strong>3.2.</strong> 数据类型</a></li><li><a href="ch03-03-how-functions-work.html"><strong>3.3.</strong> 函数如何工作</a></li><li><a href="ch03-04-comments.html"><strong>3.4.</strong> 注释</a></li><li><a href="ch03-05-control-flow.html"><strong>3.5.</strong> 控制流</a></li></ul></li><li><a href="ch04-00-understanding-ownership.html"><strong>4.</strong> 认识所有权</a></li><li><ul class="section"><li><a href="ch04-01-what-is-ownership.html"><strong>4.1.</strong> 什么是所有权</a></li><li><a href="ch04-02-references-and-borrowing.html"><strong>4.2.</strong> 引用 &amp; 借用</a></li><li><a href="ch04-03-slices.html"><strong>4.3.</strong> Slices</a></li></ul></li><li><a href="ch05-00-structs.html"><strong>5.</strong> 结构体</a></li><li><ul class="section"><li><a href="ch05-01-method-syntax.html"><strong>5.1.</strong> 方法语法</a></li></ul></li><li><a href="ch06-00-enums.html"><strong>6.</strong> 枚举和模式匹配</a></li><li><ul class="section"><li><a href="ch06-01-defining-an-enum.html"><strong>6.1.</strong> 定义枚举</a></li><li><a href="ch06-02-match.html"><strong>6.2.</strong> <code>match</code>控制流运算符</a></li><li><a href="ch06-03-if-let.html"><strong>6.3.</strong> <code>if let</code>简单控制流</a></li></ul></li><li><a href="ch07-00-modules.html"><strong>7.</strong> 模块</a></li><li><ul class="section"><li><a href="ch07-01-mod-and-the-filesystem.html"><strong>7.1.</strong> <code>mod</code>和文件系统</a></li><li><a href="ch07-02-controlling-visibility-with-pub.html"><strong>7.2.</strong> 使用<code>pub</code>控制可见性</a></li><li><a href="ch07-03-importing-names-with-use.html"><strong>7.3.</strong> 使用<code>use</code>导入命名</a></li></ul></li><li><a href="ch08-00-common-collections.html"><strong>8.</strong> 通用集合类型</a></li><li><ul class="section"><li><a href="ch08-01-vectors.html"><strong>8.1.</strong> vector</a></li><li><a href="ch08-02-strings.html"><strong>8.2.</strong> 字符串</a></li><li><a href="ch08-03-hash-maps.html"><strong>8.3.</strong> 哈希 map</a></li></ul></li><li><a href="ch09-00-error-handling.html"><strong>9.</strong> 错误处理</a></li><li><ul class="section"><li><a href="ch09-01-unrecoverable-errors-with-panic.html"><strong>9.1.</strong> <code>panic!</code>与不可恢复的错误</a></li><li><a href="ch09-02-recoverable-errors-with-result.html"><strong>9.2.</strong> <code>Result</code>与可恢复的错误</a></li><li><a href="ch09-03-to-panic-or-not-to-panic.html"><strong>9.3.</strong> <code>panic!</code>还是不<code>panic!</code></a></li></ul></li><li><a href="ch10-00-generics.html"><strong>10.</strong> 泛型、trait 和生命周期</a></li><li><ul class="section"><li><a href="ch10-01-syntax.html"><strong>10.1.</strong> 泛型数据类型</a></li><li><a href="ch10-02-traits.html"><strong>10.2.</strong> trait定义共享的行为</a></li><li><a href="ch10-03-lifetime-syntax.html"><strong>10.3.</strong> 生命周期与引用有效性</a></li></ul></li><li><a href="ch11-00-testing.html"><strong>11.</strong> 测试</a></li><li><ul class="section"><li><a href="ch11-01-writing-tests.html"><strong>11.1.</strong> 编写测试</a></li><li><a href="ch11-02-running-tests.html"><strong>11.2.</strong> 运行测试</a></li><li><a href="ch11-03-test-organization.html"><strong>11.3.</strong> 测试的组织结构</a></li></ul></li><li><a href="ch12-00-an-io-project.html"><strong>12.</strong> 一个 I/O 项目</a></li><li><ul class="section"><li><a href="ch12-01-accepting-command-line-arguments.html"><strong>12.1.</strong> 接受命令行参数</a></li><li><a href="ch12-02-reading-a-file.html"><strong>12.2.</strong> 读取文件</a></li><li><a href="ch12-03-improving-error-handling-and-modularity.html"><strong>12.3.</strong> 增强错误处理和模块化</a></li><li><a href="ch12-04-testing-the-librarys-functionality.html"><strong>12.4.</strong> 测试库的功能</a></li><li><a href="ch12-05-working-with-environment-variables.html"><strong>12.5.</strong> 处理环境变量</a></li><li><a href="ch12-06-writing-to-stderr-instead-of-stdout.html"><strong>12.6.</strong> 输出到<code>stderr</code>而不是<code>stdout</code></a></li></ul></li><li><a href="ch13-00-functional-features.html"><strong>13.</strong> Rust 中的函数式语言功能</a></li><li><ul class="section"><li><a href="ch13-01-closures.html"><strong>13.1.</strong> 闭包</a></li><li><a href="ch13-02-iterators.html"><strong>13.2.</strong> 迭代器</a></li><li><a href="ch13-03-improving-our-io-project.html"><strong>13.3.</strong> 改进 I/O 项目</a></li><li><a href="ch13-04-performance.html"><strong>13.4.</strong> 性能</a></li></ul></li><li><a href="ch14-00-more-about-cargo.html"><strong>14.</strong> 更多关于 Cargo 和 Crates.io</a></li><li><ul class="section"><li><a href="ch14-01-release-profiles.html"><strong>14.1.</strong> 发布配置</a></li><li><a href="ch14-02-publishing-to-crates-io.html"><strong>14.2.</strong> 将 crate 发布到 Crates.io</a></li><li><a href="ch14-03-cargo-workspaces.html"><strong>14.3.</strong> Cargo 工作空间</a></li><li><a href="ch14-04-installing-binaries.html"><strong>14.4.</strong> 使用<code>cargo install</code>从 Crates.io 安装文件</a></li><li><a href="ch14-05-extending-cargo.html"><strong>14.5.</strong> Cargo 自定义扩展命令</a></li></ul></li><li><a href="ch15-00-smart-pointers.html"><strong>15.</strong> 智能指针</a></li><li><ul class="section"><li><a href="ch15-01-box.html"><strong>15.1.</strong> <code>Box&lt;T&gt;</code>用于已知大小的堆上数据</a></li><li><a href="ch15-02-deref.html" class="active"><strong>15.2.</strong> <code>Deref</code> Trait 允许通过引用访问数据</a></li><li><a href="ch15-03-drop.html"><strong>15.3.</strong> <code>Drop</code> Trait 运行清理代码</a></li><li><a href="ch15-04-rc.html"><strong>15.4.</strong> <code>Rc&lt;T&gt;</code> 引用计数智能指针</a></li><li><a href="ch15-05-interior-mutability.html"><strong>15.5.</strong> <code>RefCell&lt;T&gt;</code>和内部可变性模式</a></li><li><a href="ch15-06-reference-cycles.html"><strong>15.6.</strong> 引用循环和内存泄漏是安全的</a></li></ul></li></ul>
</div>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div class="left-buttons">
<i id="sidebar-toggle" class="fa fa-bars"></i>
<i id="theme-toggle" class="fa fa-paint-brush"></i>
</div>
<h1 class="menu-title">Rust 程序设计语言 简体中文版</h1>
<div class="right-buttons">
<i id="print-button" class="fa fa-print" title="Print this book"></i>
</div>
</div>
<div id="content" class="content">
<a class="header" href="#deref-trait-允许通过引用访问数据" name="deref-trait-允许通过引用访问数据"><h2><code>Deref</code> Trait 允许通过引用访问数据</h2></a>
<blockquote>
<p><a href="https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-02-deref.md">ch15-02-deref.md</a>
<br>
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56</p>
</blockquote>
<p>第一个智能指针相关的重要 trait 是<code>Deref</code>,它允许我们重载<code>*</code>,解引用运算符(不同于乘法运算符和全局引用运算符)。重载智能指针的<code>*</code>方便访问其后的数据,在这个部分的稍后介绍解引用强制多态时我们会讨论方便的意义。</p>
<p>第八章的哈希 map 的“根据旧值更新一个值”部分简要的提到了解引用运算符。当时有一个可变引用,而我们希望改变这个引用所指向的值。为此,首先我们必须解引用。这是另一个使用<code>i32</code>值引用的例子:</p>
<pre><code class="language-rust">let mut x = 5;
{
let y = &amp;mut x;
*y += 1
}
assert_eq!(6, x);
</code></pre>
<p>我们使用<code>*y</code>来访问可变引用<code>y</code>所指向的数据,而是可变引用本身。接着可以修改它的数据,在这里对其加一。</p>
<p>引用并不是智能指针,他们只是引用指向的一个值,所以这个解引用操作是很直接的。智能指针还会储存指针或数据的元数据。当解引用一个智能指针时,我们只想要数据,而不需要元数据。我们希望能在使用常规引用的地方也能使用智能指针。为此,可以通过实现<code>Deref</code> trait 来重载<code>*</code>运算符的行为。</p>
<p>列表 15-7 展示了一个定义为储存 mp3 数据和元数据的结构体通过<code>Deref</code> trait 来重载<code>*</code>的例子。<code>Mp3</code>,在某种意义上是一个智能指针:它拥有包含音频的<code>Vec&lt;u8&gt;</code>数据。另外,它储存了一些可选的元数据,在这个例子中是音频数据中艺术家和歌曲的名称。我们希望能够方便的访问音频数据而不是元数据,所以需要实现<code>Deref</code> trait 来返回音频数据。实现<code>Deref</code> trait 需要一个叫做<code>deref</code>的方法,它借用<code>self</code>并返回其内部数据:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><code class="language-rust">use std::ops::Deref;
struct Mp3 {
audio: Vec&lt;u8&gt;,
artist: Option&lt;String&gt;,
title: Option&lt;String&gt;,
}
impl Deref for Mp3 {
type Target = Vec&lt;u8&gt;;
fn deref(&amp;self) -&gt; &amp;Vec&lt;u8&gt; {
&amp;self.audio
}
}
fn main() {
let my_favorite_song = Mp3 {
// we would read the actual audio data from an mp3 file
audio: vec![1, 2, 3],
artist: Some(String::from(&quot;Nirvana&quot;)),
title: Some(String::from(&quot;Smells Like Teen Spirit&quot;)),
};
assert_eq!(vec![1, 2, 3], *my_favorite_song);
}
</code></pre>
<p><span class="caption">Listing 15-7: An implementation of the <code>Deref</code> trait on a
struct that holds mp3 file data and metadata</span></p>
<p>大部分代码看起来都比较熟悉:一个结构体、一个 trait 实现、和一个创建了结构体示例的 main 函数。其中有一部分我们还未全面的讲解:类似于第十三章学习迭代器 trait 时出现的<code>type Item</code><code>type Target = T;</code>语法用于定义关联类型,第十九章会更详细的介绍。不必过分担心例子中的这一部分;它只是一个稍显不同的定义泛型参数的方式。</p>
<p><code>assert_eq!</code>中,我们验证<code>vec![1, 2, 3]</code>是否为<code>Mp3</code>实例<code>*my_favorite_song</code>解引用的值,结果正是如此因为我们实现了<code>deref</code>方法来返回音频数据。如果没有为<code>Mp3</code>实现<code>Deref</code> traitRust 将不会编译<code>*my_favorite_song</code>:会出现错误说<code>Mp3</code>类型不能被解引用。</p>
<p>代码能够工作的原因在于调用<code>*my_favorite_song</code><code>*</code>在背后所做的操作:</p>
<pre><code class="language-rust,ignore">*(my_favorite_song.deref())
</code></pre>
<p>这对<code>my_favorite_song</code>调用了<code>deref</code>方法,它借用了<code>my_favorite_song</code>并返回指向<code>my_favorite_song.audio</code>的引用,这正是列表 15-5 中<code>deref</code>所定义的。引用的<code>*</code>被定义为仅仅从引用中返回其数据,所以上面<code>*</code>的展开形式对于外部<code>*</code>来说并不是递归的。最终的数据类型是<code>Vec&lt;u8&gt;</code>,它与列表 15-5 中<code>assert_eq!</code><code>vec![1, 2, 3]</code>相匹配。</p>
<p><code>deref</code>方法的返回值类型仍然是引用和为何必须解引用方法的结果的原因是如果<code>deref</code>方法就返回值,使用<code>*</code>总是会获取其所有权。</p>
<a class="header" href="#函数和方法的隐式解引用强制多态" name="函数和方法的隐式解引用强制多态"><h3>函数和方法的隐式解引用强制多态</h3></a>
<p>Rust 倾向于偏爱明确而不是隐晦,不过一个情况下这并不成立,就是函数和方法的参数的<strong>解引用强制多态</strong><em>deref coercions</em>)。解引用强制多态会自动的将指针或智能指针的引用转换为指针内容的引用。解引用强制多态发生于一个值被传递给函数或方法,并只发生于需要将传递的值类型与签名中参数类型相匹配的时候。解引用强制多态的加入使得 Rust 调用函数或方法时无需很多使用<code>&amp;</code><code>*</code>的引用和解引用。</p>
<p>使用列表 15-5 中的<code>Mp3</code>结构体,如下是一个获取<code>u8</code> slice 并压缩 mp3 音频数据的函数签名:</p>
<pre><code class="language-rust,ignore">fn compress_mp3(audio: &amp;[u8]) -&gt; Vec&lt;u8&gt; {
// the actual implementation would go here
}
</code></pre>
<p>如果 Rust 没有解引用强制多态,为了使用<code>my_favorite_song</code>中的音频数据调用此函数,必须写成:</p>
<pre><code class="language-rust,ignore">compress_mp3(my_favorite_song.audio.as_slice())
</code></pre>
<p>也就是说,必须明确表用需要<code>my_favorite_song</code>中的<code>audio</code>字段而且我们希望有一个 slice 来引用这整个<code>Vec&lt;u8&gt;</code>。如果有很多地方需要用相同的方式处理<code>audio</code>数据,那么<code>.audio.as_slice()</code>就显得冗长重复了。</p>
<p>然而,因为解引用强制多态和<code>Mp3</code><code>Deref</code> trait 实现,我们可以使用如下代码使用<code>my_favorite_song</code>中的数据调用这个函数:</p>
<pre><code class="language-rust,ignore">let result = compress_mp3(&amp;my_favorite_song);
</code></pre>
<p>只有<code>&amp;</code>和实例,好的!我们可以把智能指针当成普通的引用。也就是说解引用强制多态意味着 Rust 利用了<code>Deref</code>实现的优势Rust 知道<code>Mp3</code>实现了<code>Deref</code> trait 并从<code>deref</code>方法返回<code>&amp;Vec&lt;u8&gt;</code>。它也知道标准库实现了<code>Vec&lt;T&gt;</code><code>Deref</code> trait<code>deref</code>方法返回<code>&amp;[T]</code>(我们也可以通过查阅<code>Vec&lt;T&gt;</code>的 API 文档来发现这一点。所以在编译时Rust 会发现它可以调用两次<code>Deref::deref</code>来将<code>&amp;Mp3</code>变成<code>&amp;Vec&lt;u8&gt;</code>再变成<code>&amp;[T]</code>来满足<code>compress_mp3</code>的签名。这意味着我们可以少写一些代码Rust 会多次分析<code>Deref::deref</code>的返回值类型直到它满足参数的类型,只要相关类型实现了<code>Deref</code> trait。这些间接转换在编译时进行所以利用解引用强制多态并没有运行时惩罚。</p>
<p>这里还有一个重载了<code>&amp;mut T</code><code>*</code><code>DerefMut</code> trait它以与<code>Deref</code>重载<code>&amp;T</code><code>*</code>相同的方式用于参数中。</p>
<p>Rust 在发现类型和 trait 实现满足三种情况时会进行解引用强制多态:</p>
<ul>
<li><code>&amp;T</code><code>&amp;U</code><code>T: Deref&lt;Target=U&gt;</code></li>
<li><code>&amp;mut T</code><code>&amp;mut U</code><code>T: DerefMut&lt;Target=U&gt;</code></li>
<li><code>&amp;mut T</code><code>&amp;U</code><code>T: Deref&lt;Target=U&gt;</code></li>
</ul>
<p>头两个情况除了可变性之外是相同的:如果有一个<code>&amp;T</code>,而<code>T</code>实现了返回<code>U</code>类型的<code>Deref</code>,可以直接得到<code>&amp;U</code>。对于可变引用也是一样。最后一个有些微妙如果有一个可变引用它也可以强转为一个不可变引用。反之则是_不可能_的不可变引用永远也不能强转为可变引用。</p>
<p><code>Deref</code> trait 对于只能指针模式十分重要的原因在于智能指针可以被看作普通引用并用于期望使用引用的地方。例如,无需重新编写方法和函数来直接获取智能指针。</p>
</div>
<!-- Mobile navigation buttons -->
<a href="ch15-01-box.html" class="mobile-nav-chapters previous">
<i class="fa fa-angle-left"></i>
</a>
<a href="ch15-03-drop.html" class="mobile-nav-chapters next">
<i class="fa fa-angle-right"></i>
</a>
</div>
<a href="ch15-01-box.html" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys">
<i class="fa fa-angle-left"></i>
</a>
<a href="ch15-03-drop.html" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys">
<i class="fa fa-angle-right"></i>
</a>
</div>
<!-- Local fallback for Font Awesome -->
<script>
if ($(".fa").css("font-family") !== "FontAwesome") {
$('<link rel="stylesheet" type="text/css" href="_FontAwesome/css/font-awesome.css">').prependTo('head');
}
</script>
<!-- Livereload script (if served using the cli tool) -->
<script src="highlight.js"></script>
<script src="book.js"></script>
</body>
</html>