mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
1033 lines
80 KiB
HTML
1033 lines
80 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>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></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">
|
||
<h1>介绍</h1>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch01-00-introduction.md">ch01-00-introduction.md</a>
|
||
<br>
|
||
commit c6920d4a2ee0f282addaf8f6945cefe3ef7bdf09</p>
|
||
</blockquote>
|
||
<p>欢迎阅读“Rust 程序设计语言”。一本关于 Rust 的介绍性书籍。Rust 是一个关注安全、速度和并发的编程语言。它的设计可以使程序获得性能和对底层语言的控制,并享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的人们。</p>
|
||
<p>Rust 在编译时进行其绝大多数的安全检查和内存管理决策,因此程序的运行时性能没有受到影响。这让其在许多其他语言不擅长的应用场景中得以大显身手:有可预测空间和时间要求的程序,嵌入到其他语言中,以及编写底层代码,如设备驱动和操作系统。Rust 也很擅长 web 程序:它驱动着 Rust 包注册网站(package
|
||
registry site),crates.io!我们期待看到<em>你</em>使用 Rust 进行创作。</p>
|
||
<p>本书的编排面向已经了解如何使用至少一门编程语言编程的读者。读完本书之后,你应该能自如的编写 Rust 程序。我们将通过小的,专注的并相互依赖的例子来学习 Rust,并向你展示如何使用 Rust 多样的功能,同时了解它们在后台是如何执行的。</p>
|
||
<h2>为本书做出贡献</h2>
|
||
<p>本书是开源的。如果你发现任何错误,请不要犹豫,<a href="https://github.com/rust-lang/book">在 GitHub 上</a>发起 issue 或提交 pull request。</p>
|
||
<h2>安装</h2>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch01-01-installation.md">ch01-01-installation.md</a>
|
||
<br>
|
||
commit f828919e62aa542aaaae03c1fb565da42374213e</p>
|
||
</blockquote>
|
||
<p>使用 Rust 的第一步是安装。你需要联网来执行本章的命令,因为我们要从网上下载 Rust。</p>
|
||
<p>我们将会展示很多使用终端的命令,并且这些代码都以<code>$</code>开头。并不需要真正输入<code>$</code>,它们在这里代表每行指令的开头。在网上会看到很多使用这个惯例的教程和例子:<code>$</code>代表以常规用户运行命令,<code>#</code>代表需要用管理员运行的命令。没有以<code>$</code>(或<code>#</code>)的行通常是之前命令的输出。</p>
|
||
<h3>在 Linux 或 Mac 上安装</h3>
|
||
<p>如果你使用 Linux 或 Mac,所有需要做的就是打开一个终端并输入:</p>
|
||
<pre><code class="language-sh">$ curl https://sh.rustup.rs -sSf | sh
|
||
</code></pre>
|
||
<p>这会下载一个脚本并开始安装。你可能被提示要输入密码。如果一切顺利,将会出现如下内容:</p>
|
||
<pre><code class="language-sh">Rust is installed now. Great!
|
||
</code></pre>
|
||
<p>当然,如果你不赞成<code>curl | sh</code>这种模式,可以随意下载、检查和运行这个脚本。</p>
|
||
<h3>在 Windows 上安装</h3>
|
||
<p>在 Windows 上,前往<a href="https://rustup.rs/">https://rustup.rs</a><!-- ignore -->并按照说明下载<code>rustup-init.exe</code>。运行并遵循它提供的其余指示。</p>
|
||
<p>本书其余 Windows 相关的命令假设你使用<code>cmd</code>作为你的 shell。如果你使用不同的 shell,可能能够执行 Linux 和 Mac 用户相同的命令。如果都不行,查看所使用的 shell 的文档。</p>
|
||
<h3>自定义安装</h3>
|
||
<p>如果有理由倾向于不使用 rustup.rs,请查看<a href="https://www.rust-lang.org/install.html">Rust 安装页面</a>获取其他选择。</p>
|
||
<h3>卸载</h3>
|
||
<p>卸载 Rust 同安装一样简单。在 shell 中运行卸载脚本</p>
|
||
<pre><code class="language-sh">$ rustup self uninstall
|
||
</code></pre>
|
||
<h3>故障排除</h3>
|
||
<p>安装完 Rust 后,打开 shell,输入:</p>
|
||
<pre><code class="language-sh">$ rustc --version
|
||
</code></pre>
|
||
<p>应该能看到类似这样的版本号、提交 hash 和提交日期,对应你安装时的最新稳定版本:</p>
|
||
<pre><code class="language-sh">rustc x.y.z (abcabcabc yyyy-mm-dd)
|
||
</code></pre>
|
||
<p>如果出现这些内容,Rust 就安装成功了!</p>
|
||
<p>恭喜入坑!(此处应该有掌声!)</p>
|
||
<p>如果有问题并且你在使用 Windows,检查 Rust(rustc,cargo 等)是否位于<code>%PATH%</code>系统变量中。</p>
|
||
<p>如果还是不能运行,有许多可以获取帮助的地方。最简单的是 irc.mozilla.org 上的 IRC 频道 <a href="irc://irc.mozilla.org/#rust-beginners">#rust-beginners</a> 和供一般讨论之用的 <a href="irc://irc.mozilla.org/#rust">#rust</a>,我们可以使用 <a href="http://chat.mibbit.com/?server=irc.mozilla.org&channel=%23rust-beginners,%23rust">Mibbit</a> 访问。然后我们就可以和其他能提供帮助的 Rustacean(我们这些人自嘲的绰号)聊天了。其它给力的资源包括<a href="https://users.rust-lang.org/">用户论坛</a>和<a href="http://stackoverflow.com/questions/tagged/rust">Stack Overflow</a>。</p>
|
||
<h3>本地文档</h3>
|
||
<p>安装程序也包含一份本地文档的拷贝,你可以离线阅读它们。输入<code>rustup doc</code>将在浏览器中打开本地文档。</p>
|
||
<p>任何你太确认标准库提供的类型或函数是干什么的时候,使用文档 API 查找!</p>
|
||
<h2>Hello, World!</h2>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch01-02-hello-world.md">ch01-02-hello-world.md</a>
|
||
<br>
|
||
commit aa1801d99cd3b19c96533f00c852b1c4bd5350a6</p>
|
||
</blockquote>
|
||
<p>现在你已经安装好了 Rust,让我们来编写你的第一个 Rust 程序。当学习一门新语言的时候,编写一个在屏幕上打印 “Hello, world!” 文本的小程序是一个传统,而在这一部分,我们将遵循这个传统。</p>
|
||
<blockquote>
|
||
<p>注意:本书假设你熟悉基本的命令行操作。Rust 本身并不对你的编辑器,工具和你的代码存放在何处有什么特定的要求,所以如果你比起命令行更喜欢 IDE,请随意选择你喜欢的 IDE。</p>
|
||
</blockquote>
|
||
<h3>创建项目文件</h3>
|
||
<p>首先,创建一个文件来编写 Rust 代码。Rust 并不关心你的代码存放在哪里,不过在本书中,我们建议在你的 home 目录创建一个<em>项目</em>目录,并把你的所有项目放在这。打开一个终端并输入如下命令来为这个项目创建一个文件夹:</p>
|
||
<p>Linux 和 Mac:</p>
|
||
<pre><code class="language-sh">$ mkdir ~/projects
|
||
$ cd ~/projects
|
||
$ mkdir hello_world
|
||
$ cd hello_world
|
||
</code></pre>
|
||
<p>Windows:</p>
|
||
<pre><code class="language-cmd">> mkdir %USERPROFILE%\projects
|
||
> cd %USERPROFILE%\projects
|
||
> mkdir hello_world
|
||
> cd hello_world
|
||
</code></pre>
|
||
<h3>编写并运行 Rust 程序</h3>
|
||
<p>接下来,创建一个新的叫做 <em>main.rs</em> 的源文件。Rust 文件总是以 <em>.rs</em> 后缀结尾。如果文件名多于一个单词,使用下划线分隔它们。例如,使用 <em>my_program.rs</em> 而不是 <em>myprogram.rs</em>。</p>
|
||
<p>现在打开刚创建的 <em>main.rs</em> 文件,并输入如下代码:</p>
|
||
<p><span class="filename">Filename: main.rs</span></p>
|
||
<pre><code class="language-rust">fn main() {
|
||
println!("Hello, world!");
|
||
}
|
||
</code></pre>
|
||
<p>保存文件,并回到终端窗口。在 Linux 或 OSX 上,输入如下命令:</p>
|
||
<pre><code class="language-sh">$ rustc main.rs
|
||
$ ./main
|
||
Hello, world!
|
||
</code></pre>
|
||
<p>在 Windows 上,运行<code>.\main.exe</code>而不是<code>./main</code>。不管使用何种系统,你应该在终端看到<code>Hello, world!</code>字符串。如果你做到了,那么恭喜你!你已经正式编写了一个 Rust 程序。你是一名 Rust 程序员了!欢迎入坑。</p>
|
||
<h3>分析 Rust 程序</h3>
|
||
<p>现在,让我们回过头来仔细看看你的“Hello, world!”程序到底发生了什么。这是谜题的第一片:</p>
|
||
<pre><code class="language-rust">fn main() {
|
||
|
||
}
|
||
</code></pre>
|
||
<p>这几行定义了一个 Rust <strong>函数</strong>。<code>main</code> 函数是特殊的:这是每一个可执行的 Rust 程序首先运行的函数(译者注:入口点)。第一行表示“定义一个叫 <code>main</code> 的函数,没有参数也没有返回值。”如果有参数的话,它们应该出现在括号中,<code>(</code>和<code>)</code>。</p>
|
||
<p>同时注意函数体被包裹在大括号中,<code>{</code>和<code>}</code>。Rust 要求所有函数体都位于大括号中(译者注:对比有些语言特定情况可以省略大括号)。将前一个大括号与函数声明置于一行,并留有一个空格被认为是一个好的代码风格。</p>
|
||
<p>在<code>main()</code>函数中:</p>
|
||
<pre><code class="language-rust"> println!("Hello, world!");
|
||
</code></pre>
|
||
<p>这行代码做了这个小程序的所有工作:它在屏幕上打印文本。有很多需要注意的细节。第一个是 Rust 代码风格使用 4 个空格缩进,而不是 1 个制表符(tab)。</p>
|
||
<p>第二个重要的部分是<code>println!()</code>。这叫做 Rust <em>宏</em>,是如何进行 Rust 元编程(metaprogramming)的关键所在。相反如果调用一个函数的话,它应该看起来像这样:<code>println</code>(没有<code>!</code>)。我们将在 24 章更加详细的讨论 Rust 宏,不过现在你只需记住当看到符号 <code>!</code> 的时候,就代表在调用一个宏而不是一个普通的函数。</p>
|
||
<p>接下来,<code>"Hello, world!"</code> 是一个 <em>字符串</em>。我们把这个字符串作为一个参数传递给<code>println!</code>,它负责在屏幕上打印这个字符串。轻松加愉快!(⊙o⊙)</p>
|
||
<p>这一行以一个分号结尾(<code>;</code>)。<code>;</code>代表这个表达式的结束和下一个表达式的开始。大部分 Rust 代码行以<code>;</code>结尾。</p>
|
||
<h3>编译和运行是两个步骤</h3>
|
||
<p>在“编写并运行 Rust 程序”部分,展示了如何运行一个新创建的程序。现在我们将拆分并检查每一步操作。</p>
|
||
<p>在运行一个 Rust 程序之前,必须编译它。可以输入<code>rustc</code>命令来使用 Rust 编译器并像这样传递你源文件的名字:</p>
|
||
<pre><code class="language-sh">$ rustc main.rs
|
||
</code></pre>
|
||
<p>如果你来自 C 或 C++ 背景,你会发现这与<code>gcc</code>和<code>clang</code>类似。编译成功后,Rust 应该会输出一个二进制可执行文件,在 Linux 或 OSX 上在 shell 中你可以通过<code>ls</code>命令看到如下:</p>
|
||
<pre><code class="language-sh">$ ls
|
||
main main.rs
|
||
</code></pre>
|
||
<p>在 Windows 上,输入:</p>
|
||
<pre><code class="language-cmd">> dir /B %= the /B option says to only show the file names =%
|
||
main.exe
|
||
main.rs
|
||
</code></pre>
|
||
<p>这表示我们有两个文件:<em>.rs</em> 后缀的源文件,和可执行文件(在 Windows下是 <em>main.exe</em>,其它平台是 <em>main</em>)。这里剩下的操作就只有运行 <em>main</em> 或 <em>main.exe</em> 文件了,像这样:</p>
|
||
<pre><code class="language-sh">$ ./main # or .\main.exe on Windows
|
||
</code></pre>
|
||
<p>如果 <em>main.rs</em> 是我们的“Hello, world!”程序,它将会在终端上打印<code>Hello, world!</code>。</p>
|
||
<p>来自 Ruby、Python 或 JavaScript 这样的动态类型语言背景的同学,可能不太习惯在分开的步骤编译和执行程序。Rust 是一种 <em>静态提前编译语言</em>(<em>ahead-of-time compiled language</em>),这意味着可以编译好程序后,把它给任何人,他们都不需要安装 Rust 就可运行。如果你给他们一个 <code>.rb</code> , <code>.py</code> 或 <code>.js</code> 文件,他们需要先分别安装 Ruby,Python,JavaScript 实现(运行时环境,VM),不过你只需要一句命令就可以编译和执行程序。这一切都是语言设计的权衡取舍。</p>
|
||
<p>仅仅使用<code>rustc</code>编译简单程序是没问题的,不过随着项目的增长,你将想要能够控制你项目拥有的所有选项,并使其易于分享你的代码给别人或别的项目。接下来,我们将介绍一个叫做 Cargo 的工具,它将帮助你编写现实生活中的 Rust 程序。</p>
|
||
<h2>Hello, Cargo!</h2>
|
||
<p>Cargo 是 Rust 的构建系统和包管理工具,同时 Rustacean 们使用 Cargo 来管理它们的 Rust 项目,因为它使得很多任务变得更轻松。例如,Cargo负责构建代码、下载代码依赖的库并编译这些库。我们把代码需要的库叫做 <em>依赖</em>(<em>dependencies</em>)。</p>
|
||
<p>最简单的 Rust 程序,例如我们刚刚编写的,并没有任何依赖,所以目前我们只使用了 Cargo 负责构建代码的部分。随着你编写更加复杂的 Rust 程序,你会想要添加依赖,那么如果你使用 Cargo 开始的话,这将会变得简单许多。</p>
|
||
<p>因为绝大部分 Rust 项目使用 Cargo,本书接下来的部分将假设你使用它。如果使用安装章节介绍的官方安装包的话,Rust 自带 Cargo。如果通过其他方式安装 Rust 的话,可以在终端输入如下命令检查是否安装了 Cargo:</p>
|
||
<pre><code class="language-sh">$ cargo --version
|
||
</code></pre>
|
||
<p>如果看到了版本号,一切 OK!如果出现一个类似“<code>command not found</code>”的错误,那么你应该查看安装方式的文档来确定如何单独安装 Cargo。</p>
|
||
<h3>使用 Cargo 创建项目</h3>
|
||
<p>让我们使用 Cargo 来创建一个新项目并看看与<code>hello_world</code>项目有什么不同。回到项目目录(或者任何你决定放置代码的目录):</p>
|
||
<p>Linux 和 Mac:</p>
|
||
<pre><code class="language-sh">$ cd ~/projects
|
||
</code></pre>
|
||
<p>Windows:</p>
|
||
<pre><code class="language-cmd">> cd %USERPROFILE%\projects
|
||
</code></pre>
|
||
<p>并在任何操作系统运行:</p>
|
||
<pre><code class="language-sh">$ cargo new hello_cargo --bin
|
||
$ cd hello_cargo
|
||
</code></pre>
|
||
<p>我们向<code>cargo new</code>传递了<code>--bin</code>因为我们的目标是生成一个可执行程序,而不是一个库。可执行文件是二进制可执行文件,通常就叫做 <em>二进制文件</em>(<em>binaries</em>)。项目的名称被定为<code>hello_cargo</code>,同时 Cargo 在一个同名(子)目录中创建它的文件,接着我们可以进入查看。</p>
|
||
<p>如果列出 <em>hello_cargo</em> 目录中的文件,我们将会看到 Cargo 生成了两个文件和一个目录:一个 <em>Cargo.toml</em> 文件和一个 <em>src</em> 目录,<em>main.rs</em> 文件位于目录中。它也在 <em>hello_cargo</em> 目录初始化了一个 git 仓库,以及一个 <em>.gitignore</em> 文件;你可以改为使用不同的版本控制系统,或者不使用,通过<code>--vcs</code>参数。</p>
|
||
<p>使用你选择的文本编辑器(IDE)打开 <em>Cargo.toml</em> 文件。它应该看起来像这样:</p>
|
||
<p><span class="filename">Filename: Cargo.toml</span></p>
|
||
<pre><code class="language-toml">[package]
|
||
name = "hello_cargo"
|
||
version = "0.1.0"
|
||
authors = ["Your Name <you@example.com>"]
|
||
|
||
[dependencies]
|
||
</code></pre>
|
||
<p>这个文件使用<a href="https://github.com/toml-lang/toml"><em>TOML</em></a><!-- ignore --> (Tom's Obvious, Minimal Language) 格式。TOML 类似于 INI,不过有一些额外的改进之处,并且被用作 Cargo 的配置文件的格式。</p>
|
||
<p>第一行,<code>[package]</code>,是一个部分标题表明下面的语句用来配置一个包。随着我们在这个文件增加更多的信息,我们还会增加其他部分。</p>
|
||
<p>最后一行,<code>[dependencies]</code>,是列出项目依赖的 <em>crates</em>(我们这么称呼 Rust 代码的包)的部分的开始,这样 Cargo 也就知道去下载和编译它们。这个项目并不需要任何其他的 crate,不过在猜猜看教程章节会需要。</p>
|
||
<p>现在看看 <em>src/main.rs</em>:</p>
|
||
<pre><code class="language-rust">fn main() {
|
||
println!("Hello, world!");
|
||
}
|
||
</code></pre>
|
||
<p>Cargo 为你生成了一个“Hello World!”,正如我们之前编写的那个!目前为止我们所见过的之前项目与 Cargo 生成的项目区别有:</p>
|
||
<ul>
|
||
<li>代码位于 <em>src</em> 目录</li>
|
||
<li>项目根目录包含一个 <em>Cargo.toml</em> 配置文件</li>
|
||
</ul>
|
||
<p>Cargo 期望源文件位于 src 目录,这样将项目根目录留给 README、license 信息、配置文件和其他跟代码无关的文件。这样,Cargo 帮助你保持项目干净整洁。一切井井有条。</p>
|
||
<p>如果没有使用 Cargo 开始项目,正如我们在 <em>hello_world</em> 目录中的项目,可以把它转化为一个 Cargo 使用的项目,通过将代码放入 <em>src</em> 目录并创建一个合适的 <em>Cargo.toml</em>。</p>
|
||
<h3>构建并运行 Cargo 项目</h3>
|
||
<p>现在让我们看看通过 Cargo 构建和运行 Hello World 程序有什么不同。为此,我们输入如下命令:</p>
|
||
<pre><code>$ cargo build
|
||
Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
|
||
</code></pre>
|
||
<p>这应该创建 <em>target/debug/hello_cargo</em>(或者在 Windows 上是 <em>target\debug\hello_cargo.exe</em>)可执行文件,可以通过这个命令运行:</p>
|
||
<pre><code class="language-sh">$ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows
|
||
Hello, world!
|
||
</code></pre>
|
||
<p>好的!如果一切顺利,<code>Hello, world!</code>应该再次打印在终端上。</p>
|
||
<p>第一次运行的时候也会使 Cargo 在项目根目录创建一个叫做 <em>Cargo.lock</em> 的新文件,它看起来像这样:</p>
|
||
<p><span class="filename">Filename: Cargo.lock</span></p>
|
||
<pre><code class="language-toml">[root]
|
||
name = "hello_cargo"
|
||
version = "0.1.0"
|
||
</code></pre>
|
||
<p>Cargo 使用 <em>Cargo.lock</em> 来记录程序的依赖。这个项目并没有依赖,所以内容有一点稀少。事实上,你自己永远也不需要碰这个文件;仅仅让 Cargo 处理它就行了。</p>
|
||
<p>我们刚刚使用<code>cargo build</code>构建了项目并使用<code>./target/debug/hello_cargo</code>运行了它,不过也可以使用<code>cargo run</code>编译并运行:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Running `target/debug/hello_cargo`
|
||
Hello, world!
|
||
</code></pre>
|
||
<p>注意这一次,并没有出现告诉我们 Cargo 正在编译 <code>hello_cargo</code> 的输出。Cargo 发现文件并没有被改变,所以只是运行了二进制文件。如果修改了源文件的话,将会出现像这样的输出:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
|
||
Running `target/debug/hello_cargo`
|
||
Hello, world!
|
||
</code></pre>
|
||
<p>所以又出现一些更多的不同:</p>
|
||
<ul>
|
||
<li>使用<code>cargo build</code>构建项目(或使用<code>cargo run</code>一步构建并运行),而不是使用<code>rustc</code></li>
|
||
<li>不同于将构建结果放在源码相同目录,Cargo 会将它放到 <em>target/debug</em> 目录中的文件,我们将会看到</li>
|
||
</ul>
|
||
<p>Cargo 的另一个有点是不管你使用什么操作系统它的命令都是一样的,所以之后我们将不再为 Linux 和 Mac 以及 Windows 提供特定的命令。</p>
|
||
<h3>发布构建</h3>
|
||
<p>当项目最终准备好发布了,可以使用<code>cargo build --release</code>来优化编译项目。这会在 <em>target/release</em> 下生成可执行文件,而不是 <em>target/debug</em>。这些优化可以让 Rust 代码运行的更快,不过启用他们会让程序花更长的时间编译。这也是为何这是两种不同的配置:一个为了开发,这时你经常想要快速重新构建;另一个构建提供给用户的最终程序,这时并不会重新构建并希望能运行得越快越好。如果你在测试代码的运行时间,请确保运行<code>cargo build --release</code>并使用 <em>target/release</em> 下的可执行文件进行测试。</p>
|
||
<h3>把 Cargo 当作习惯</h3>
|
||
<p>对于简单项目, Cargo 并不能比<code>rustc</code>提供更多的价值,不过随着开发的进行终将体现它的价值。对于拥有多个 crate 的复杂项目,可以仅仅运行<code>cargo build</code>,然后一切将有序运行。即便这个项目很简单,现在它使用了很多接下来你 Rust 程序生涯将会用到的实用工具。事实上,无形中你可以使用下面的命令开始所有你想要从事的项目:</p>
|
||
<pre><code class="language-sh">$ git clone someurl.com/someproject
|
||
$ cd someproject
|
||
$ carg
|
||
</code></pre>
|
||
<blockquote>
|
||
<p>注意:如果你想要查看 Cargo 的更多细节,请阅读官方的 <a href="http://doc.crates.io/guide.html">Cargo guide</a>,它覆盖了其所有的功能。</p>
|
||
</blockquote>
|
||
<h1>猜猜看</h1>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch02-00-guessing-game-tutorial.md">ch02-00-guessing-game-tutorial.md</a>
|
||
<br>
|
||
commit 77370c073661548dd56bbcb43cc64713585acbba</p>
|
||
</blockquote>
|
||
<p>让我们通过自己动手的方式一起完成一个项目来快速上手 Rust!本章通过展示如何在真实的项目中运用的方式向你介绍一些常用的 Rust 概念。你将会学到<code>let</code>、<code>match</code>、方法、关联函数、使用外部 crate 等更多的知识!接下来的章节会探索这些概念的细节。在这一章,我们练习基础。</p>
|
||
<p>我们会实现一个经典新手编程问题:猜猜看游戏。它是这么工作的:程序将会随机生成一个 1 到 100 之间的随机整数。接着它会提示玩家输入一个猜测。当输入了一个猜测后,它会告诉提示猜测是太大了还是太小了。猜对了,它会打印出祝贺并退出。</p>
|
||
<h2>准备一个新项目</h2>
|
||
<p>要创建一个新项目,进入你在第一章创建的<em>项目</em>目录,并使用 Cargo 创建它,像这样:</p>
|
||
<pre><code class="language-sh">$ cargo new guessing_game --bin
|
||
$ cd guessing_game
|
||
</code></pre>
|
||
<p>第一个命令,<code>cargo new</code>,获取项目的名称(<code>guessing_game</code>)作为第一个参数。<code>--bin</code>参数告诉 Cargo 创建一个二进制项目,与第一章类似。第二个命令进入到新创建的项目目录。</p>
|
||
<p>看一样生成的 <em>Cargo.toml</em> 文件:</p>
|
||
<p><span class="filename">Filename: Cargo.toml</span></p>
|
||
<pre><code class="language-toml">[package]
|
||
name = "guessing_game"
|
||
version = "0.1.0"
|
||
authors = ["Your Name <you@example.com>"]
|
||
|
||
[dependencies]
|
||
</code></pre>
|
||
<p>如果 Cargo 从环境中获取的作者信息不正确,修改这个文件并再次保存。</p>
|
||
<p>正如第一章那样,<code>cargo new</code>生成了一个“Hello, world!”程序。查看 <em>src/main.rs</em> 文件:</p>
|
||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||
<pre><code class="language-rust">fn main() {
|
||
println!("Hello, world!");
|
||
}
|
||
</code></pre>
|
||
<p>现在让我们使用<code>cargo run</code>在相同的步骤编译并运行这个“Hello, world!”程序:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
Running `target/debug/guessing_game`
|
||
Hello, world!
|
||
</code></pre>
|
||
<p><code>run</code>命令在你需要快速迭代项目时就派上用场了,而这个游戏就正是这么一个项目:我们需要在进行下一步之前快速测试每次迭代。</p>
|
||
<p>重新打开 <em>src/main.rs</em> 文件。我们将会在这个文件编写全部的代码。</p>
|
||
<h2>处理一次猜测</h2>
|
||
<p>程序的第一部分会请求用户输入,处理输入,并检查输入是否为期望的形式。首先,允许玩家输入一个猜测。在 <em>src/main.rs</em> 中输入列表 2-1 中的代码。</p>
|
||
<figure>
|
||
<span class="filename">Filename: src/main.rs</span>
|
||
<pre><code class="language-rust,ignore">use std::io;
|
||
|
||
fn main() {
|
||
println!("Guess the number!");
|
||
|
||
println!("Please input your guess.");
|
||
|
||
let mut guess = String::new();
|
||
|
||
io::stdin().read_line(&mut guess)
|
||
.expect("Failed to read line");
|
||
|
||
println!("You guessed: {}", guess);
|
||
}
|
||
</code></pre>
|
||
<figcaption>
|
||
<p>Listing 2-1: Code to get a guess from the user and print it out</p>
|
||
</figcaption>
|
||
</figure>
|
||
<p>这些代码包含很多信息,所以让我们一点一点地过一遍。为了获取用户输入并接着打印结果作为输出,我们需要从标准库(被称为<code>std</code>)中引用<code>io</code>(输入/输出)库:</p>
|
||
<pre><code class="language-rust,ignore">use std::io;
|
||
</code></pre>
|
||
<p>Rust 默认只在每个程序的 <a href="https://doc.rust-lang.org/std/prelude/"><em>prelude</em></a><!-- ignore --> 中引用很少的一些类型。如果想要使用的类型并不在 prelude 中,你必须使用一个<code>use</code>语句显式的将其导入到程序中。使用<code>std::io</code>库将提供很多<code>io</code>相关的功能,接受用户输入的功能。</p>
|
||
<p>正如第一章所讲,<code>main</code>函数是程序的入口点:</p>
|
||
<pre><code class="language-rust,ignore">fn main() {
|
||
</code></pre>
|
||
<p><code>fn</code>语法声明了一个新函数,<code>()</code>表明没有参数,<code>{</code>作为函数体的开始。</p>
|
||
<p>第一章也讲到了,<code>println!</code>是一个在屏幕上打印字符串的宏:</p>
|
||
<pre><code class="language-rust,ignore">println!("Guess the number!");
|
||
|
||
println!("Please input your guess.");
|
||
</code></pre>
|
||
<p>这些代码仅仅打印一个提示,说明游戏的内容并请求用户输入。</p>
|
||
<h3>用变量储存值</h3>
|
||
<p>接下来,创建一个地方储存用户输入,像这样:</p>
|
||
<pre><code class="language-rust,ignore">let mut guess = String::new();
|
||
</code></pre>
|
||
<p>现在程序开始变得有意思了!这一小行代码发生了很多事。注意这是一个<code>let</code>语句,用来创建 <em>变量</em>。这里是另外一个例子:</p>
|
||
<pre><code class="language-rust,ignore">let foo = bar;
|
||
</code></pre>
|
||
<p>这行代码会创建一个叫做<code>foo</code>的新变量并把它绑定到值<code>bar</code>上。在 Rust 中,变量默认是不可变的。下面的例子展示了如何在变量名前使用<code>mut</code>来使一个变量可变:</p>
|
||
<pre><code class="language-rust">let foo = 5; // immutable
|
||
let mut bar = 5; // mutable
|
||
</code></pre>
|
||
<blockquote>
|
||
<p>注意:<code>//</code> 开始一个注释,它持续到本行的结尾。Rust 忽略注释中的所有内容。</p>
|
||
</blockquote>
|
||
<p>现在我们知道了<code>let mut guess</code>会引入一个叫做<code>guess</code>的可变变量。等号(<code>=</code>)的另一边是<code>guess</code>所绑定的值,它是<code>String::new</code>的结果,这个函数会返回一个<code>String</code>的新实例。<a href="../std/string/struct.String.html"><code>String</code></a><!-- ignore -->是一个标准库提供的字符串类型,它是可增长的、UTF-8 编码的文本块。</p>
|
||
<p><code>::new</code>那一行的<code>::</code>语法表明<code>new</code>是<code>String</code>类型的一个 <em>关联函数</em>(<em>associated function</em>)。关联函数是针对类型实现的,在这个例子中是<code>String</code>,而不是<code>String</code>的某个特定实例。一些语言中把它称为 <em>静态方法</em>(<em>static method</em>)。</p>
|
||
<p><code>new</code>函数创建了一个新的空的<code>String</code>,你会在很多类型上发现<code>new</code>函数,因为这是创建某个类型新值的常用函数名。</p>
|
||
<p>总结一下,<code>let mut guess = String::new();</code>这一行创建了一个可变变量,目前它绑定到一个<code>String</code>新的、空的实例上。哟!</p>
|
||
<p>回忆一下我们在程序的第一行使用<code>use std::io;</code>从标准库中引用输入/输出功能。现在在<code>io</code>上调用一个关联函数,<code>stdin</code>:</p>
|
||
<pre><code class="language-rust,ignore">io::stdin().read_line(&mut guess)
|
||
.expect("Failed to read line");
|
||
</code></pre>
|
||
<p>如果我们在程序的开头没有<code>use std::io</code>这一行,我们可以把函数调用写成<code>std::io::stdin</code>这样。<code>stdin</code>函数返回一个 <a href="../std/io/struct.Stdin.html"><code>std::io::Stdin</code></a><!-- ignore -->的实例,这是一个代表终端标准输入句柄的类型。</p>
|
||
<p>代码的下一部分,<code>.read_line(&mut guess)</code>,调用 <a href="../std/io/struct.Stdin.html#method.read_line"><code>read_line</code></a><!-- ignore --> 方法从标准输入句柄获取用户输入。我们还向<code>read_line()</code>传递了一个参数:<code>&mut guess</code>。</p>
|
||
<p><code>read_line</code>的工作是把获取任何用户键入到标准输入的字符并放入一个字符串中,所以它获取字符串作为一个参数。这个字符串需要是可变的,这样这个方法就可以通过增加用户的输入来改变字符串的内容。</p>
|
||
<p><code>&</code>表明这个参数是一个 <em>引用</em>(<em>reference</em>),它提供了一个允许多个不同部分的代码访问同一份数据而不需要在内存中多次拷贝的方法。引用是一个复杂的功能,而 Rust 的一大优势就是它是安全而优雅操纵引用。完成这个程序并不需要知道这么多细节:第四章会更全面的解释引用。现在,我们只需知道它像变量一样,默认是不可变的。因此,需要写成<code>&mut guess</code>而不是<code>&guess</code>来使其可变。</p>
|
||
<p>这行代码还没有分析完。虽然这是单独一行代码,但它只是一个逻辑上代码行(虽然换行了但仍是一个语句)的第一部分。第二部分是这个方法:</p>
|
||
<pre><code class="language-rust,ignore">.expect("Failed to read line");
|
||
</code></pre>
|
||
<p>当使用<code>.foo()</code>语法调用方法时,明智的选择是换行并留出空白(缩进)来把长的代码行拆开。我们可以把代码写成这样:</p>
|
||
<pre><code class="language-rust,ignore">io::stdin().read_line(&mut guess).expect("Failed to read line");
|
||
</code></pre>
|
||
<p>不过,过长的代码行难以阅读,所以最好拆开来写,两行代码两个方法调用。现在来看看这行代码干了什么。</p>
|
||
<h3>使用<code>Result</code>类型来处理潜在的错误</h3>
|
||
<p>之前提到过,<code>read_line</code>将用户输入放入到传递给它字符串中,不过它也返回一个值————一个<a href="../std/io/type.Result.html"><code>io::Result</code></a><!-- ignore -->。Rust 标准库中有很多叫做<code>Result</code>的类型。一个<a href="../std/result/enum.Result.html"><code>Result</code></a><!-- ignore -->泛型以及对应子模块的特定版本,比如<code>io::Result</code>。</p>
|
||
<p><code>Result</code>类型是 <a href="ch06-00-enums.html"><em>枚举</em>(<em>enumerations</em>)</a><!-- ignore -->,通常也写作 <em>enums</em>。枚举拥有固定值集合的类型,而这些值被称为枚举的 <em>成员</em>(<em>variants</em>)。第六章会更详细的介绍枚举。</p>
|
||
<p>对于<code>Result</code>,它的成员是<code>Ok</code>或<code>Err</code>,<code>Ok</code>表明操作成功了,同时<code>Ok</code>成员之中包含成功生成的值。<code>Err</code>意味着操作失败,<code>Err</code>之中包含操作是为什么或如何失败的信息。</p>
|
||
<p><code>Result</code>类型的作用是编码错误处理信息。<code>Result</code>类型的值,正如其他任何类型,拥有定义于其上的方法。<code>io::Result</code>的实例拥有<a href="../std/result/enum.Result.html#method.expect"><code>expect</code>方法</a><!-- ignore -->可供调用。如果<code>io::Result</code>实例的值是<code>Err</code>,<code>expect</code>会导致程序崩溃并显示显示你作为参数传递给<code>expect</code>的信息。如果<code>io::Result</code>实例的值是<code>Ok</code>,<code>expect</code>会获取<code>Ok</code>中的值并原原本本的返回给你,这样就可以使用它了。在本例中,返回值是用户输入到标准输入的一些字符。</p>
|
||
<p>如果不使用<code>expect</code>,程序也能编译,不过会出现一个警告:</p>
|
||
<pre><code class="language-sh">$ cargo build
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
src/main.rs:10:5: 10:39 warning: unused result which must be used,
|
||
#[warn(unused_must_use)] on by default
|
||
src/main.rs:10 io::stdin().read_line(&mut guess);
|
||
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
</code></pre>
|
||
<p>Rust 警告说我们没有使用<code>read_line</code>返回的值<code>Result</code>,表明程序没有处理一个可能的错误。消除警告的正确方式是老实编写错误处理,不过因为我们仅仅希望程序出现问题就崩溃,可以使用<code>expect</code>。你会在第九章学习从错误中恢复。</p>
|
||
<h3>使用<code>println!</code>占位符打印值</h3>
|
||
<p>除了位于结尾的大括号,目前为止编写的代码就只有一行代码值得讨论一下了,就是这一行:</p>
|
||
<pre><code class="language-rust,ignore">println!("You guessed: {}", guess);
|
||
</code></pre>
|
||
<p>这行代码打印出存储了用户输入的字符串。这对<code>{}</code>是一个在特定位置预留值的占位符。可以使用<code>{}</code>打印多个值:第一个<code>{}</code>对应格式化字符串之后列出的第一个值,第二个对应第二个值,以此类推。用一个<code>println!</code>调用打印多个值应该看起来像这样:</p>
|
||
<pre><code class="language-rust">let x = 5;
|
||
let y = 10;
|
||
|
||
println!("x = {} and y = {}", x, y);
|
||
</code></pre>
|
||
<p>这行代码会打印出<code>x = 5 and y = 10</code>。</p>
|
||
<h3>测试第一部分代码</h3>
|
||
<p>让我们来测试下猜猜看游戏的第一部分。使用<code>cargo run</code>运行它:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
Running `target/debug/guessing_game`
|
||
Guess the number!
|
||
Please input your guess.
|
||
6
|
||
You guessed: 6
|
||
</code></pre>
|
||
<p>至此为止,游戏的第一部分已经完成:我们从键盘获取了输入并打印了出来。</p>
|
||
<h2>生成一个秘密数字</h2>
|
||
<p>接下来,需要生成一个秘密数字,用户会尝试猜测它。秘密数字应该每次都不同,这样多玩几次才会有意思。生成一个 1 到 100 之间的随机数这样游戏也不会太难。Rust 标准库中还未包含随机数功能。然而,Rust 团队确实提供了一个<a href="https://crates.io/crates/rand"><code>rand</code> crate</a>。</p>
|
||
<h2>使用 crate 来增加更多功能</h2>
|
||
<p>记住 <em>crate</em> 是一个 Rust 代码的包。我们正在构建的项目是一个 <em>二进制 crate</em>,它生成一个可执行文件。 <code>rand</code> crate 是一个 <em>库 crate</em>,它包含意在被其他程序使用的代码。</p>
|
||
<p>Cargo 对外部 crate 的运用是其真正闪光的地方。在我们可以使用<code>rand</code>编写代码之前,需要编辑 <em>Cargo.toml</em> 来包含<code>rand</code>作为一个依赖。现在打开这个文件并在<code>[dependencies]</code>部分标题(Cargo 为你创建了它)的下面添加如下代码:</p>
|
||
<p><span class="filename">Filename: Cargo.toml</span></p>
|
||
<pre><code class="language-toml">[dependencies]
|
||
|
||
rand = "0.3.14"
|
||
</code></pre>
|
||
<p>在 <em>Cargo.toml</em> 文件中,任何标题之后的内容都是属于这个部分的,一直持续到直到另一个部分开始。<code>[dependencies]</code>部分告诉 Cargo 项目依赖了哪个外部 crate 和需要的 crate 版本。在这个例子中,我们使用语义化版本符号<code>0.3.14</code>来指定<code>rand</code>crate。Cargo 理解<a href="http://semver.org">语义化版本(Semantic Versioning)</a><!-- ignore -->(有时也称为 <em>SemVer</em>),这是一个编写版本号的标准。版本号<code>0.3.14</code>事实上是<code>^0.3.14</code>的缩写,它的意思是“任何与 0.3.14 版本公有 API 相兼容的版本”。</p>
|
||
<p>现在,不用修改任何代码,构建项目,如列表 2-2:</p>
|
||
<figure>
|
||
<pre><code class="language-text">$ cargo build
|
||
Updating registry `https://github.com/rust-lang/crates.io-index`
|
||
Downloading rand v0.3.14
|
||
Downloading libc v0.2.14
|
||
Compiling libc v0.2.14
|
||
Compiling rand v0.3.14
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
</code></pre>
|
||
<figcaption>
|
||
<p>Listing 2-2: The output from running <code>cargo build</code> after adding the rand crate
|
||
as a dependency</p>
|
||
</figcaption>
|
||
</figure>
|
||
<p>可能会出现不同的版本号(不过多亏了语义化版本,它们与代码是兼容的!),同时显示顺序也可能会有所不同。</p>
|
||
<p>现在我们有了一个外部依赖,Cargo 从 <em>registry</em> (<a href="https://crates.io">Crates.io</a>)上获取了一份(兼容的)最新版本代码的拷贝。Crates.io 是 Rust 生态环境中的人们向他人贡献他们的开源 Rust 项目的地方。</p>
|
||
<p>在更新完 registry (索引)后,Cargo 检查<code>[dependencies]</code>部分并下载还不存在部分。在这个例子中,虽然只列出了<code>rand</code>一个依赖,Cargo 也获取了一份<code>libc</code>的拷贝,因为<code>rand</code>依赖<code>libc</code>来正常工作。在下载他们之后,Rust 编译他们接着用这些依赖编译项目。</p>
|
||
<p>如果不做任何修改就立刻再次运行<code>cargo build</code>,则不会有任何输出。Cargo 知道它已经下载并编译了依赖,同时 <em>Cargo.toml</em> 文件中也没有任何相关修改。Cargo 也知道代码没有做任何修改,所以它也不会重新编译代码。因为无事可做,它简单的退出了。如果打开 <em>src/main.rs</em> 文件,并做一些普通的修改,保存并再次构建,只会出现一行输出:</p>
|
||
<pre><code class="language-sh">$ cargo build
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
</code></pre>
|
||
<p>这一行表明 Cargo 只构建了对 <em>src/main.rs</em> 文件做出的微小修改。依赖没有被修改,所以 Cargo 知道可以复用已经为此下载并编译的代码。它只是重新构建了部分(项目)代码。</p>
|
||
<h4>The <em>Cargo.lock</em> 文件确保构建是可重现的</h4>
|
||
<p>Cargo 有一个机制来确保每次任何人重新构建代码都会生成相同的成品:Cargo 只会使用你指定的依赖的版本,除非你又手动指定了别的。例如,如果下周<code>rand</code> crate 的<code>v0.3.15</code>版本出来了,而它包含一个重要的 bug 修改并也含有一个会破坏代码运行的缺陷的时候会发生什么呢?</p>
|
||
<p>这个问题的答案是 <em>Cargo.lock</em> 文件,它在第一次运行<code>cargo build</code>时被创建并位于 <em>guessing_game</em> 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并接着写入 <em>Cargo.lock</em> 文件中。当将来构建项目时,Cargo 发现 <em>Cargo.lock</em> 存在就会使用这里指定的版本,而不是重新进行所有版本的计算。这使得你拥有了一个自动的可重现的构建。换句话说,项目会继续使用<code>0.3.14</code>直到你显式升级,多亏了 <em>Cargo.lock</em> 文件。我们将会在这个文件编写全部的代码。</p>
|
||
<h4>更新 crate 到一个新版本</h4>
|
||
<p>当你<em>确实</em>需要升级 crate 时,Cargo 提供了另一个命令,<code>update</code>,他会:</p>
|
||
<ol>
|
||
<li>忽略 <em>Cargo.lock</em> 文件并计算出所有符合 <em>Cargo.toml</em> 中规格的最新版本。</li>
|
||
<li>如果成功了,Cargo 会把这些版本写入 <em>Cargo.lock</em> 文件。</li>
|
||
</ol>
|
||
<p>不过,Cargo 默认只会寻找大于<code>0.3.0</code>而小于<code>0.4.0</code>的版本。如果<code>rand</code> crate 发布了两个新版本,<code>0.3.15</code>和<code>0.4.0</code>,在运行<code>cargo update</code>时会出现如下内容:</p>
|
||
<pre><code class="language-sh">$ cargo update
|
||
Updating registry `https://github.com/rust-lang/crates.io-index`
|
||
Updating rand v0.3.14 -> v0.3.15
|
||
</code></pre>
|
||
<p>这时,值得注意的是 <em>Cargo.lock</em> 文件中的一个改变,<code>rand</code> crate 现在使用的版本是<code>0.3.15</code>。</p>
|
||
<p>如果想要使用<code>0.4.0</code>版本的<code>rand</code>或是任何<code>0.4.x</code>系列的版本,必须像这样更新 <em>Cargo.toml</em> 文件:</p>
|
||
<pre><code class="language-toml">[dependencies]
|
||
|
||
rand = "0.4.0"
|
||
</code></pre>
|
||
<p>下一次运行<code>cargo build</code>时,Cargo 会更新 registry 中可用的 crate 并根据你指定新版本重新计算<code>rand</code>的要求。</p>
|
||
<p>第十四章会讲到<a href="http://doc.crates.io">Cargo</a><!-- ignore --> 和<a href="http://doc.crates.io/crates-io.html">它的生态系统</a><!-- ignore -->的更多内容,不过目前你只需要了解这么多。Cargo 使得复用库文件变得非常容易,所以 Rustacean 们能够通过组合很多包来编写出更轻巧的项目。</p>
|
||
<h3>生成一个随机数</h3>
|
||
<p>让我们开始<em>使用</em><code>rand</code>。下一步是更新 <em>src/main.rs</em>,如列表 2-3:</p>
|
||
<figure>
|
||
<span class="filename">Filename: src/main.rs</span>
|
||
<pre><code class="language-rust,ignore">extern crate rand;
|
||
|
||
use std::io;
|
||
use rand::Rng;
|
||
|
||
fn main() {
|
||
println!("Guess the number!");
|
||
|
||
let secret_number = rand::thread_rng().gen_range(1, 101);
|
||
|
||
println!("The secret number is: {}", secret_number);
|
||
|
||
println!("Please input your guess.");
|
||
|
||
let mut guess = String::new();
|
||
|
||
io::stdin().read_line(&mut guess)
|
||
.expect("Failed to read line");
|
||
|
||
println!("You guessed: {}", guess);
|
||
}
|
||
</code></pre>
|
||
<figcaption>
|
||
<p>Listing 2-3: Code changes needed in order to generate a random number</p>
|
||
</figcaption>
|
||
</figure>
|
||
<p>我们在顶部增加一行<code>extern crate rand;</code>来让 Rust 知道我们要使用外部依赖。这也会调用相应的<code>use rand</code>,所以现在可以使用<code>rand::</code>前缀来调用<code>rand</code>中的任何内容。</p>
|
||
<p>接下来,我们增加了另一行<code>use</code>:<code>use rand::Rng</code>。<code>Rng</code>是一个定义了随机数生成器应实现方法的 trait,如果要使用这些方法的话这个 trait 必须在作用域中。第十章会详细介绍 trait。</p>
|
||
<p>另外,中间还新增加了两行。<code>rand::thread_rng</code>函数会提供具体会使用的随机数生成器:它位于当前执行线程本地并从操作系统获取 seed。接下来,调用随机数生成器的<code>gen_range</code>方法。这个方法由我们使用<code>use rand::Rng</code>语句引入到作用域的<code>Rng</code> trait 定义。<code>gen_range</code>方法获取两个数作为参数并生成一个两者之间的随机数。它包含下限但不包含上限,所以需要指定<code>1</code>和<code>101</code>来请求一个<code>1</code>和<code>100</code>之间的数。</p>
|
||
<p>并不仅仅能够知道该引用哪个 trait 和该从 crate 中使用哪个方法。如何使用 crate 的说明在每个 crate 的文档中。Cargo 另一个很棒的功能是可以运行<code>cargo doc --open</code>命令来构建所有本地依赖提供的文档并在浏览器中打开。例如,如果你对<code>rand</code> crate 中的其他功能感兴趣,运行<code>cargo doc --open</code>并点击左侧导航栏的<code>rand</code>。</p>
|
||
<p>新增加的第二行代码打印出了秘密数字。这在开发程序时很有用,因为我们可以去测试它,不过在最终版本我们会删掉它。游戏一开始就打印出结果就没什么可玩的了!</p>
|
||
<p>尝试运行程序几次:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
Running `target/debug/guessing_game`
|
||
Guess the number!
|
||
The secret number is: 7
|
||
Please input your guess.
|
||
4
|
||
You guessed: 4
|
||
$ cargo run
|
||
Running `target/debug/guessing_game`
|
||
Guess the number!
|
||
The secret number is: 83
|
||
Please input your guess.
|
||
5
|
||
You guessed: 5
|
||
</code></pre>
|
||
<p>你应该能得到不同的随机数,同时他们应该都是在 1 和 100 之间的。干得漂亮!</p>
|
||
<h2>比较猜测与秘密数字</h2>
|
||
<p>现在有了用户输入和一个随机数,我们可以比较他们。这个步骤如列表 2-4:</p>
|
||
<figure>
|
||
<span class="filename">Filename: src/main.rs</span>
|
||
<pre><code class="language-rust,ignore">extern crate rand;
|
||
|
||
use std::io;
|
||
use std::cmp::Ordering;
|
||
use rand::Rng;
|
||
|
||
fn main() {
|
||
println!("Guess the number!");
|
||
|
||
let secret_number = rand::thread_rng().gen_range(1, 101);
|
||
|
||
println!("The secret number is: {}", secret_number);
|
||
|
||
println!("Please input your guess.");
|
||
|
||
let mut guess = String::new();
|
||
|
||
io::stdin().read_line(&mut guess)
|
||
.expect("Failed to read line");
|
||
|
||
println!("You guessed: {}", guess);
|
||
|
||
match guess.cmp(&secret_number) {
|
||
Ordering::Less => println!("Too small!"),
|
||
Ordering::Greater => println!("Too big!"),
|
||
Ordering::Equal => println!("You win!"),
|
||
}
|
||
}
|
||
</code></pre>
|
||
<figcaption>
|
||
<p>Listing 2-4: Handling the possible return values of comparing two numbers</p>
|
||
</figcaption>
|
||
</figure>
|
||
<p>新代码的第一行是另一个<code>use</code>,从标准库引入了一个叫做<code>std::cmp::Ordering</code>的类型到作用域。<code>Ordering</code>是另一个枚举,像<code>Result</code>一样,不过<code>Ordering</code>的成员是<code>Less</code>、<code>Greater</code>和<code>Equal</code>。这是你比较两个值时可能出现三种结果。</p>
|
||
<p>接着在底部的五行新代码使用了<code>Ordering</code>类型:</p>
|
||
<pre><code class="language-rust,ignore">match guess.cmp(&secret_number) {
|
||
Ordering::Less => println!("Too small!"),
|
||
Ordering::Greater => println!("Too big!"),
|
||
Ordering::Equal => println!("You win!"),
|
||
}
|
||
</code></pre>
|
||
<p><code>cmp</code>方法比较两个值并可以在任何可比较的值上调用。它获取一个任何你想要比较的值的引用:这里是把<code>guess</code>与<code>secret_number</code>做比较。<code>cmp</code>返回一个使用<code>use</code>语句引用的<code>Ordering</code>枚举的成员。我们使用一个<a href="ch06-02-match.html"><code>match</code></a><!-- ignore -->表达式根据对<code>guess</code>和<code>secret_number</code>中的值调用<code>cmp</code>后返回的哪个<code>Ordering</code>枚举成员来决定接下来干什么。</p>
|
||
<p>一个<code>match</code>表达式由 <em>分支(arms)</em> 构成。一个分支包含一个 <em>模式</em>(<em>pattern</em>)和代码,这些代码在<code>match</code>表达式开头给出的值符合分支的模式时将被执行。Rust 获取提供给<code>match</code>的值并挨个检查每个分支的模式。<code>match</code>结构和模式是 Rust 中非常强大的功能,它帮助你体现代码可能遇到的多种情形并帮助你处理全部的可能。这些功能将分别在第六章和第十九章详细介绍。</p>
|
||
<p>让我们看看一个使用这里的<code>match</code>表达式会发生什么的例子。假设用户猜了 50,这时随机生成的秘密数字是 38。当代码比较 50 与 38 时,<code>cmp</code>方法会返回<code>Ordering::Greater</code>,因为 50 比 38 要大。<code>Ordering::Greater</code>是<code>match</code>表达式得到的值。它检查第一个分支的模式,<code>Ordering::Less</code>,不过值<code>Ordering::Greater</code>并不匹配<code>Ordering::Less</code>。所以它忽略了这个分支的代码并移动到下一个分支。下一个分支的模式,<code>Ordering::Greater</code>,<em>正确</em>匹配了<code>Ordering::Greater</code>!这个分支关联的代码会被执行并在屏幕打印出<code>Too big!</code>。<code>match</code>表达式就此终止,因为在这个特定场景下没有检查最后一个分支的必要。</p>
|
||
<p>然而,列表 2-4 的代码并不能编译,尝试一下:</p>
|
||
<pre><code class="language-sh">$ cargo build
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
error[E0308]: mismatched types
|
||
--> src/main.rs:23:21
|
||
|
|
||
23 | match guess.cmp(&secret_number) {
|
||
| ^^^^^^^^^^^^^^ expected struct `std::string::String`, found integral variable
|
||
|
|
||
= note: expected type `&std::string::String`
|
||
= note: found type `&{integer}`
|
||
|
||
error: aborting due to previous error
|
||
Could not compile `guessing_game`.
|
||
</code></pre>
|
||
<p>错误的核心表明这里有<em>不匹配的类型</em>(<em>mismatched types</em>)。Rust 拥有一个静态强类型系统。不过,它也有类型推断。当我们写出<code>let guess = String::new()</code>时,Rust 能够推断出<code>guess</code>应该是一个<code>String</code>,并不需要我们写出类型。另一方面,<code>secret_number</code>,是一个数字类型。一些数字类型拥有 1 到 100 之间的值:<code>i32</code>,一个 32 位的数字;<code>u32</code>,一个 32 位无符号数字;<code>i64</code>,一个 64 位数字;等等。Rust 默认使用<code>i32</code>,所以<code>secret_number</code>的类型就是它,除非增加类型信息或任何能让 Rust 推断出不同数值类型的信息。这里错误的原因是 Rust 不会比较字符串类型和数字类型。</p>
|
||
<p>最终我们想要把程序从输入中读取到的<code>String</code>转换为一个真正的数字类型,这样好与秘密数字向比较。可以通过在<code>main</code>函数体中增加如下两行代码来实现:</p>
|
||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||
<pre><code class="language-rust,ignore">extern crate rand;
|
||
|
||
use std::io;
|
||
use std::cmp::Ordering;
|
||
use rand::Rng;
|
||
|
||
fn main() {
|
||
println!("Guess the number!");
|
||
|
||
let secret_number = rand::thread_rng().gen_range(1, 101);
|
||
|
||
println!("The secret number is: {}", secret_number);
|
||
|
||
println!("Please input your guess.");
|
||
|
||
let mut guess = String::new();
|
||
|
||
io::stdin().read_line(&mut guess)
|
||
.expect("Failed to read line");
|
||
|
||
let guess: u32 = guess.trim().parse()
|
||
.expect("Please type a number!");
|
||
|
||
println!("You guessed: {}", guess);
|
||
|
||
match guess.cmp(&secret_number) {
|
||
Ordering::Less => println!("Too small!"),
|
||
Ordering::Greater => println!("Too big!"),
|
||
Ordering::Equal => println!("You win!"),
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>这两行代码是:</p>
|
||
<pre><code class="language-rust,ignore">let guess: u32 = guess.trim().parse()
|
||
.expect("Please type a number!");
|
||
</code></pre>
|
||
<p>这里创建了一个叫做<code>guess</code>的变量。不过等等,难道这个程序不是已经有了一个叫做<code>guess</code>的变量了吗?确实如此,不过 Rust 允许我们通过 <em>覆盖</em>(<em>shadow</em>) 用一个新值来覆盖<code>guess</code>之前的值。这个功能经常用在类似需要把一个值从一种类型转换到另一种类型的场景。shadowing 允许我们复用<code>guess</code>变量的名字而不是强迫我们创建两个不同变量,比如<code>guess_str</code>和<code>guess</code>。(第三章会介绍 shadowing 的更多细节。)</p>
|
||
<p><code>guess</code>被绑定到<code>guess.trim().parse()</code>表达式。表达式中的<code>guess</code>对应包含输入的<code>String</code>类型的原始<code>guess</code>。<code>String</code>实例的<code>trim</code>方法会消除字符串开头和结尾的空白。<code>u32</code>只能包含数字字符。不过用户必须输入回车键才能让<code>read_line</code>返回。当用户按下回车键时,会在字符串中增加一个换行(newline)字符。例如,如果用户输入 5 并回车,<code>guess</code>看起来像这样:<code>5\n</code>。<code>\n</code>代表“换行”,回车键。<code>trim</code>方法消除<code>\n</code>,只留下<code>5</code>。</p>
|
||
<p><a href="../std/primitive.str.html#method.parse">字符串的<code>parse</code>方法</a><!-- ignore -->解析一个字符串成某个数字。因为这个方法可以解析多种数字类型,需要告诉 Rust 我们需要的具体的数字类型,这里通过<code>let guess: u32</code>指定。<code>guess</code>后面的冒号(<code>:</code>)告诉 Rust 我们指明了变量的类型。Rust 有一些内建的数字类型;这里的<code>u32</code>是一个无符号的 32 位整型。它是一个好的较小正整数的默认类型。第三章会讲到其他数字类型。另外,例子程序中的<code>u32</code>注解和与<code>secret_number</code>的比较意味着 Rust 会推断<code>secret_number</code>应该是也是<code>u32</code>类型。现在可以使用相同类型比较两个值了!</p>
|
||
<p><code>parse</code>调用容易产生错误。例如,如果字符串包含<code>A👍%</code>,就无法将其转换为一个数字。因为它可能失败,<code>parse</code>方法返回一个<code>Result</code>类型,非常像之前在 XX 页“使用<code>Result</code>类型来处理潜在的错误”部分讨论的<code>read_line</code>方法。这里再次类似的使用<code>expect</code>方法处理这个<code>Result</code>类型。如果<code>parse</code>因为不能从字符串生成一个数字而返回一个<code>Err</code>的<code>Result</code>成员时,<code>expect</code>会使游戏崩溃并打印提供给它的信息。如果<code>parse</code>能成功地将字符串转换为一个数字,它会返回<code>Result</code>的<code>Ok</code>成员,同时<code>expect</code>会返回<code>Ok</code>中我们需要的数字。</p>
|
||
<p>现在让我们运行程序!</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
Running `target/guessing_game`
|
||
Guess the number!
|
||
The secret number is: 58
|
||
Please input your guess.
|
||
76
|
||
You guessed: 76
|
||
Too big!
|
||
</code></pre>
|
||
<p>漂亮!即便是在猜测之前添加了空格,程序依然能判断出用户猜测了 76。多运行程序几次来检验不同类型输入的相应行为:猜一个正确的数字,猜一个过大的数字和猜一个过小的数字。</p>
|
||
<p>现在游戏已经大体上能玩了,不过用户只能猜一次。增加一个循环来改变它吧!</p>
|
||
<h2>使用循环来允许多次猜测</h2>
|
||
<p><code>loop</code>关键字提供了一个无限循环。增加它后给了用户多次猜测的机会:</p>
|
||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||
<pre><code class="language-rust,ignore">extern crate rand;
|
||
|
||
use std::io;
|
||
use std::cmp::Ordering;
|
||
use rand::Rng;
|
||
|
||
fn main() {
|
||
println!("Guess the number!");
|
||
|
||
let secret_number = rand::thread_rng().gen_range(1, 101);
|
||
|
||
println!("The secret number is: {}", secret_number);
|
||
|
||
loop {
|
||
println!("Please input your guess.");
|
||
|
||
let mut guess = String::new();
|
||
|
||
io::stdin().read_line(&mut guess)
|
||
.expect("Failed to read line");
|
||
|
||
let guess: u32 = guess.trim().parse()
|
||
.expect("Please type a number!");
|
||
|
||
println!("You guessed: {}", guess);
|
||
|
||
match guess.cmp(&secret_number) {
|
||
Ordering::Less => println!("Too small!"),
|
||
Ordering::Greater => println!("Too big!"),
|
||
Ordering::Equal => println!("You win!"),
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>如上所示,我们将提示用户猜测之后的所有内容放入了循环。确保这些代码多缩进了四个空格,并再次运行程序。注意这里有一个新问题,因为程序忠实地执行了我们要求它做的:永远地请求另一个猜测!看起来用户没法退出啊!</p>
|
||
<p>用户总是可以使用<code>Ctrl-C</code>快捷键来终止程序。不过这里还有另一个逃离这个贪得无厌的怪物的方法,就是在 XX 页“比较猜测”部分提到的<code>parse</code>:如果用户输入一个非数字回答,程序会崩溃。用户可以利用这一点来退出,如下所示:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
Running `target/guessing_game`
|
||
Guess the number!
|
||
The secret number is: 59
|
||
Please input your guess.
|
||
45
|
||
You guessed: 45
|
||
Too small!
|
||
Please input your guess.
|
||
60
|
||
You guessed: 60
|
||
Too big!
|
||
Please input your guess.
|
||
59
|
||
You guessed: 59
|
||
You win!
|
||
Please input your guess.
|
||
quit
|
||
thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/libcore/result.rs:785
|
||
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||
error: Process didn't exit successfully: `target/debug/guess` (exit code: 101)
|
||
</code></pre>
|
||
<p>输入<code>quit</code>就会退出程序,同时其他任何非数字输入也一样。然而,毫不夸张的说这是不理想的。我们想要当猜测正确的数字时游戏能自动退出。</p>
|
||
<h3>猜测正确后退出</h3>
|
||
<p>让我们增加一个<code>break</code>来在用户胜利时退出游戏:</p>
|
||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||
<pre><code class="language-rust,ignore">extern crate rand;
|
||
|
||
use std::io;
|
||
use std::cmp::Ordering;
|
||
use rand::Rng;
|
||
|
||
fn main() {
|
||
println!("Guess the number!");
|
||
|
||
let secret_number = rand::thread_rng().gen_range(1, 101);
|
||
|
||
println!("The secret number is: {}", secret_number);
|
||
|
||
loop {
|
||
println!("Please input your guess.");
|
||
|
||
let mut guess = String::new();
|
||
|
||
io::stdin().read_line(&mut guess)
|
||
.expect("Failed to read line");
|
||
|
||
let guess: u32 = guess.trim().parse()
|
||
.expect("Please type a number!");
|
||
|
||
println!("You guessed: {}", guess);
|
||
|
||
match guess.cmp(&secret_number) {
|
||
Ordering::Less => println!("Too small!"),
|
||
Ordering::Greater => println!("Too big!"),
|
||
Ordering::Equal => {
|
||
println!("You win!");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>通过在<code>You win!</code>之后增加一行<code>break</code>,程序在用户猜对了神秘数字后会退出循环。退出循环也就意味着退出程序,因为循环是<code>main</code>的最后一部分。</p>
|
||
<h3>处理无效输入</h3>
|
||
<p>为了进一步改善游戏性,而不是在用户输入非数字时崩溃,需要让游戏忽略非数字从而用户可以继续猜测。可以通过修改<code>guess</code>从<code>String</code>转化为<code>u32</code>那部分代码来实现:</p>
|
||
<pre><code class="language-rust,ignore">let guess: u32 = match guess.trim().parse() {
|
||
Ok(num) => num,
|
||
Err(_) => continue,
|
||
};
|
||
</code></pre>
|
||
<p>从<code>expect</code>调用切换到<code>expect</code>语句是如何从遇到错误就崩溃到真正处理错误的常用手段。记住<code>parse</code>返回一个<code>Result</code>类型,而<code>Result</code>是一个拥有<code>Ok</code>或<code>Err</code>两个成员的枚举。在这里使用<code>match</code>表达式,就像之前处理<code>cmp</code>方法返回的<code>Ordering</code>一样。</p>
|
||
<p>如果<code>parse</code>能够成功的将字符串转换为一个数字,它会返回一个包含结果数字<code>Ok</code>值。这个<code>Ok</code>值会匹配第一个分支的模式,这时<code>match</code>表达式仅仅返回<code>parse</code>产生的<code>Ok</code>值之中的<code>num</code>值。这个数字会最终如期变成新创建的<code>guess</code>变量。</p>
|
||
<p>如果<code>parse</code><em>不</em>能将字符串转换为一个数字,它会返回一个包含更多错误信息的<code>Err</code>值。<code>Err</code>值不能匹配第一个<code>match</code>分支的<code>Ok(num)</code>模式,但是会匹配第二个分支的<code>Err(_)</code>模式。<code>_</code>是一个包罗万象的值;在这个例子中,我们想要匹配所有<code>Err</code>值,不管其中有何种信息。所以程序会执行第二个分支的代码,<code>continue</code>,这意味着进入<code>loop</code>的下一次循环并请求另一个猜测。这样程序就有效地忽略了<code>parse</code>可能遇到的所有错误!</p>
|
||
<p>现在万事俱备(只欠东风)了。运行<code>cargo run</code>来尝试一下:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||
Running `target/guessing_game`
|
||
Guess the number!
|
||
The secret number is: 61
|
||
Please input your guess.
|
||
10
|
||
You guessed: 10
|
||
Too small!
|
||
Please input your guess.
|
||
99
|
||
You guessed: 99
|
||
Too big!
|
||
Please input your guess.
|
||
foo
|
||
Please input your guess.
|
||
61
|
||
You guessed: 61
|
||
You win!
|
||
</code></pre>
|
||
<p>太棒了!再有最后一个小的修改,就能完成猜猜看游戏了:还记得程序依然会打印出秘密数字。这在测试时还好,但会毁了游戏性。删掉打印秘密数字的<code>println!</code>。列表 2-5 为最终代码:</p>
|
||
<figure>
|
||
<span class="filename">Filename: src/main.rs</span>
|
||
<pre><code class="language-rust,ignore">extern crate rand;
|
||
|
||
use std::io;
|
||
use std::cmp::Ordering;
|
||
use rand::Rng;
|
||
|
||
fn main() {
|
||
println!("Guess the number!");
|
||
|
||
let secret_number = rand::thread_rng().gen_range(1, 101);
|
||
|
||
loop {
|
||
println!("Please input your guess.");
|
||
|
||
let mut guess = String::new();
|
||
|
||
io::stdin().read_line(&mut guess)
|
||
.expect("Failed to read line");
|
||
|
||
let guess: u32 = match guess.trim().parse() {
|
||
Ok(num) => num,
|
||
Err(_) => continue,
|
||
};
|
||
|
||
println!("You guessed: {}", guess);
|
||
|
||
match guess.cmp(&secret_number) {
|
||
Ordering::Less => println!("Too small!"),
|
||
Ordering::Greater => println!("Too big!"),
|
||
Ordering::Equal => {
|
||
println!("You win!");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<figcaption>
|
||
<p>Listing 2-5: Complete code of the guessing game</p>
|
||
</figcaption>
|
||
</figure>
|
||
<h2>总结一下,</h2>
|
||
<p>此时此刻,你顺利完成了猜猜看游戏!恭喜!</p>
|
||
<p>这是一个通过动手实践的方式想你介绍许多 Rust 新知识的项目:<code>let</code>、<code>match</code>、方法、关联函数,使用外部 crate,等等。接下来的几章,我们将会详细学习这些概念。第三章涉及到大部分编程语言都有的概念,比如变量、数据类型和函数,以及如何在 Rust 中使用他们。第四章探索所有权(ownership),这是一个 Rust 同其他语言都不相同的功能。第五章讨论结构体和方法语法,而第六章侧重解释枚举。</p>
|
||
<h1>通用编程概念</h1>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch03-00-common-programming-concepts.md">ch03-00-common-programming-concepts.md</a>
|
||
<br>
|
||
commit 2067b6e2bff990bceb39ae8f35780bd3bed08644</p>
|
||
</blockquote>
|
||
<p>这一章涉及到几乎出现在所有编程语言中的概念,以及他们在 Rust 中如何工作。很多编程语言在核心概念上都是共通的。本章中展示的所有概念没有一个是 Rust 所特有的,不过我们会在 Rust 环境中讨论他们并解释他们的使用习惯。</p>
|
||
<p>具体的,我们将会学习变量,基本类型,函数,注释和控制流。这些基础知识将会出现在每一个 Rust 程序中,提早学习这些概念会使你在起步时拥有一个核心的基础。</p>
|
||
<!-- PROD: START BOX -->
|
||
<blockquote>
|
||
<h3>关键字</h3>
|
||
<p>Rust 语言有一系列被保留为只能被语言使用的<em>关键字</em>(<em>keywords</em>),如大部分语言一样。注意你不能使用这些关键字作为变量或函数的名称。大部分关键字有特殊的意义,并将会被用来进行 Rust 程序中的多种任务;一些关键字目前没有相关的功能不过为了将来可能添加进 Rust 的功能而被保留。可以在附录 A 中找到一份关键字的列表</p>
|
||
</blockquote>
|
||
<!-- PROD: END BOX --><h2>变量和可变性</h2>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch03-01-variables-and-mutability.md">ch03-01-variables-and-mutability.md</a>
|
||
<br>
|
||
commit b0fab378c9c6a817d4f0080d7001d085017cdef8</p>
|
||
</blockquote>
|
||
<p>第二章中提到过,变量默认是<em>不可变</em>(<em>immutable</em>)的。这是 Rust 中许多鼓励以利用 Rust 提供的安全和简单并发优势编写代码的助力之一。不过,仍然有使变量可变的选项。让我们探索一下为什么以及如何鼓励你拥抱不可变性,还有为什么你可能想要弃之不用。</p>
|
||
<p>当变量使不可变时,这意味着一旦一个值被绑定上了一个名称,你就不能改变这个值。作为说明,通过<code>cargo new --bin variables</code>在 <em>projects</em> 目录生成一个叫做 <em>variables</em> 的新项目。</p>
|
||
<p>接着,在新建的 <em>variables</em> 目录,打开 <em>src/main.rs</em> 并替换其代码为如下:</p>
|
||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||
<pre><code class="language-rust,ignore">fn main() {
|
||
let x = 5;
|
||
println!("The value of x is: {}", x);
|
||
x = 6;
|
||
println!("The value of x is: {}", x);
|
||
}
|
||
</code></pre>
|
||
<p>保存并使用<code>cargo run</code>运行程序。应该会看到一个错误信息,如下输出所示:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling variables v0.0.1 (file:///projects/variables)
|
||
error[E0384]: re-assignment of immutable variable `x`
|
||
--> src/main.rs:4:5
|
||
|
|
||
2 | let x = 5;
|
||
| - first assignment to `x`
|
||
3 | println!("The value of x is: {}", x);
|
||
4 | x = 6;
|
||
| ^^^^^ re-assignment of immutable variable
|
||
</code></pre>
|
||
<p>这个例子显示了编译器如何帮助你寻找程序中的错误。即便编译器错误可能是令人沮丧的,他们也仅仅意味着程序不能安全的完成你想让它完成的工作;他们<em>不能</em>说明你不是一个好的程序员!有经验的 Rustacean 们也会遇到编译器错误。这些错误表明错误的原因是<code>对不可变变量重新赋值</code>(<code>re-assignment of immutable variable</code>),因为我们尝试对不可变变量<code>x</code>赋第二个值。</p>
|
||
<p>当尝试去改变之前设计为不可变的值出现编译时错误是很重要的,因为这种情况可能导致 bug。如果代码的一部分假设一个值永远也不会改变而另一部分代码改变了它,这样第一部分代码就有可能不能像它设计的那样运行。你必须承认这种 bug 难以跟踪,尤其是当第二部分代码只是<em>有时</em>当变量使不可变时,这意味着一旦一个值被绑定上了一个名称,你就不能改变这个值。</p>
|
||
<p>Rust 编译器保证如果声明一个值不会改变,它就真的不会改变。这意味着当阅读和编写代码时,并不需要记录如何以及在哪可能会被改变,这使得代码易于推导。</p>
|
||
<p>不过可变性也是非常有用的。变量只是默认不可变;可以通过在变量名之前增加<code>mut</code>来使其可变。它向之后的读者表明了其他部分的代码将会改变这个变量值的意图。</p>
|
||
<p>例如,改变 <em>src/main.rs</em> 并替换其代码为如下:</p>
|
||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||
<pre><code class="language-rust">fn main() {
|
||
let mut x = 5;
|
||
println!("The value of x is: {}", x);
|
||
x = 6;
|
||
println!("The value of x is: {}", x);
|
||
}
|
||
</code></pre>
|
||
<p>当运行这个程序,出现如下:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling variables v0.1.0 (file:///projects/variables)
|
||
Running `target/debug/variables`
|
||
The value of x is: 5
|
||
The value of x is: 6
|
||
</code></pre>
|
||
<p>通过<code>mut</code>,允许把绑定到<code>x</code>的值从<code>5</code>改成<code>6</code>。在一些情况下,你会想要使一个变量可变,因为这比只使用不可变变量实现的代码更易于编写。</p>
|
||
<p>除了避免 bug 外,这里还有数个需要权衡取舍的地方。例如,有时使用大型数据结构时,适当地使变量可变可能比复制和返回新分配的实例要更快。对于较小的数据结构,总是创建新实例并采用一种更函数式的编程风格可能会使代码更易理解。所以为了可读性而造成的性能惩罚也许使值得的。</p>
|
||
<h3>变量和常量的区别</h3>
|
||
<p>不能改变一个变量的值可能会使你想起另一个大部分编程语言都有的概念:<em>常量</em>(<em>constants</em>)。常量也是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。首先,不允许对常量使用<code>mut</code>:常量不光是默认不能改变,它总是不能改变。常量使用<code>const</code>关键字而不是<code>let</code>关键字声明,而且<em>必须</em>注明值的类型。现在我们准备在下一部分,“数据类型”,涉及到类型和类型注解,所以现在无需担心这些细节。常量可以在任何作用域声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。最后一个区别是常量只能用于常量表达式,而不能作为函数调用的结果或任何其他只在运行时使用到的值。</p>
|
||
<p>这是一个常量声明的例子,它的名称是<code>MAX_POINTS</code>而它的值是 100,000。Rust 常量的命名规范是使用大写字母和单词间使用下划线:</p>
|
||
<pre><code class="language-rust">const MAX_POINTS: u32 = 100_000;
|
||
</code></pre>
|
||
<p>常量在整个程序生命周期中都有效,位于它声明的作用域之中。这使得常量可以用作多个部分的代码可能需要知道的程序范围的值,例如一个游戏中任何玩家可以获得的最高分或者一年的秒数。</p>
|
||
<p>将用于整个程序的硬编码的值命名为常量(并编写文档)对为将来代码维护者表明值的意义是很有用的。它也能帮助你将硬编码的值至于一处以便将来可能需要修改他们。</p>
|
||
<h3>覆盖</h3>
|
||
<p>如第二章猜猜看游戏所讲到的,我们可以定义一个与之前变量名称相同的新变量,而新变量会<em>覆盖</em>之前的变量。Rustacean 们称其为第一个变量被第二个<em>给覆盖</em>了,这意味着第二个变量的值是使用这个变量时会看到的值。可以用相同变量名称来覆盖它自己以及重复使用<code>let</code>关键字来多次覆盖,如下所示:</p>
|
||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||
<pre><code class="language-rust">fn main() {
|
||
let x = 5;
|
||
|
||
let x = x + 1;
|
||
|
||
let x = x * 2;
|
||
|
||
println!("The value of x is: {}", x);
|
||
}
|
||
</code></pre>
|
||
<p>这个程序首先将<code>x</code>绑定到值<code>5</code>上。接着通过<code>let x =</code>覆盖<code>x</code>,获取原始值并加<code>1</code>这样<code>x</code>的值就变成<code>6</code>了。第三个<code>let</code>语句也覆盖了<code>x</code>,获取之前的值并乘以<code>2</code>,<code>x</code>的最终值是<code>12</code>。当运行这个程序,它会有如下输出:</p>
|
||
<pre><code class="language-sh">$ cargo run
|
||
Compiling variables v0.1.0 (file:///projects/variables)
|
||
Running `target/debug/variables`
|
||
The value of x is: 12
|
||
</code></pre>
|
||
<p>这与将变量声明为<code>mut</code>是有区别的。因为除非再次使用<code>let</code>关键字,不小心尝试对变量重新赋值会导致编译时错误。我们可以用这个值进行一些计算,不过计算完之后变量仍然是不变的。</p>
|
||
<p>另一个<code>mut</code>与覆盖的区别是当再次使用<code>let</code>关键字时,事实上创建了一个新变量,我们可以改变值的类型。例如,假设程序请求用户输入空格来提供在一些文本之间需要多少空间来分隔,不过我们真正需要的是将输入存储成数字(多少个空格):</p>
|
||
<pre><code class="language-rust">let spaces = " ";
|
||
let spaces = spaces.len();
|
||
</code></pre>
|
||
<p>这里允许第一个<code>spaces</code>变量是字符串类型,而第二个<code>spaces</code>变量,它是一个恰巧与第一个变量名字相同的崭新的变量,它是数字类型。因此覆盖使我们不必使用不同的名字,比如<code>spaces_str</code>和<code>spaces_num</code>;相反,我们可以复用<code>spaces</code>这个更简单的名称。然而,如果尝试使用<code>mut</code>,如下所示:</p>
|
||
<pre><code class="language-rust,ignore">let mut spaces = " ";
|
||
spaces = spaces.len();
|
||
</code></pre>
|
||
<p>会导致一个编译时错误,因为不允许改变一个变量的类型:</p>
|
||
<pre><code class="language-sh">error[E0308]: mismatched types
|
||
--> src/main.rs:3:14
|
||
|
|
||
3 | spaces = spaces.len();
|
||
| ^^^^^^^^^^^^ expected &str, found usize
|
||
|
|
||
= note: expected type `&str`
|
||
= note: found type `usize`
|
||
</code></pre>
|
||
<p>现在我们探索了变量如何工作,让我们看看他们能有多少数据类型。</p>
|
||
<h2>数据类型</h2>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/src/ch03-02-data-types.md">ch03-02-data-types.md</a>
|
||
<br>
|
||
commit d05b7c63ff50b3f9126bb5533e0ba5dd424b83d1</p>
|
||
</blockquote>
|
||
<p>Rust 中的任何值都有一个具体的<em>类型</em>(<em>type</em>),这告诉了 Rust 它被指定为何种数据这样 Rust 就知道如何处理这些数据了。这一部分将讲到一些语言内建的类型。我们将这些类型分为两个子集:标量(scalar)和复合(compound)。</p>
|
||
<p>贯穿整个部分,请记住 Rust 是一个<em>静态类型</em>(<em>statically typed</em>)语言,也就是说必须在编译时就知道所有变量的类型。编译器通常可以通过值以及如何使用他们来推断出我们想要用的类型。当多个类型都是可能的时候,比如第二章中<code>parse</code>将<code>String</code>转换为数字类型,必须增加类型注解,像这样:</p>
|
||
<pre><code class="language-rust">let guess: u32 = "42".parse().unwrap();
|
||
</code></pre>
|
||
<p>如果这里不添加类型注解,Rust 会显示如下错误,它意味着编译器需要我们提供更多我们想要使用哪个可能的类型的信息:</p>
|
||
<pre><code class="language-sh">error[E0282]: unable to infer enough type information about `_`
|
||
--> src/main.rs:2:5
|
||
|
|
||
2 | let guess = "42".parse().unwrap();
|
||
| ^^^^^ cannot infer type for `_`
|
||
|
|
||
= note: type annotations or generic parameter binding required
|
||
</code></pre>
|
||
<p>在我们讨论各种数据类型时会看到不同的类型注解。</p>
|
||
<h3>标量类型</h3>
|
||
<p><em>标量</em>类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。你可能在其他语言中见过他们,不过让我们深入了解他们在 Rust 中时如何工作的。</p>
|
||
<h4>整型</h4>
|
||
<p><em>整数</em>是一个没有小数部分的数字。我们在这一章的前面使用过一个整型,<code>i32</code>类型。这个类型声明表明在 32 位系统上它关联的值应该是一个有符号整数(因为这个<code>i</code>,与<code>u</code>代表的无符号相对)。表格 3-1 展示了 Rust 内建的整数类型。每一个变体的有符号和无符号列(例如,<em>i32</em>)可以用来声明对应的整数值。</p>
|
||
<figure>
|
||
<figcaption>
|
||
<p>Table 3-1: Integer Types in Rust</p>
|
||
</figcaption>
|
||
<table><thead><tr><td> Length </td><td> Signed </td><td> Unsigned </td></tr></thead>
|
||
<tr><td> 8-bit </td><td> i8 </td><td> u8 </td></tr>
|
||
<tr><td> 16-bit </td><td> i16 </td><td> u16 </td></tr>
|
||
<tr><td> 32-bit </td><td> i32 </td><td> u32 </td></tr>
|
||
<tr><td> 64-bit </td><td> i64 </td><td> u64 </td></tr>
|
||
<tr><td> arch </td><td> isize </td><td> usize </td></tr>
|
||
</table>
|
||
</figure>
|
||
<p>每一种变体都可以是有符号或无符号的并有一个显式的大小。有符号和无符号代表数字是否能够是正数或负数;换句话说,数字是否需要有一个符号(有符号数)或者永远只需要是正的这样就可以不用符号(无符号数)。</p>
|
||
<h1>How Functions Work</h1>
|
||
<h1>Comments</h1>
|
||
<h1>Control Flow</h1>
|
||
|
||
</div>
|
||
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
|
||
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
</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 type="text/javascript">
|
||
var socket = new WebSocket("ws://localhost:3001");
|
||
socket.onmessage = function (event) {
|
||
if (event.data === "reload") {
|
||
socket.close();
|
||
location.reload(true); // force reload from server (not from cache)
|
||
}
|
||
};
|
||
|
||
window.onbeforeunload = function() {
|
||
socket.close();
|
||
}
|
||
</script>
|
||
|
||
|
||
<script src="highlight.js"></script>
|
||
<script src="book.js"></script>
|
||
</body>
|
||
</html>
|