mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
507 lines
45 KiB
HTML
507 lines
45 KiB
HTML
|
<!DOCTYPE HTML>
|
|||
|
<html lang="en" class="light" dir="ltr">
|
|||
|
<head>
|
|||
|
<!-- Book generated using mdBook -->
|
|||
|
<meta charset="UTF-8">
|
|||
|
<title>引用与借用 - Rust 程序设计语言 简体中文版</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" href="highlight.css">
|
|||
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|||
|
<link rel="stylesheet" href="ayu-highlight.css">
|
|||
|
|
|||
|
<!-- Custom theme stylesheets -->
|
|||
|
<link rel="stylesheet" href="ferris.css">
|
|||
|
<link rel="stylesheet" href="theme/2018-edition.css">
|
|||
|
<link rel="stylesheet" href="theme/semantic-notes.css">
|
|||
|
<link rel="stylesheet" href="theme/listing.css">
|
|||
|
|
|||
|
</head>
|
|||
|
<body class="sidebar-visible no-js">
|
|||
|
<div id="body-container">
|
|||
|
<!-- Provide site root to javascript -->
|
|||
|
<script>
|
|||
|
var path_to_root = "";
|
|||
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|||
|
<script>
|
|||
|
try {
|
|||
|
var theme = localStorage.getItem('mdbook-theme');
|
|||
|
var 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>
|
|||
|
var theme;
|
|||
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|||
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|||
|
var html = document.querySelector('html');
|
|||
|
html.classList.remove('light')
|
|||
|
html.classList.add(theme);
|
|||
|
var body = document.querySelector('body');
|
|||
|
body.classList.remove('no-js')
|
|||
|
body.classList.add('js');
|
|||
|
</script>
|
|||
|
|
|||
|
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
|||
|
|
|||
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|||
|
<script>
|
|||
|
var body = document.querySelector('body');
|
|||
|
var sidebar = null;
|
|||
|
var 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';
|
|||
|
body.classList.remove('sidebar-visible');
|
|||
|
body.classList.add("sidebar-" + sidebar);
|
|||
|
</script>
|
|||
|
|
|||
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|||
|
<div class="sidebar-scrollbox">
|
|||
|
<ol class="chapter"><li class="chapter-item expanded affix "><a href="title-page.html">Rust 程序设计语言</a></li><li class="chapter-item expanded affix "><a href="foreword.html">前言</a></li><li class="chapter-item expanded affix "><a href="ch00-00-introduction.html">简介</a></li><li class="chapter-item expanded "><a href="ch01-00-getting-started.html"><strong aria-hidden="true">1.</strong> 入门指南</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch01-01-installation.html"><strong aria-hidden="true">1.1.</strong> 安装</a></li><li class="chapter-item expanded "><a href="ch01-02-hello-world.html"><strong aria-hidden="true">1.2.</strong> Hello, World!</a></li><li class="chapter-item expanded "><a href="ch01-03-hello-cargo.html"><strong aria-hidden="true">1.3.</strong> Hello, Cargo!</a></li></ol></li><li class="chapter-item expanded "><a href="ch02-00-guessing-game-tutorial.html"><strong aria-hidden="true">2.</strong> 写个猜数字游戏</a></li><li class="chapter-item expanded "><a href="ch03-00-common-programming-concepts.html"><strong aria-hidden="true">3.</strong> 常见编程概念</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch03-01-variables-and-mutability.html"><strong aria-hidden="true">3.1.</strong> 变量与可变性</a></li><li class="chapter-item expanded "><a href="ch03-02-data-types.html"><strong aria-hidden="true">3.2.</strong> 数据类型</a></li><li class="chapter-item expanded "><a href="ch03-03-how-functions-work.html"><strong aria-hidden="true">3.3.</strong> 函数</a></li><li class="chapter-item expanded "><a href="ch03-04-comments.html"><strong aria-hidden="true">3.4.</strong> 注释</a></li><li class="chapter-item expanded "><a href="ch03-05-control-flow.html"><strong aria-hidden="true">3.5.</strong> 控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-understanding-ownership.html"><strong aria-hidden="true">4.</strong> 认识所有权</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch04-01-what-is-ownership.html"><strong aria-hidden="true">4.1.</strong> 什么是所有权?</a></li><li class="chapter-item expanded "><a href="ch04-02-references-and-borrowing.html" class="active"><strong aria-hidden="true">4.2.</strong> 引用与借用</a></li><li class="chapter-item expanded "><a href="ch04-03-slices.html"><strong aria-hidden="true">4.3.</strong> Slice 类型</a></li></ol></li><li class="chapter-item expanded "><a href="ch05-00-structs.html"><strong aria-hidden="true">5.</strong> 使用结构体组织相关联的数据</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch05-01-defining-structs.html"><strong aria-hidden="true">5.1.</strong> 结构体的定义和实例化</a></li><li class="chapter-item expanded "><a href="ch05-02-example-structs.html"><strong aria-hidden="true">5.2.</strong> 结构体示例程序</a></li><li class="chapter-item expanded "><a href="ch05-03-method-syntax.html"><strong aria-hidden="true">5.3.</strong> 方法语法</a></li></ol></li><li class="chapter-item expanded "><a href="ch06-00-enums.html"><strong aria-hidden="true">6.</strong> 枚举和模式匹配</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch06-01-defining-an-enum.html"><strong aria-hidden="true">6.1.</strong> 枚举的定义</a></li><li class="chapter-item expanded "><a href="ch06-02-match.html"><strong aria-hidden="true">6.2.</strong> match 控制流结构</a></li><li class="chapter-item expanded "><a href="ch06-03-if-let.html"><strong aria-hidden="true">6.3.</strong> if let 简洁控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch07-00-managing-growing-projects-with-packages-crates-and-modules.html"><strong aria-hidden="true">7.</strong> 使用包、Crate 和模块管理不断增长的项目</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch07-01-packages-and-crates.html"><strong aria-hidden="true">7.1.</strong> 包和 Crate</a></li><li class="chapter-item
|
|||
|
</div>
|
|||
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
|||
|
<div class="sidebar-resize-indicator"></div>
|
|||
|
</div>
|
|||
|
</nav>
|
|||
|
|
|||
|
<!-- Track and set sidebar scroll position -->
|
|||
|
<script>
|
|||
|
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
|
|||
|
sidebarScrollbox.addEventListener('click', function(e) {
|
|||
|
if (e.target.tagName === 'A') {
|
|||
|
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
|
|||
|
}
|
|||
|
}, { passive: true });
|
|||
|
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
|
|||
|
sessionStorage.removeItem('sidebar-scroll');
|
|||
|
if (sidebarScrollTop) {
|
|||
|
// preserve sidebar scroll position when navigating via links within sidebar
|
|||
|
sidebarScrollbox.scrollTop = sidebarScrollTop;
|
|||
|
} else {
|
|||
|
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
|
|||
|
var activeSection = document.querySelector('#sidebar .active');
|
|||
|
if (activeSection) {
|
|||
|
activeSection.scrollIntoView({ block: 'center' });
|
|||
|
}
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<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="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 程序设计语言 简体中文版</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/KaiserY/trpl-zh-cn/tree/main" title="Git repository" aria-label="Git repository">
|
|||
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|||
|
</a>
|
|||
|
<a href="https://github.com/KaiserY/trpl-zh-cn/edit/main/src/ch04-02-references-and-borrowing.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>
|
|||
|
<h2 id="引用与借用"><a class="header" href="#引用与借用">引用与借用</a></h2>
|
|||
|
<blockquote>
|
|||
|
<p><a href="https://github.com/rust-lang/book/blob/main/src/ch04-02-references-and-borrowing.md">ch04-02-references-and-borrowing.md</a>
|
|||
|
<br>
|
|||
|
commit 3d51f70c78162faaebcab0da0de2ddd333e7a8ed</p>
|
|||
|
</blockquote>
|
|||
|
<p>示例 4-5 中的元组代码有这样一个问题:我们必须将 <code>String</code> 返回给调用函数,以便在调用 <code>calculate_length</code> 后仍能使用 <code>String</code>,因为 <code>String</code> 被移动到了 <code>calculate_length</code> 内。相反我们可以提供一个 <code>String</code> 值的引用(reference)。<strong>引用</strong>(<em>reference</em>)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。
|
|||
|
与指针不同,引用确保指向某个特定类型的有效值。</p>
|
|||
|
<p>下面是如何定义并使用一个(新的)<code>calculate_length</code> 函数,它以一个对象的引用作为参数而不是获取值的所有权:</p>
|
|||
|
<p><span class="filename">文件名:src/main.rs</span></p>
|
|||
|
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
|
|||
|
let s1 = String::from("hello");
|
|||
|
|
|||
|
let len = calculate_length(&s1);
|
|||
|
|
|||
|
println!("The length of '{s1}' is {len}.");
|
|||
|
}
|
|||
|
|
|||
|
fn calculate_length(s: &String) -> usize {
|
|||
|
s.len()
|
|||
|
}</code></pre></pre>
|
|||
|
<p>首先,注意变量声明和函数返回值中的所有元组代码都消失了。其次,注意我们传递 <code>&s1</code> 给 <code>calculate_length</code>,同时在函数定义中,我们获取 <code>&String</code> 而不是 <code>String</code>。这些 & 符号就是 <strong>引用</strong>,它们允许你使用值但不获取其所有权。图 4-6 展示了一张示意图。</p>
|
|||
|
<p><img alt="Three tables: the table for s contains only a pointer to the table
|
|||
|
for s1. The table for s1 contains the stack data for s1 and points to the
|
|||
|
string data on the heap." src="img/trpl04-06.svg" class="center" /></p>
|
|||
|
<p><span class="caption">图 4-6:<code>&String s</code> 指向 <code>String s1</code> 示意图</span></p>
|
|||
|
<blockquote>
|
|||
|
<p>注意:与使用 <code>&</code> 引用相反的操作是 <strong>解引用</strong>(<em>dereferencing</em>),它使用解引用运算符,<code>*</code>。我们将会在第八章遇到一些解引用运算符,并在第十五章详细讨论解引用。</p>
|
|||
|
</blockquote>
|
|||
|
<p>仔细看看这个函数调用:</p>
|
|||
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">fn main() {
|
|||
|
</span> let s1 = String::from("hello");
|
|||
|
|
|||
|
let len = calculate_length(&s1);
|
|||
|
<span class="boring">
|
|||
|
</span><span class="boring"> println!("The length of '{s1}' is {len}.");
|
|||
|
</span><span class="boring">}
|
|||
|
</span><span class="boring">
|
|||
|
</span><span class="boring">fn calculate_length(s: &String) -> usize {
|
|||
|
</span><span class="boring"> s.len()
|
|||
|
</span><span class="boring">}</span></code></pre></pre>
|
|||
|
<p><code>&s1</code> 语法让我们创建一个 <strong>指向</strong> 值 <code>s1</code> 的引用,但是并不拥有它。因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。</p>
|
|||
|
<p>同理,函数签名使用 <code>&</code> 来表明参数 <code>s</code> 的类型是一个引用。让我们增加一些解释性的注释:</p>
|
|||
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">fn main() {
|
|||
|
</span><span class="boring"> let s1 = String::from("hello");
|
|||
|
</span><span class="boring">
|
|||
|
</span><span class="boring"> let len = calculate_length(&s1);
|
|||
|
</span><span class="boring">
|
|||
|
</span><span class="boring"> println!("The length of '{s1}' is {len}.");
|
|||
|
</span><span class="boring">}
|
|||
|
</span><span class="boring">
|
|||
|
</span>fn calculate_length(s: &String) -> usize { // s 是 String 的引用
|
|||
|
s.len()
|
|||
|
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
|
|||
|
// 所以什么也不会发生</code></pre></pre>
|
|||
|
<p>变量 <code>s</code> 有效的作用域与函数参数的作用域一样,不过当 <code>s</code> 停止使用时并不丢弃引用指向的数据,因为 <code>s</code> 并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。</p>
|
|||
|
<p>我们将创建一个引用的行为称为 <strong>借用</strong>(<em>borrowing</em>)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。我们并不拥有它。</p>
|
|||
|
<p>如果我们尝试修改借用的变量呢?尝试示例 4-6 中的代码。剧透:这行不通!</p>
|
|||
|
<p><span class="filename">文件名:src/main.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore does_not_compile">fn main() {
|
|||
|
let s = String::from("hello");
|
|||
|
|
|||
|
change(&s);
|
|||
|
}
|
|||
|
|
|||
|
fn change(some_string: &String) {
|
|||
|
some_string.push_str(", world");
|
|||
|
}</code></pre>
|
|||
|
<p><span class="caption">示例 4-6:尝试修改借用的值</span></p>
|
|||
|
<p>这里是错误:</p>
|
|||
|
<pre><code class="language-console">$ cargo run
|
|||
|
Compiling ownership v0.1.0 (file:///projects/ownership)
|
|||
|
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
|
|||
|
--> src/main.rs:8:5
|
|||
|
|
|
|||
|
8 | some_string.push_str(", world");
|
|||
|
| ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
|
|||
|
|
|
|||
|
help: consider changing this to be a mutable reference
|
|||
|
|
|
|||
|
7 | fn change(some_string: &mut String) {
|
|||
|
| +++
|
|||
|
|
|||
|
For more information about this error, try `rustc --explain E0596`.
|
|||
|
error: could not compile `ownership` (bin "ownership") due to 1 previous error
|
|||
|
</code></pre>
|
|||
|
<p>正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。</p>
|
|||
|
<h3 id="可变引用"><a class="header" href="#可变引用">可变引用</a></h3>
|
|||
|
<p>我们通过一个小调整就能修复示例 4-6 代码中的错误,允许我们修改一个借用的值,这就是 <strong>可变引用</strong>(<em>mutable reference</em>):</p>
|
|||
|
<p><span class="filename">文件名:src/main.rs</span></p>
|
|||
|
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
|
|||
|
let mut s = String::from("hello");
|
|||
|
|
|||
|
change(&mut s);
|
|||
|
}
|
|||
|
|
|||
|
fn change(some_string: &mut String) {
|
|||
|
some_string.push_str(", world");
|
|||
|
}</code></pre></pre>
|
|||
|
<p>首先,我们必须将 <code>s</code> 改为 <code>mut</code>。然后在调用 <code>change</code> 函数的地方创建一个可变引用 <code>&mut s</code>,并更新函数签名以接受一个可变引用 <code>some_string: &mut String</code>。这就非常清楚地表明,<code>change</code> 函数将改变它所借用的值。</p>
|
|||
|
<p>可变引用有一个很大的限制:如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 <code>s</code> 的可变引用的代码会失败:</p>
|
|||
|
<p><span class="filename">文件名:src/main.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore does_not_compile"><span class="boring">fn main() {
|
|||
|
</span> let mut s = String::from("hello");
|
|||
|
|
|||
|
let r1 = &mut s;
|
|||
|
let r2 = &mut s;
|
|||
|
|
|||
|
println!("{}, {}", r1, r2);
|
|||
|
<span class="boring">}</span></code></pre>
|
|||
|
<p>错误如下:</p>
|
|||
|
<pre><code class="language-console">$ cargo run
|
|||
|
Compiling ownership v0.1.0 (file:///projects/ownership)
|
|||
|
error[E0499]: cannot borrow `s` as mutable more than once at a time
|
|||
|
--> src/main.rs:5:14
|
|||
|
|
|
|||
|
4 | let r1 = &mut s;
|
|||
|
| ------ first mutable borrow occurs here
|
|||
|
5 | let r2 = &mut s;
|
|||
|
| ^^^^^^ second mutable borrow occurs here
|
|||
|
6 |
|
|||
|
7 | println!("{}, {}", r1, r2);
|
|||
|
| -- first borrow later used here
|
|||
|
|
|||
|
For more information about this error, try `rustc --explain E0499`.
|
|||
|
error: could not compile `ownership` (bin "ownership") due to 1 previous error
|
|||
|
</code></pre>
|
|||
|
<p>这个报错说这段代码是无效的,因为我们不能在同一时间多次将 <code>s</code> 作为可变变量借用。第一个可变的借入在 <code>r1</code> 中,并且必须持续到在 <code>println!</code> 中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在 <code>r2</code> 中创建另一个可变引用,该引用借用与 <code>r1</code> 相同的数据。</p>
|
|||
|
<p>这一限制以一种非常小心谨慎的方式允许可变性,防止同一时间对同一数据存在多个可变引用。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。<strong>数据竞争</strong>(<em>data race</em>)类似于竞态条件,它可由这三个行为造成:</p>
|
|||
|
<ul>
|
|||
|
<li>两个或更多指针同时访问同一数据。</li>
|
|||
|
<li>至少有一个指针被用来写入数据。</li>
|
|||
|
<li>没有同步数据访问的机制。</li>
|
|||
|
</ul>
|
|||
|
<p>数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!</p>
|
|||
|
<p>一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能 <strong>同时</strong> 拥有:</p>
|
|||
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">fn main() {
|
|||
|
</span> let mut s = String::from("hello");
|
|||
|
|
|||
|
{
|
|||
|
let r1 = &mut s;
|
|||
|
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
|
|||
|
|
|||
|
let r2 = &mut s;
|
|||
|
<span class="boring">}</span></code></pre></pre>
|
|||
|
<p>Rust 在同时使用可变与不可变引用时也采用的类似的规则。这些代码会导致一个错误:</p>
|
|||
|
<pre><code class="language-rust ignore does_not_compile"><span class="boring">fn main() {
|
|||
|
</span> let mut s = String::from("hello");
|
|||
|
|
|||
|
let r1 = &s; // 没问题
|
|||
|
let r2 = &s; // 没问题
|
|||
|
let r3 = &mut s; // 大问题
|
|||
|
|
|||
|
println!("{}, {}, and {}", r1, r2, r3);
|
|||
|
<span class="boring">}</span></code></pre>
|
|||
|
<p>错误如下:</p>
|
|||
|
<pre><code class="language-console">$ cargo run
|
|||
|
Compiling ownership v0.1.0 (file:///projects/ownership)
|
|||
|
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
|
|||
|
--> src/main.rs:6:14
|
|||
|
|
|
|||
|
4 | let r1 = &s; // no problem
|
|||
|
| -- immutable borrow occurs here
|
|||
|
5 | let r2 = &s; // no problem
|
|||
|
6 | let r3 = &mut s; // BIG PROBLEM
|
|||
|
| ^^^^^^ mutable borrow occurs here
|
|||
|
7 |
|
|||
|
8 | println!("{}, {}, and {}", r1, r2, r3);
|
|||
|
| -- immutable borrow later used here
|
|||
|
|
|||
|
For more information about this error, try `rustc --explain E0502`.
|
|||
|
error: could not compile `ownership` (bin "ownership") due to 1 previous error
|
|||
|
</code></pre>
|
|||
|
<p>哇哦!我们 <strong>也</strong> 不能在拥有不可变引用的同时拥有可变引用。</p>
|
|||
|
<p>不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!然而,多个不可变引用是可以的,因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。</p>
|
|||
|
<p>注意一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。例如,因为最后一次使用不可变引用(<code>println!</code>),发生在声明可变引用之前,所以如下代码是可以编译的:</p>
|
|||
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">fn main() {
|
|||
|
</span> let mut s = String::from("hello");
|
|||
|
|
|||
|
let r1 = &s; // 没问题
|
|||
|
let r2 = &s; // 没问题
|
|||
|
println!("{r1} and {r2}");
|
|||
|
// 此位置之后 r1 和 r2 不再使用
|
|||
|
|
|||
|
let r3 = &mut s; // 没问题
|
|||
|
println!("{r3}");
|
|||
|
<span class="boring">}</span></code></pre></pre>
|
|||
|
<p>不可变引用 <code>r1</code> 和 <code>r2</code> 的作用域在 <code>println!</code> 最后一次使用之后结束,这也是创建可变引用 <code>r3</code> 的地方。它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。</p>
|
|||
|
<p>尽管这些错误有时使人沮丧,但请牢记这是 Rust 编译器在提前指出一个潜在的 bug(在编译时而不是在运行时)并精准显示问题所在。这样你就不必去跟踪为何数据并不是你想象中的那样。</p>
|
|||
|
<h3 id="悬垂引用dangling-references"><a class="header" href="#悬垂引用dangling-references">悬垂引用(Dangling References)</a></h3>
|
|||
|
<p>在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 <strong>悬垂指针</strong>(<em>dangling pointer</em>),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。</p>
|
|||
|
<p>让我们尝试创建一个悬垂引用,Rust 会通过一个编译时错误来避免:</p>
|
|||
|
<p><span class="filename">文件名:src/main.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore does_not_compile">fn main() {
|
|||
|
let reference_to_nothing = dangle();
|
|||
|
}
|
|||
|
|
|||
|
fn dangle() -> &String {
|
|||
|
let s = String::from("hello");
|
|||
|
|
|||
|
&s
|
|||
|
}</code></pre>
|
|||
|
<p>这里是错误:</p>
|
|||
|
<pre><code class="language-console">$ cargo run
|
|||
|
Compiling ownership v0.1.0 (file:///projects/ownership)
|
|||
|
error[E0106]: missing lifetime specifier
|
|||
|
--> src/main.rs:5:16
|
|||
|
|
|
|||
|
5 | fn dangle() -> &String {
|
|||
|
| ^ expected named lifetime parameter
|
|||
|
|
|
|||
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
|
|||
|
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
|||
|
|
|
|||
|
5 | fn dangle() -> &'static String {
|
|||
|
| +++++++
|
|||
|
help: instead, you are more likely to want to return an owned value
|
|||
|
|
|
|||
|
5 - fn dangle() -> &String {
|
|||
|
5 + fn dangle() -> String {
|
|||
|
|
|
|||
|
|
|||
|
error[E0515]: cannot return reference to local variable `s`
|
|||
|
--> src/main.rs:8:5
|
|||
|
|
|
|||
|
8 | &s
|
|||
|
| ^^ returns a reference to data owned by the current function
|
|||
|
|
|||
|
Some errors have detailed explanations: E0106, E0515.
|
|||
|
For more information about an error, try `rustc --explain E0106`.
|
|||
|
error: could not compile `ownership` (bin "ownership") due to 2 previous errors
|
|||
|
</code></pre>
|
|||
|
<p>错误信息引用了一个我们还未介绍的功能:生命周期(lifetimes)。第十章会详细介绍生命周期。不过,如果你不理会生命周期部分,错误信息中确实包含了为什么这段代码有问题的关键信息:</p>
|
|||
|
<pre><code class="language-text">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>
|
|||
|
<p><span class="filename">文件名:src/main.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore does_not_compile"><span class="boring">fn main() {
|
|||
|
</span><span class="boring"> let reference_to_nothing = dangle();
|
|||
|
</span><span class="boring">}
|
|||
|
</span><span class="boring">
|
|||
|
</span>fn dangle() -> &String { // dangle 返回一个字符串的引用
|
|||
|
|
|||
|
let s = String::from("hello"); // s 是一个新字符串
|
|||
|
|
|||
|
&s // 返回字符串 s 的引用
|
|||
|
} // 这里 s 离开作用域并被丢弃。其内存被释放。
|
|||
|
// 危险!</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><pre class="playground"><code class="language-rust edition2021"><span class="boring">fn main() {
|
|||
|
</span><span class="boring"> let string = no_dangle();
|
|||
|
</span><span class="boring">}
|
|||
|
</span><span class="boring">
|
|||
|
</span>fn no_dangle() -> String {
|
|||
|
let s = String::from("hello");
|
|||
|
|
|||
|
s
|
|||
|
}</code></pre></pre>
|
|||
|
<p>这样就没有任何错误了。所有权被移动出去,所以没有值被释放。</p>
|
|||
|
<h3 id="引用的规则"><a class="header" href="#引用的规则">引用的规则</a></h3>
|
|||
|
<p>让我们概括一下之前对引用的讨论:</p>
|
|||
|
<ul>
|
|||
|
<li>在任意给定时间,<strong>要么</strong> 只能有一个可变引用,<strong>要么</strong> 只能有多个不可变引用。</li>
|
|||
|
<li>引用必须总是有效的。</li>
|
|||
|
</ul>
|
|||
|
<p>接下来,我们来看看另一种不同类型的引用:slice。</p>
|
|||
|
|
|||
|
</main>
|
|||
|
|
|||
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|||
|
<!-- Mobile navigation buttons -->
|
|||
|
<a rel="prev" href="ch04-01-what-is-ownership.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="ch04-03-slices.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="ch04-01-what-is-ownership.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="ch04-03-slices.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="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="ferris.js"></script>
|
|||
|
|
|||
|
|
|||
|
</div>
|
|||
|
</body>
|
|||
|
</html>
|