Guessing Game - chapter 3.1 - rust book stable
misaka15842 misakamoe@yahoo.com latest change 2015.7.31
中文翻译不保证正确性,不保证确切表达了原文含义,翻译尽量不添加个人理解。如果有能力和时间请尽量阅读原文以避免译文可能导致的误解。 戳我阅读原文 猜数字游戏
我们来实现一个 ** 猜数字游戏 ** 来作为我们的第一个实际操作的 rust 工程。游戏程序的流程应当是这样的:
- 程序生成一个零到一百之间的随机数作为答案
- 玩家输入一个猜测的数字
- 如果猜错了,游戏应该告诉玩家猜测的数字太大还是太小
- 如果猜对了,游戏应该祝贺玩家获得成功
听起来不错吧?
建立工程
现在让我们建立一个新的 rust 项目。 首先,来到我们的工程目录下
$ cd /path/to/your/projects
还记得我们如何创建的 hello_world 工程吗?如果不记得也不要紧,请翻阅 *rust book 第二章 hello cargo *。 cargo
有一个命令可以帮助我们创建一个工程,我们只需要这样做:
$ cargo new guessing_game --bin
我们将工程的名字作为参数传递给了cargo new
命令,同时给出了--bin
的选项来告诉cargo
创建一个binary项目而不是library项目。 让我们看看自动生成的工程文件 Cargo.toml
:
[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
cargo
从你的平台获取了一些信息用于生成Cargo.toml
,如果信息不正确,那就去修正它。
最后,cargo
为我们自动生成了一个src/main.rs
文件:
fn main() {
println!("Hello, world!")
}
我们来编译一下:
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
漂亮!来再次打开我们的src/main.rs
,我们将要把我们的代码都写入到这个文件中。不过,在继续下一步之前,请允许我们再展示cargo
的一个命令:run
。 cargo run
有那么一点点像 cargo build
,但是cargo run
在编译后还会执行编译生成的可执行文件。让我们试试:
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Running `target/debug/guessing_game`
Hello, world!
太棒了!run
命令将在你需要快速迭代工程的时候提供巨大的便利。而我们的guessing_game
正是这样一个项目,我们需要在每次进行下一步开发之前执行测试。 处理输入
现在让我们来实现它!首先我们需要让游戏允许玩家输入一个数字作为猜测。将以下代码放进你的src/main.rs
:
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)
.ok()
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
是的,这里面有很多的东西,让我们一点一点来解释。
use std::io;
我们需要获取玩家的输入,然后输出结果,因此,我们需要标准库中的IO库。Rust 默认只为程序导入了很少一部分代码,称之为 prelude。如果我们需要的模块不在prelude中,那么你就需要显式地导入它。
fn main() {
正如我们之前所讲述的(2.1 Hello World),main()
是我们程序的入口点。fn
语法用于声明一个新的函数,()
表示这个函数没有参数。函数体由一对{}
所包含,紧随其后。因为我们的声明中没有包含返回值,因此将会返回 ()
,一个空的元组(tuple)。
println!("Guess the number!");
println!("Please input your guess.");
我们在之前已经学习了 println!() 是一个宏(2.1 Hello world!),用于向屏幕输出一个字符串。
let mut guess = String::new();
现在我们遇到了有趣的地方,这一行代码做了很多事情。首先,我们注意到这是一个 let
语句,用于创建一个变量绑定(variable bindings)。通过:
let foo = bar;
这样的语法,我们创建了一个新的名为foo的绑定,并且绑定到值 bar。在很多语言中,这被称为 变量(variable),但是rust 的变量绑定相对而言有少许优势。
举个栗子,变量绑定默认是不可变(immutable) 的。这就是为什么我们的例子使用了 mut 关键字:它使一个绑定可变(mutable)。let
并不是把一个 名字 放在 = 操作符左边,实际上,lhs 接受了一种 "模式"(patterns)。我们将会在迟些时候使用模式,我们现在用足够简单的方式来使用它:
let foo = 5; // immutable.
let mut bar = 5; // mutable
哦还有,//
用于引导一个行注释。rust 将会忽略注释中的任何东西。(事实上还有一种注释:文档注释。这个我们迟些再讲。)
现在,我们知道了let mut guess
将会引入一个名为guess的可变的绑定,那么我们来看看=
的另一边,我们绑定了什么。
Strng::new();
String 是由标准库提供的一种字符串类型。准确的说,String是一种可变长度的,默认使用UTF-8编码的文本类型。 ::new()
语法使用::
,因为这是一个特殊类型的关联函数。也就是说,它与 String 类型本身相关联,而不是 String 类型的实例。有些语言也将之称为 静态方法。
而这个函数之所以叫做new()
,是因为它创建了一个新的实例,一个空字符串。你将会在很多类型中看到new()
函数用于创建一种类型的值。
让我们再进一步:
io::stdin().read_line(&mut guess)
.ok()
.expect("Failed to read line");
这几行代码具有比刚才那行更多的内涵,我们来一点一点分析。
这几行代码有更多的含义。让我们一点一点分析。首先第一行有两个部分,首先是:use std::io;
还记得我们在程序第一行引入的 std::io吗?我们现在调用了其中的一个关联函数。如果我们没有 use std::io,我们就需要这样写:std::io::stdin()
这个特殊的函数返回一个你的终端的标准输入句柄。具体参见:std::io::Stdin
. 下一个部分将要使用句柄来获得用户的输入:
.read_line(&mut guess)
这里我们调用了句柄的 read_line()
方法。方法 ( method ) 就像是关联函数,但是方法仅仅能在特定类型的实例上调用,而不是类型本身上。我们同时传递了一个参数给 read_line()
:&mut guess
。
还记得我们在上面是怎么绑定的 guess
吗?我们将 guess
绑定为可变的。read_line()
并没有接受一个 String
作为参数:它得到了 &mut String
。
Rust 有一个名为 引用(reference)的特性,允许你对同一个数据有多个引用,减少拷贝。引用是一个复杂的特性。作为 Rust 主要的卖点之一,引用被设计成安全而易于使用的。不过现在,我们不需要知道太多细节来完成我们的程序,我们只需要知道引用就像是默认可变的 let 绑定。因此,我们只要知道这里需要写 &mut guess
,而不是 &guess
。
那么,为什么 read_line()
需要一个对字符串的可变的引用?read_line()
的工作将用户输入到标准输入的东西放到一个字符串里,因此,它需要一个字符串参数,为了储存用户的输入,参数还必须是可变的。
但是我们还没有完成这行代码。这一行文本仅仅只是一个逻辑行的第一部分。
.ok()
.expect("Failed to read line");
你可能会希望使用一个新行和增加的缩进来使用像 .foo()
这样的语法调用一个方法,这能帮助你分割一个长行。尽管我们可以这样做:
io::stdin().read_line(&mut guess).ok().expect("failed to read line");
但是这样将会难以阅读。所以我们将它作为三个方法调用来分割了它。
我们已经谈论了 read_line()
,那么 ok()
和 expect()
又是什么鬼?是的,我们已经有了read_line()
来将用户的输入放到我们传递的 &mut String
中。但是read_line()
方法也返回了一个值:在这个场合,返回值是一个 io::Result
.
设计Result
类型的目的是将要交给错误处理程序的信息编码。Result
的值就像任意一个类型一样,定义了方法。在这个场合下,io::Result
有一个名为ok()
的方法。这个方法表示 ”我们假设这次读取结果是成功的,如果没有,那么就抛出错误信息。那么,为什么要抛出错误信息?是的,在我们的程序中,我们仅仅希望能输出一个通用的错误信息,因为程序的任何问题都意味着我们无法继续执行。ok()
方法返回一个定义了except()
方法的另一个值,except()
方法接受一个参数来调用它,如果它的值不是成功完成,那么panic!()
将会被调用,我们传递给except()
方法的参数将作为调用panic!()
的参数。panic!()
函数将会使我们的程序崩溃,并将我们传递给它的参数作为信息显示出来。
现在我们的例子还有一行有待解释:
println!("You guessed: {}", guess);
}
这里输出了我们保存的输入的字符串。本行里的 {}
是一个占位符。同时,我们将 guess
作为参数调用了 println!()
。如果我们有多个 {}
,那么我们将需要传递多个参数来调用println!()
。
let x = 5;
let y = 10;
println!("x and y: {} and {}", x, y);
简单。
不管怎么说,好歹讲了这么多了XD。我们可以使用cargo run
来执行它:
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6
很好,我们的第一部分完成了。现在,我们能从键盘获取输入,并且输出到屏幕上。
生成一个随机数
接下来我们需要一个随机数作为最终答案。Rust当前并没有在标准库中包含随机数功能。不过 Rust team 实现了一套随机数并作为 crate
提供。crate
是一个Rust代码的包(package),我们已经构建过一个'binary crate',生成一个可执行文件。rand
是一个'library crate',包含了可以用于其他程序的代码。
在rust中使用一个外部的crate正是Cargo
真正的闪光点所在。在我们写下使用rand的代码之前,我们需要修改我们的Cargo.toml
文件。打开它,然后在底部添加这些行:
[dependencies]
rand="0.3.0"
[dependencies]
节就像[package]
节一样,在这行之下所有东西都属于这个节所有,直到遇到下一个节的开始。Cargo利用dependencies节来得知程序有哪些依赖,要求什么样的版本。在这个场景下,我们使用版本号 "0.3.0"。Cargo 能理解 Semantic Versioning 格式的版本号字符串,并在rust中作为标准的版本号格式使用。如果我们希望使用最新版本的crate,我们可以使用 *
或者使用版本范围。阅读 Cargo’s documentation 来获取更多细节。
现在,不需要改变我们的任何代码,让我们编译工程:
$ cargo build
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading rand v0.3.8
Downloading libc v0.1.6
Compiling libc v0.1.6
Compiling rand v0.3.8
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
(当然,你可能会看见不同的版本)
我们看到了很多新的输出!现在我们有了一个外部依赖,Cargo从Crates.io取得了我们注册的所有东西的最新版本。Crates.io是人们存放Rust生态系统的开源项目代码的地方。
在更新注册信息之前,Cargo 检查 [dependencies]
节并且下载我们本地还没有的crate依赖。在这个场景下,我们仅仅依赖了rand
但是我们还获取了一个libc
,这是因为rand
依赖libc
进行工作。cargo将会下载完它们并为我们的项目编译好依赖的crate。
如果我们再次运行cargo build
,我们将会获得和之前不同的输出:
$ cargo build
是的,没有输出!Cargo知道我们的工程已经构建,我们的crate依赖也已经构建,所以它这时候没有事情可以做。在没有事情做的情况下,它简单的退出了。如果我们再次打开src/main.rs
,做一些无关紧要的修改,然后再次保存并编译,我们将可以看到一行输出:
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
我们告诉Cargo我们想要0.3.x版本的rand,并且cargo获取了0.3.x最新的版本,0.3.8。但是如果下一周0.3.9发布了非常重要的bug修复补丁的话,我们要怎么办?得到修复是重要的,但是如果0.3.9包含一个回归,破坏了我们的代码怎么办?
答案就是你可以在工程目录中找到的Cargo.lock
文件。在你编译项目的第一时间,Cargo就保存了你使用的所有crate的版本信息,写入到了Cargo.lock
文件中。在这个特性下你编译你的工程,如果Cargo检测到Cargo.lock文件存在,那么就会使用Cargo.lock
文件中指定的版本而不是再次写入你要使用的版本。这允许你自动重复编译。换句话说,我们可以一直停留在0.3.8版本知道我们明确想要升级,所有享受别人分享的代码的人都应该感谢lock文件。
那么我们在明确想要使用0.3.9应该如何做呢?Cargo有另一个命令,update
。它意味着忽略lock文件,获取所有最新版本的依赖。如果它这么做了,那么lock文件将被重写为新版本。不过在默认情况下,cargo只会查找比0.3.0大而比0.4.0小的版本。如果我们想要转用0.4.x,那么我们需要修改Cargo.toml
。当我们这么做之后,再次运行cargo build
,Cargo将会更新目录并重新处理对rand的需求。
关于Cargo和它的生态系统还有很多可以说的,不过现在我们仅仅知道这些就足够了。Cargo使我们重用库变得十分简单,容易写短小的项目来将多个子包组装成一个功能强大的程序。
现在让我们真正使用 rand
。这就是我们接下来要做的:
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)
.ok()
.expect("failed to read line");
println!("You guessed: {}", guess);
}
看看我们在第一行做的第一件事情,extern crate rand
。因为我们在我们的Cargo.toml
中,[dependencies]
节中声明了rand
,所以我们可以使用extern crate rand
来使Rust知道我们将要使用rand
。这和使用use rand;
是等价的,当然我们也可以rand::
前缀来使用rand
的任何东西。
接下来,我们添加了另一行use。use rand::Rng;
。之所以导入这个,是因为我们将要调用的一个方法需要Rng
来工作。这个基本思想是:将方法定义于’特性(traits)‘之上,之后,如果要这个方法工作,就必须在范围内找到’特性(traits)‘。阅读文档traits来了解更多细节。
在中间我们添加了两行代码:
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is: {}", secret_number);
我们使用rand::thread_rng()
函数来获取一个我们所处的线程的随机数生成器的拷贝,也就是我们在上面的use rand::Rng
,它有一个gen_range()
方法可用。这个方法要求两个参数,生成一个介于两个参数之间的数(这个范围的数学描述是 [a,b) ),所以我们需要1和101两个参数来获取1到100的随机数。
第二行我们输出了secret_number
,也就是我们游戏的答案。这在开发中很有用,可以帮助我们更简单地测试代码。但是显然我们应该在最终版本中删除掉这行,理由嘛,显而易见的。
让我们试试运行我们的新程序:
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/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
很棒!接下来,让我们比较输入的数字和答案吧。
Comparing Guesses 比较猜测
现在,我们已经获取了用户输入,接下来我们需要将输入和我们游戏的答案进行比较。 我们对代码做一点改动:
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)
.ok()
.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!"),
}
}
让我们来看看。首先,我们添加了一个use语句:use std::cmp::Ordering;
。这句代码让我们从std
命名空间中借出了一个叫std::cmp::Ordering
的类型到我们的代码中。然后,我们在最底下添加了5行新的代码:
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
首先我们需要知道,cmp()
方法可以在任何可比较类型的对象上调用,它接受一个要比较的对象的引用作为参数。这个方法返回一个我们先前提到的Ordering
类型的枚举值。我们使用match
语句来确定返回值的确切值。Ordering
是一个枚举类型,关于枚举(enumeration),我们通过下面这个例子来简单了解一下。
enum Foo {
Bar,
Baz,
}
在这个定义中,在Foo中的所有东西,不管是Foo::Bar
还是Foo::Baz
,我们都可以使用::
来从枚举类型的命名空间中借出枚举值。 Ordering
枚举有三个可能的枚举值:Less
,Equal
,Greater
。match
语句提取类型中的值,并允许你为每个可能的值创建一个arm
。比如说,Ordering
有三个枚举值,我们就可以写三个arm
:
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
如果太小,我们输出Too small!,如果太大,我们输出Too big!,相同的情况,you win!。
现在我们完成了这一步了吗?让我们编译一下试试:
$ cargo build
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
src/main.rs:28:21: 28:35 error: mismatched types:
expected `&collections::string::String`,
found `&_`
(expected struct `collections::string::String`,
found integral variable) [E0308]
src/main.rs:28 match guess.cmp(&secret_number) {
^~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `guessing_game`.
看起来这是个巨大的错误。这个问题的核心是'mismatched types'。Rust有一个强健的静态类型系统。尽管如此,它同时还有*静态类型推断(static type inference)*的能力。当我们写下let guess = String::new()
时,Rust可以推断出guess
的类型是String
,于是禁止我们将其他类型的值写入它。而我们的secret_number
是一个拥有0-100之间值的数字类型:它可能是i32
,一个32位的整数。或者u32
,一个32位无符号的整数。或者i64
,一个64位的整数。等等。不过这并没有问题,这种情况下Rust默认使用i32
类型。 现在我们应该明白了,secret_number
是i32
类型,而guess
是String
类型。而cmp()
方法要求比较的两个对象必须是同一个类型,因此,编译器发出了抱怨:"嘿,这两个类型不一样,我不知道怎么比较它们!"。
那么如何解决呢?我们想要把String
类型转换成i32
类型,以便进行比较。 我们可以添加三行代码来做到这些:
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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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!"),
}
}
这是我们新的三行
let guess: u32 = guess.trim().parse()
.ok()
.expect("Please type a number!");
"哈?等下等下,我想我们已经有了一个guess
了不是吗?" 是的,但是Rust允许我们**隐藏(shadow)**之前写下的guess
,并且使用新的guess
代替。这是经常在Rust中使用的技巧,我们开始时guess
是一个String
对象,而现在我们获得了i32
类型的guess
!这个特性可以让我们避免写下诸如guess_str
之类的多个变量来储存含义相同的数据。
看起来,我们将guess
重新绑定到了我们之前使用的一些东西上?
guess.trim().parse()
接着我们就调用了ok()
和except()
方法。这里的guess
是旧的guess,String
类型的guess
。在String
上的trim()
方法可以将字符串开头与结尾的空白字符去除。这是很重要的,因为我们在输入的最后按下了Enter键(回车键),read_line()
将它也读取了。这意味着我们输入'5'然后敲击回车,实际上程序接收到的是'5\n'。'\n'是指'换行(newline)',也就是Enter。trim()
可以解决这个问题,在执行它之后我们将会得到'5',回车被去除了。 在String
上的parser()
方法可以将String
解释为一种数字类型的值。我们需要给他一个提示,让他将字符串分析为某种特定类型的数字。在这里我们使用let guess: u32
来明确告诉Rust我们想要的类型是u32
。u32
是无符号32位的整数类型。Rust有很多内建的数字类型,我们选择了u32
,因为这对于我们guess
的取值范围而言最好的选择。
我们就像调用read_line()
一样来调用parse()
并捕获可能发生的错误。如果我们的字符串储存了"A%ASD!!FGD"这样的数据的话,调用parse()
会发生什么呢?显然,我们无法把这样的字符串转换成数字。我们使用和上面调用read_line()
时同样的方法,ok()
和except()
来捕获错误并主动崩溃退出。
我们来运行试试看!
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/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!
漂亮!你可以看到,我们在输入时输入了多余的空格,而程序正确的处理了这种情况,得知了我们猜测的数字是'76'。我们运行了一会儿这个小程序,确认了猜测和比较功能的正常执行,告诉了我们答案比猜测的数字小。
现在我们已经完成了游戏的大部分功能,美中不足的是游戏中我们只能猜测一次。我们添加一个循环吧!
Looping 循环
loop
关键字允许我们创造一个"永不停止"的循环。
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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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!"),
}
}
}
我们试一试。不过,我们只添加了一个不停止的循环吗?还记得我们之前写的parse()
吗?如果我们输入了一个非数字,程序将会返回退出。
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/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!'
哈,我们输入了'quit'确确实实让程序退出了。事实上如果我们输入任何非数字,都将导致程序的崩溃退出。不过这还不够理想。首先,我们需要在我们赢得了游戏后退出:
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)
.ok()
.expect("failed to read line");
let guess: u32 = guess.trim().parse()
.ok()
.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;
}
}
}
}
通过在'you win'行后添加break
,我们让程序在赢得了游戏后退出了。事实上,break
语句表示退出一个循环,而退出循环在本程序中意味着我们将会退出程序。现在,我们在两种情况下会退出程序:输入了非数字,玩家获得了胜利。现在我们不想让程序在输入了非数字时退出,只要忽略掉错误,我们可以这样做:
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)
.ok()
.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;
}
}
}
}
这几行代码实现了我们上面所述的要求。
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
使用match
语句处理ok()
和except()
,我们实现了从"出错后崩溃"到"处理掉了错误"的转变。parse()
返回的结果是一个枚举,就像是Ordering
一样。但是在这种情况下,枚举值关联了一些数据:ok()
是成功,Err()
是失败。同时,它们还包含了更多的信息:成功处理的数字,错误的类型。在这种情况下,我们将成功情况下,将处理出的数字交给一个名字然后放到右边。而错误的情况下,由于我们不在乎是什么错误,所以我们使用_
来代替一个名字。这样做让我们忽略了错误,continue
让我们进入循环的下一次迭代。
现在看起来好多了:
$ cargo run
Compiling guessing_game v0.1.0 (file:///home/you/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!
棒极了!让我们去除掉输出secret_number
的愚蠢行为,游戏就真正完成了!
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)
.ok()
.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;
}
}
}
}
完成
现在,我们真正完成了我们的第一个Rust程序,猜数字游戏。
在这里,我们展示了很多Rust语言的基本概念和语法:let
,match
,方法,关联函数,使用外部crate,等等。我们的下一个项目将会展示更多的东西。