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/ch04-02-references-and-borr...

322 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="en">
<head>
<meta charset="UTF-8">
<title>引用 &amp; 借用 - 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" class="active"><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"><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="#引用与借用" name="引用与借用"><h2>引用与借用</h2></a>
<blockquote>
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch04-02-references-and-borrowing.md">ch04-02-references-and-borrowing.md</a>
<br>
commit c9fd8eb1da7a79deee97020e8ad49af8ded78f9c</p>
</blockquote>
<p>在上一部分的结尾处的使用元组的代码是有问题的,我们需要将<code>String</code>返回给调用者函数这样就可以在调用<code>calculate_length</code>后仍然可以使用<code>String</code>了,因为<code>String</code>先被移动到了<code>calculate_length</code></p>
<p>下面是如何定义并使用一个(新的)<code>calculate_length</code>函数,它以一个对象的<strong>引用</strong>作为参数而不是获取值的所有权:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><code class="language-rust">fn main() {
let s1 = String::from(&quot;hello&quot;);
let len = calculate_length(&amp;s1);
println!(&quot;The length of '{}' is {}.&quot;, s1, len);
}
fn calculate_length(s: &amp;String) -&gt; usize {
s.len()
}
</code></pre>
<p>首先,注意变量声明和函数返回值中的所有元组代码都消失了。其次,注意我们传递<code>&amp;s1</code><code>calculate_length</code>,同时在函数定义中,我们获取<code>&amp;String</code>而不是<code>String</code></p>
<p>这些 &amp; 符号就是<strong>引用</strong>,他们允许你使用值但不获取它的所有权。图 4-8 展示了一个图解。</p>
<figure>
<img alt="&String s pointing at String s1" src="img/trpl04-05.svg" class="center" />
<figcaption>
<p>Figure 4-8: <code>&amp;String s</code> pointing at <code>String s1</code></p>
</figcaption>
</figure>
<p>仔细看看这个函数调用:</p>
<pre><code class="language-rust"># fn calculate_length(s: &amp;String) -&gt; usize {
# s.len()
# }
let s1 = String::from(&quot;hello&quot;);
let len = calculate_length(&amp;s1);
</code></pre>
<p><code>&amp;s1</code>语法允许我们创建一个<strong>参考</strong><code>s1</code>的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域它指向的值也不会被丢弃。</p>
<p>同理,函数签名使用了<code>&amp;</code>来表明参数<code>s</code>的类型是一个引用。让我们增加一些解释性的注解:</p>
<pre><code class="language-rust">fn calculate_length(s: &amp;String) -&gt; usize { // s is a reference to a String
s.len()
} // Here, s goes out of scope. But because it does not have ownership of what
// it refers to, nothing happens.
</code></pre>
<p>变量<code>s</code>有效的作用域与函数参数的作用域一样,不过当引用离开作用域后并不丢弃它指向的数据因为我们没有所有权。函数使用引用而不是实际值作为参数意味着无需返回值来交还所有权,因为就不曾拥有它。</p>
<p>我们将获取引用作为函数参数称为<strong>借用</strong><em>borrowing</em>)。正如现实生活中,如果一个人拥有某样东西,你可以从它哪里借来。当你使用完毕,必须还回去。</p>
<p>那么如果我们尝试修改借用的变量呢?尝试列表 4-9 中的代码。剧透:这行不通!</p>
<figure>
<span class="filename">Filename: src/main.rs</span>
<pre><code class="language-rust,ignore">fn main() {
let s = String::from(&quot;hello&quot;);
change(&amp;s);
}
fn change(some_string: &amp;String) {
some_string.push_str(&quot;, world&quot;);
}
</code></pre>
<figcaption>
<p>Listing 4-9: Attempting to modify a borrowed value</p>
</figcaption>
</figure>
<p>这里是错误:</p>
<pre><code class="language-sh">error: cannot borrow immutable borrowed content `*some_string` as mutable
--&gt; error.rs:8:5
|
8 | some_string.push_str(&quot;, world&quot;);
| ^^^^^^^^^^^
</code></pre>
<p>正如变量默认是不可变的,引用也一样。不允许修改引用的值。</p>
<a class="header" href="#可变引用" name="可变引用"><h3>可变引用</h3></a>
<p>可以通过一个小调整来修复在列表 4-9 代码中的错误,在列表 4-9 的代码中:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><code class="language-rust">fn main() {
let mut s = String::from(&quot;hello&quot;);
change(&amp;mut s);
}
fn change(some_string: &amp;mut String) {
some_string.push_str(&quot;, world&quot;);
}
</code></pre>
<p>首先,必须将<code>s</code>改为<code>mut</code>。然后必须创建一个可变引用<code>&amp;mut s</code>和接受一个可变引用<code>some_string: &amp;mut String</code></p>
<p>不过可变引用有一个很大的限制:在特定作用域中的特定数据有且只有一个可变引用。这些代码会失败:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><code class="language-rust,ignore">let mut s = String::from(&quot;hello&quot;);
let r1 = &amp;mut s;
let r2 = &amp;mut s;
</code></pre>
<p>具体错误如下:</p>
<pre><code class="language-text">error[E0499]: cannot borrow `s` as mutable more than once at a time
--&gt; borrow_twice.rs:5:19
|
4 | let r1 = &amp;mut s;
| - first mutable borrow occurs here
5 | let r2 = &amp;mut s;
| ^ second mutable borrow occurs here
6 | }
| - first borrow ends here
</code></pre>
<p>这个限制允许可变性,不过是以一种受限制的方式。新 Rustacean 们经常与此作斗争,因为大部分语言任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争data races</p>
<p><strong>数据竞争</strong>是一种特定类型的竞争状态,它可由这三个行为造成:</p>
<ol>
<li>两个或更多指针同时访问相同的数据。</li>
<li>至少有一个指针被用来写数据。</li>
<li>没有被用来同步数据访问的机制。</li>
</ol>
<p>数据竞争会导致未定义行为并且当在运行时尝试追踪时可能会变得难以诊断和修复Rust 阻止了这种情况的发生,因为存在数据竞争的代码根本就不能编译!</p>
<p>一如既往,使用大括号来创建一个新的作用域,允许拥有多个可变引用,只是不能<strong>同时</strong>拥有:</p>
<pre><code class="language-rust">let mut s = String::from(&quot;hello&quot;);
{
let r1 = &amp;mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &amp;mut s;
</code></pre>
<p>当结合可变和不可变引用时有一个类似的规则存在。这些代码会导致一个错误:</p>
<pre><code class="language-rust,ignore">let mut s = String::from(&quot;hello&quot;);
let r1 = &amp;s; // no problem
let r2 = &amp;s; // no problem
let r3 = &amp;mut s; // BIG PROBLEM
</code></pre>
<p>错误如下:</p>
<pre><code class="language-sh">error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable
--&gt; borrow_thrice.rs:6:19
|
4 | let r1 = &amp;s; // no problem
| - immutable borrow occurs here
5 | let r2 = &amp;s; // no problem
6 | let r3 = &amp;mut s; // BIG PROBLEM
| ^ mutable borrow occurs here
7 | }
| - immutable borrow ends here
</code></pre>
<p>哇哦!我们<strong></strong>不能在拥有不可变引用的同时拥有可变引用。不可变引用的用户可不希望在它的眼皮底下值突然就被改变了!然而,多个不可变引用是没有问题的因为没有哪个读取数据的人有能力影响其他人读取到的数据。</p>
<p>即使这些错误有时是使人沮丧的。记住这是 Rust 编译器在提早指出一个潜在的 bug在编译时而不是运行时并明确告诉你问题在哪而不是任由你去追踪为何有时数据并不是你想象中的那样。</p>
<a class="header" href="#悬垂引用" name="悬垂引用"><h3>悬垂引用</h3></a>
<p>在存在指针的语言中,容易错误地生成一个<strong>悬垂指针</strong><em>dangling pointer</em>),一个引用某个内存位置的指针,这个内存可能已经因为被分配给别人,因为释放内存时指向内存的指针被保留了下来。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当我们拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。</p>
<p>让我们尝试创建一个悬垂引用:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><code class="language-rust,ignore">fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -&gt; &amp;String {
let s = String::from(&quot;hello&quot;);
&amp;s
}
</code></pre>
<p>这里是错误:</p>
<pre><code>error[E0106]: missing lifetime specifier
--&gt; dangle.rs:5:16
|
5 | fn dangle() -&gt; &amp;String {
| ^^^^^^^
|
= help: this function's return type contains a borrowed value, but there is no
value for it to be borrowed from
= help: consider giving it a 'static lifetime
error: aborting due to previous error
</code></pre>
<p>错误信息引用了一个我们还未涉及到的功能:<strong>生命周期</strong><em>lifetimes</em>)。第十章会详细介绍生命周期。不过,如果你不理会生命周期的部分,错误信息确实包含了为什么代码是有问题的关键:</p>
<pre><code>this function's return type contains a borrowed value, but there is no value
for it to be borrowed from.
</code></pre>
<p>让我们仔细看看我们的<code>dangle</code>代码的每一步到底放生了什么:</p>
<pre><code class="language-rust,ignore">fn dangle() -&gt; &amp;String { // dangle returns a reference to a String
let s = String::from(&quot;hello&quot;); // s is a new String
&amp;s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!
</code></pre>
<p>因为<code>s</code>是在<code>dangle</code>创建的,当<code>dangle</code>的代码执行完毕后,<code>s</code>将被释放。不过我们尝试返回一个它的引用。这意味着这个引用会指向一个无效的<code>String</code>这可不好。Rust 不会允许我们这么做的。</p>
<p>正确的代码是直接返回<code>String</code></p>
<pre><code class="language-rust">fn no_dangle() -&gt; String {
let s = String::from(&quot;hello&quot;);
s
}
</code></pre>
<p>这样就可以没有任何错误的运行了。所有权被移动出去,所以没有值被释放掉。</p>
<a class="header" href="#引用的规则" name="引用的规则"><h3>引用的规则</h3></a>
<p>简要的概括一下对引用的讨论:</p>
<ol>
<li>在任意给定时间,<strong>只能</strong>拥有如下中的一个:</li>
</ol>
<ul>
<li>一个可变引用。</li>
<li>任意属性的不可变引用。</li>
</ul>
<ol start="2">
<li>引用必须总是有效的。</li>
</ol>
<p>接下来我们来看看一种不同类型的引用slices。</p>
</div>
<!-- Mobile navigation buttons -->
<a href="ch04-01-what-is-ownership.html" class="mobile-nav-chapters previous">
<i class="fa fa-angle-left"></i>
</a>
<a href="ch04-03-slices.html" class="mobile-nav-chapters next">
<i class="fa fa-angle-right"></i>
</a>
</div>
<a href="ch04-01-what-is-ownership.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="ch04-03-slices.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>