• 猜猜看
    • 准备
    • 处理一次猜测
    • 生成一个秘密数字
    • 比较猜测
    • 循环
    • 完成!

    猜猜看

    guessing-game.md


    commit 602fa7c6eba1b0fbeca0847fe295d9137e9a0243

    让我们学习一些 Rust!作为第一个项目,我们来实现一个经典新手编程问题:猜猜看游戏。它是这么工作的:程序将会随机生成一个 1 到 100 之间的随机数。它接着会提示猜一个数。当我们猜了一个数之后,它会告诉我们是太大了还是太小了。猜对了,它会祝贺我们。听起来如何?

    准备

    让我们准备一个新项目。进入到项目目录。还记得之前如何创建hello_world的项目目录和Cargo.toml文件的吗?Cargo 有一个命令来做这些。让我们试试:

    1. $ cd ~/projects
    2. $ cargo new guessing_game --bin
    3. Created binary (application) `guessing_game` project
    4. $ cd guessing_game

    我们将项目名字传递给cargo new,然后用了--bin标记,因为要创建一个二进制文件,而不是一个库文件。

    查看生成的Cargo.toml文件:

    1. [package]
    2. name = "guessing_game"
    3. version = "0.1.0"
    4. authors = ["Your Name <you@example.com>"]

    Cargo 从系统环境变量中获取这些信息。如果这不对,赶紧修改它。

    最后,Cargo 为我们生成了一个“Hello, world!”。查看src/main.rs文件:

    1. fn main() {
    2. println!("Hello, world!");
    3. }

    让我们编译 Cargo 为我们生成的项目:

    1. $ cargo build
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. Finished debug [unoptimized + debuginfo] target(s) in 0.53 secs

    很好!再次打开你的src/main.rs文件。我们会将所有代码写在这个文件里。稍后我们会讲到多文件项目。

    还记得上一章节讲到的run命令吗?让我们再次试试它:

    1. $ cargo run
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
    4. Running `target/debug/guessing_game`
    5. Hello, world!

    很好!我们的小游戏恰恰是run命令大显身手的这类程序:我们需要在进行下一步之前快速测试每次迭代。

    处理一次猜测

    让我们开始吧!我们需要做的第一件事是让我们的玩家输入一个猜测。把这些放入你的src/main.rs

    1. use std::io;
    2. fn main() {
    3. println!("Guess the number!");
    4. println!("Please input your guess.");
    5. let mut guess = String::new();
    6. io::stdin().read_line(&mut guess)
    7. .expect("Failed to read line");
    8. println!("You guessed: {}", guess);
    9. }

    这有好多东西!让我们一点一点地过一遍。

    1. use std::io;

    我们需要获取用户输入,并接着打印结果作为输出。为此,我们需要标准库的io库。Rust 为所有程序只导入了很少一些东西,‘prelude’。如果它不在预先导入中,你将不得不直接use它。这还有第二个”prelude”,ioprelude,它也起到了类似的作用:你引入它,它引入一系列拥有的 IO 相关的库。

    1. fn main() {

    就像你之前见过的,main()是你程序的入口点。fn语法声明了一个新函数,()表明这里没有参数,而{开始了函数体。因为不包含返回类型,它假设是(),一个空的元组。

    1. println!("Guess the number!");
    2. println!("Please input your guess.");

    我们之前学过println!()是一个在屏幕上打印字符串的宏。

    1. let mut guess = String::new();

    现在我们遇到有意思的东西了!这一小行有很多内容。第一个我们需要注意到的是let语句,它用来创建“变量绑定”。它使用这个形式:

    1. let foo = bar;

    这会创建一个叫做foo的新绑定,并绑定它到bar这个值上。在很多语言中,这叫做一个“变量”,不过 Rust 的变量绑定暗藏玄机。

    例如,它们默认是不可变的。这时为什么我们的例子使用了mut:它让一个绑定可变,而不是不可变。let并不从左手边获取一个名字,事实上它接受一个模式(pattern)。我们会在后面更多的使用模式。现在它使用起来非常简单:

    1. let foo = 5; // immutable.
    2. let mut bar = 5; // mutable

    噢,同时//会开始一个注释,直到这行的末尾。Rust 忽略注释中的任何内容。

    那么现在我们知道了let mut guess会引入一个叫做guess的可变绑定,不过我们也必须看看=的右侧所绑定的内容:String::new()

    String是一个字符串类型,由标准库提供。String是一个可增长的,UTF-8编码的文本。

    ::new()语法用了::因为它是一个特定类型的”关联函数“。这就是说,它与String自身关联,而不是与一个特定的String实例关联。一些语言管这叫一个“静态方法”。

    这个函数叫做new(),因为它创建了一个新的,空的String。你会在很多类型上找到new()函数,因为它是创建一些类型新值的通常名称。

    让我们继续:

    1. io::stdin().read_line(&mut guess)
    2. .expect("Failed to read line");

    这稍微有点多!让我们一点一点来。第一行有两部分。这是第一部分:

    1. io::stdin()

    还记得我们如何在程序的第一行use std::io的吗?现在我们调用了一个与之相关的函数。如果我们不use std::io,那么我们就得写成std::io::stdin()

    这个特殊的函数返回一个指向你终端标准输入的句柄。更具体的,可参考std::io::Stdin。

    下一部分将用这个句柄去获取用户输入:

    1. .read_line(&mut guess)

    这里,我们对我们的句柄调用了read_line()方法。“方法”就像关联函数,不过只在一个类型的特定实例上可用,而不是这个类型本身。我们也向read_line()传递了一个参数:&mut guess

    还记得我们上面怎么绑定guess的吗?我们说它是可变的。然而,read_line并不接收String作为一个参数:它接收一个&mut String。Rust有一个叫做“引用”的功能,它允许你对一片数据有多个引用,用它可以减少拷贝。引用是一个复杂的功能,因为Rust的一个主要卖点就是它如何安全和便捷地使用引用。然而,目前我们还不需要知道很多细节来完成我们的程序。现在,所有我们需要了解的是像let绑定,引用默认是不可变的。因此,我们需要写成&mut guess,而不是&guess

    为什么read_line()会需要一个字符串的可变引用呢?它的工作是从标准输入获取用户输入,并把它放入一个字符串。所以它用字符串作为参数,为了可以增加输入,它必须是可变的。

    不过我们还未完全看完这行代码。虽然它是单独的一行代码,但只是这个单独逻辑代码行的开头部分:

    1. .expect("Failed to read line");

    当你用.foo()语法调用一个函数的时候,你可能会引入一个新行符或其它空白。这帮助我们拆分长的行。我们可以这么干:

    1. io::stdin().read_line(&mut guess).expect("failed to read line");

    不过这样会难以阅读。所以我们把它分开,3 行对应 3 个方法调用。我们已经谈论过了read_line(),不过expect()呢?好吧,我们已经提到过read_line()将用户输入放入我们传递给它的&mut String中。不过它也返回一个值:在这个例子中,一个io::Result。Rust的标准库中有很多叫做Result的类型:一个泛型Result,然后是子库的特殊版本,例如io::Result

    这个Result类型的作用是编码错误处理信息。Result类型的值,像任何(其它)类型,有定义在其上的方法。在这个例子中,io::Result有一个expect()方法获取调用它的值,而且如果它不是一个成功的值,panic!并带有你传递给它的信息。这样的panic!会使我们的程序崩溃,显示(我们传递的)信息。

    如果我们去掉这两个函数调用,我们的程序会编译通过,不过我们会得到一个警告:

    1. $ cargo build
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. warning: unused result which must be used, #[warn(unused_must_use)] on by default
    4. --> src/main.rs:10:5
    5. |
    6. 10 | io::stdin().read_line(&mut guess);
    7. | ^
    8. Finished debug [unoptimized + debuginfo] target(s) in 0.42 secs

    Rust警告我们我们并未使用Result的值。这个警告来自io::Result的一个特殊注解。Rust 尝试告诉你你并未处理一个可能的错误。阻止错误的正确方法是老实编写错误处理。幸运的是,如果我们只是想如果这有一个问题就崩溃的话,我们可以用这两个小方法。如果我们想从错误中恢复什么的,我们得做点别的,不过我们会把它留给接下来的项目。

    这是我们第一个例子仅剩的一行:

    1. println!("You guessed: {}", guess);
    2. }

    这打印出我们保存输入的字符串。{}是一个占位符,所以我们传递guess作为一个参数。如果我们有多个{},我们应该传递多个参数:

    1. let x = 5;
    2. let y = 10;
    3. println!("x and y: {} and {}", x, y);

    轻松加愉快。

    总而言之,这只是一个观光。我们可以用cargo run运行代码:

    1. $ cargo run
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. Finished debug [unoptimized + debuginfo] target(s) in 0.44 secs
    4. Running `target/debug/guessing_game`
    5. Guess the number!
    6. Please input your guess.
    7. 6
    8. You guessed: 6

    好的!我们的第一部分完成了:我们可以从键盘获取输入,并把它打印回去。

    生成一个秘密数字

    接下来,我们要生成一个秘密数字。Rust标准库中还未包含随机数功能。然而,Rust 团队确实提供了一个rand crate。一个“包装箱”(crate)是一个 Rust 代码的包。我们已经构建了一个”二进制包装箱“,它是一个可执行文件。rand是一个”库包装箱“,它包含被认为应该被其它程序使用的代码。

    使用外部包装箱是 Cargo 的亮点。在我们使用rand编写代码之前,我们需要修改我们的Cargo.toml。打开它,并在末尾增加这几行:

    1. [dependencies]
    2. rand = "0.3.0"

    Cargo.toml[dependencies]部分就像[package]部分:所有之后的东西都是它的一部分,直到下一个部分开始。Cargo使用依赖部分来知晓你用的外部包装箱的依赖,和你要求的版本。在这个例子中,我们用了0.3.0版本。Cargo理解语义化版本,它是一个编写版本号的标准。像上面只有数字的版本事实上是^0.3.0的简写,代表“任何兼容 0.3.0 的版本”。如果你只想使用0.3.0版本,你可以使用rand = "=0.3.0"(注意那两个双引号)。我们也可以指定一个版本范围。Cargo文档包含更多细节。

    现在,在不修改任何我们代码的情况下,让我们构建我们的项目:

    1. $ cargo build
    2. Updating registry `https://github.com/rust-lang/crates.io-index`
    3. Downloading rand v0.3.14
    4. Downloading libc v0.2.17
    5. Compiling libc v0.2.17
    6. Compiling rand v0.3.14
    7. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    8. Finished debug [unoptimized + debuginfo] target(s) in 5.88 secs

    (当然,你可能会看到不同的版本)

    很多新的输出!现在我们有了一个外部依赖,Cargo 从记录中获取了所有东西的最新版本,它们是来自Crates.io的一份拷贝。Crates.io 是 Rust 生态系统中人们发表开源 Rust 项目供他人使用的地方。

    在更新了记录后,Cargo 检查我们的[dependencies]并下载任何我们还没有的东西。在这个例子中,虽然我们只说了我们要依赖rand,我们也获取了一份libc的拷贝。这是因为rand依赖libc工作。在下载了它们之后,它编译它们,然后接着编译我们的项目。

    如果我们再次运行cargo build,我们会得到不同的输出:

    1. $ cargo build
    2. Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs

    没错,没有输出!Cargo 知道我们的项目被构建了,并且所有它的依赖也被构建了,所以没有理由再做一遍所有这些。没有事情做,它简单地退出了。如果我们再打开src/main.rs,做一个无所谓的修改,然后接着再保存,我们就会看到一行:

    1. $ cargo build
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. Finished debug [unoptimized + debuginfo] target(s) in 0.45 secs

    所以,我们告诉Cargo我们需要任何0.3.x版本的rand,并且因此它获取在本文被编写时的最新版,v0.3.14。不过你瞧瞧当下一周,v0.3.15出来了,带有一个重要的 bug 修改吗?虽然 bug 修改很重要,不过如果0.3.15版本包含破坏我们代码的回归缺陷(regression)呢?

    这个问题的回答是现在你会在你项目目录中找到的Cargo.lock。当你第一次构建你的项目的时候,Cargo 查明所有符合你的要求的版本,并接着把它们写到了Cargo.lock文件里。当你在未来构建你的项目的时候,Cargo 会注意到Cargo.lock的存在,并接着使用指定的版本而不是再次去做查明版本的所有工作。这让你有了一个可重复的自动构建。换句话说,我们会保持在0.3.8直到我们显式的升级,这对任何使用我们共享的代码的人同样有效,感谢锁文件。

    当我们确实想要使用v0.3.15怎么办?Cargo 有另一个命令,update,它代表“忽略锁,搞清楚所有我们指定的最新版本。如果这能工作,将这些版本写入锁文件”。不过,默认,Cargo 只会寻找大于0.3.0小于0.4.0的版本。如果你想要移动到0.4.x,我们不得不直接更新Cargo.toml文件。当我们这么做,下一次我们cargo build,Cargo会更新索引并重新计算我们的rand要求。

    关于Cargo和它的生态系统有很多东西要说,不过眼下,这是我们需要知道的一切。Cargo让重用库变得真正的简单,并且Rustacean们可以编写更小的由很多子包组装成的项目。

    让我们真正的使用rand,这是我们的下一步:

    1. extern crate rand;
    2. use std::io;
    3. use rand::Rng;
    4. fn main() {
    5. println!("Guess the number!");
    6. let secret_number = rand::thread_rng().gen_range(1, 101);
    7. println!("The secret number is: {}", secret_number);
    8. println!("Please input your guess.");
    9. let mut guess = String::new();
    10. io::stdin().read_line(&mut guess)
    11. .expect("failed to read line");
    12. println!("You guessed: {}", guess);
    13. }

    先修改第一行。现在它是extern crate rand。因为在[dependencies]声明了rand,我们可以用extern crate来让Rust知道我们正在使用它。这也等同于一个use rand;,所以我们可以通过rand::前缀使用rand包装箱中的一切。

    下一步,我们增加了另一行useuse rand::Rng。我们一会将要使用一个方法,并且它要求Rng在作用域中才能工作。这个基本观点是:方法定义在一些叫做“特性(traits,也有译作特质)”的东西上面,而为了让方法能够工作,需要这个特性位于作用域中。关于更多细节,阅读trait部分。

    这里还有两行我们增加的,在中间:

    1. let secret_number = rand::thread_rng().gen_range(1, 101);
    2. println!("The secret number is: {}", secret_number);

    我们用rand::thread_rng()函数来获取一个随机数生成器的拷贝,它位于我们特定的执行线程的本地。因为use rand::Rng了,有一个gen_range()方法可用。这个函数获取两个参数,并产生一个位于其间的数字。它包含下限,不过不包含上限,所以需要1101来生成一个1100之间的数。

    第二行仅仅打印出了秘密数字,这在开发程序时做简单测试很有用。不过在最终版本中我们会删除它。在开始就打印出结果就没什么可玩的了!

    尝试运行新程序几次:

    1. $ cargo run
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. Finished debug [unoptimized + debuginfo] target(s) in 0.55 secs
    4. Running `target/debug/guessing_game`
    5. Guess the number!
    6. The secret number is: 7
    7. Please input your guess.
    8. 4
    9. You guessed: 4
    10. $ cargo run
    11. Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
    12. Running `target/debug/guessing_game`
    13. Guess the number!
    14. The secret number is: 83
    15. Please input your guess.
    16. 5
    17. You guessed: 5

    好的!接下来:让我们比较我们的猜测和秘密数字。

    比较猜测

    现在我们得到了用户输入,让我们比较我们的猜测和随机值。这是我们的下一步,虽然它还不能正常工作:

    1. extern crate rand;
    2. use std::io;
    3. use std::cmp::Ordering;
    4. use rand::Rng;
    5. fn main() {
    6. println!("Guess the number!");
    7. let secret_number = rand::thread_rng().gen_range(1, 101);
    8. println!("The secret number is: {}", secret_number);
    9. println!("Please input your guess.");
    10. let mut guess = String::new();
    11. io::stdin().read_line(&mut guess)
    12. .expect("failed to read line");
    13. println!("You guessed: {}", guess);
    14. match guess.cmp(&secret_number) {
    15. Ordering::Less => println!("Too small!"),
    16. Ordering::Greater => println!("Too big!"),
    17. Ordering::Equal => println!("You win!"),
    18. }
    19. }

    这有一些新东西。第一个是另一个use。我们带来了一个叫做std::cmp::Ordering类型到作用域中。接着,底部5行代码使用了它:

    1. match guess.cmp(&secret_number) {
    2. Ordering::Less => println!("Too small!"),
    3. Ordering::Greater => println!("Too big!"),
    4. Ordering::Equal => println!("You win!"),
    5. }

    cmp()可以在任何能被比较的值上调用,并且它获取你想要比较的值的引用。它返回我们之前useOrdering类型。我们使用一个match语句来决定具体是哪种OrderingOrdering是一个枚举(enum),它看起来像这样:

    1. enum Foo {
    2. Bar,
    3. Baz,
    4. }

    通过这个定义,任何Foo可以是Foo::Bar或者Foo::Baz。我们用::来表明一个特定enum变量的命名空间。

    Ordering枚举有3个可能的变量:LessEqualGreatermatch语句获取类型的值,并让你为每个可能的值创建一个“分支”。因为有 3 种类型的Ordering,我们有 3 个分支:

    1. match guess.cmp(&secret_number) {
    2. Ordering::Less => println!("Too small!"),
    3. Ordering::Greater => println!("Too big!"),
    4. Ordering::Equal => println!("You win!"),
    5. }

    如果它是Less,我们打印Too small!,如果它是GreaterToo big!,而如果EqualYou win!match真的非常有用,并且在 Rust 中经常使用。

    我确实提到过我们还不能正常运行,虽然。让我们试试:

    1. $ cargo build
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. error[E0308]: mismatched types
    4. --> src/main.rs:23:21
    5. |
    6. 23 | match guess.cmp(&secret_number) {
    7. | ^^^^^^^^^^^^^^ expected struct `std::string::String`, found integral variable
    8. |
    9. = note: expected type `&std::string::String`
    10. = note: found type `&{integer}`
    11. error: aborting due to previous error
    12. error: Could not compile `guessing_game`.
    13. To learn more, run the command again with --verbose.

    噢!这是一个大错误。它的核心是我们有“不匹配的类型”。Rust 有一个强大的静态类型系统。然而,它也有类型推断。当我们写let guess = String::new(),Rust能够推断出guess应该是一个String,并因此不需要我们写出类型。而我们的secret_number,这有很多类型可以有从1100的值:i32,一个 32 位数,或者u32,一个无符号的32位值,或者i64,一个 64 位值。或者其它什么的。目前为止,这并不重要,所以 Rust 默认为i32。然而,这里,Rust 并不知道如何比较guesssecret_number。它们必须是相同的类型。最终,我们想要我们作为输入读到的String转换为一个真正的数字类型,来进行比较。我们可以用额外 3 行来搞定它。这是我们的新程序:

    1. extern crate rand;
    2. use std::io;
    3. use std::cmp::Ordering;
    4. use rand::Rng;
    5. fn main() {
    6. println!("Guess the number!");
    7. let secret_number = rand::thread_rng().gen_range(1, 101);
    8. println!("The secret number is: {}", secret_number);
    9. println!("Please input your guess.");
    10. let mut guess = String::new();
    11. io::stdin().read_line(&mut guess)
    12. .expect("failed to read line");
    13. let guess: u32 = guess.trim().parse()
    14. .expect("Please type a number!");
    15. println!("You guessed: {}", guess);
    16. match guess.cmp(&secret_number) {
    17. Ordering::Less => println!("Too small!"),
    18. Ordering::Greater => println!("Too big!"),
    19. Ordering::Equal => println!("You win!"),
    20. }
    21. }

    新的两行是:

    1. let guess: u32 = guess.trim().parse()
    2. .expect("Please type a number!");

    稍等,我认为我们已经用过了一个guess?确实,不过 Rust 允许我们用新值“遮盖(shadow)”之前的guess。这在这种具体的情况中经常被用到,guess开始是一个String,不过我们想要把它转换为一个u32。遮盖(Shadowing)让我们重用guess名字,而不是强迫我们想出两个独特的像guess_strguess,或者别的什么。

    我们绑定guess到一个看起来像我们之前写的表达式:

    1. guess.trim().parse()

    这里,guess引用旧的guess,那个我们输入用到的StringStringtrim()方法会去掉我们字符串开头和结尾的任何空格。这很重要,因为我们不得不按“回车”键来满足read_line()。这意味着如果输入5并按回车,guess看起来像这样:5\n\n代表“新行”,回车键。trim()去掉这些,保留5给我们的字符串。字符串的parse()方法将字符串解析为一些类型的数字。因为它可以解析多种数字,我们需要给Rust一些提醒作为我们具体想要的数字的类型。因此,let guess: u32guess后面的分号(:)告诉 Rust 我们要标注它的类型。u32是一个无符号的,32位整型。Rust 有一系列内建数字类型,不过我们选择了u32。它是一个小正数的默认好选择。

    就像read_line(),我们调用parse()可能产生一个错误。如果我们的字符串包含A?%?呢?并不能将它们转换成一个数字。为此,我们将做我们在read_line()时做的相同的事:使用expect()方法来在这里出现错误时崩溃。

    让我们尝试下我们的程序!

    1. $ cargo run
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. Finished debug [unoptimized + debuginfo] target(s) in 0.57 secs
    4. Running `target/guessing_game`
    5. Guess the number!
    6. The secret number is: 58
    7. Please input your guess.
    8. 76
    9. You guessed: 76
    10. Too big!

    很好!你可以看到我甚至在我的猜测前加上了空格,不过它仍然识别出我猜了 76。运行这个程序几次,并检测猜测正确的值,和小的值。

    现在我们让游戏大体上能玩了,不过我们只能猜一次。让我们增加循环来改变它!

    循环

    loop关键字给我们一个无限循环。让我们加上它:

    1. extern crate rand;
    2. use std::io;
    3. use std::cmp::Ordering;
    4. use rand::Rng;
    5. fn main() {
    6. println!("Guess the number!");
    7. let secret_number = rand::thread_rng().gen_range(1, 101);
    8. println!("The secret number is: {}", secret_number);
    9. loop {
    10. println!("Please input your guess.");
    11. let mut guess = String::new();
    12. io::stdin().read_line(&mut guess)
    13. .expect("failed to read line");
    14. let guess: u32 = guess.trim().parse()
    15. .expect("Please type a number!");
    16. println!("You guessed: {}", guess);
    17. match guess.cmp(&secret_number) {
    18. Ordering::Less => println!("Too small!"),
    19. Ordering::Greater => println!("Too big!"),
    20. Ordering::Equal => println!("You win!"),
    21. }
    22. }
    23. }

    并试试看。不过稍等,难道我们仅仅加上一个无限循环吗?是的。记得我们我们关于parse()的讨论吗?如果我们给出一个非数字回答,明显我们会panic!并退出:

    1. $ cargo run
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. Finished debug [unoptimized + debuginfo] target(s) in 0.58 secs
    4. Running `target/guessing_game`
    5. Guess the number!
    6. The secret number is: 59
    7. Please input your guess.
    8. 45
    9. You guessed: 45
    10. Too small!
    11. Please input your guess.
    12. 60
    13. You guessed: 60
    14. Too big!
    15. Please input your guess.
    16. 59
    17. You guessed: 59
    18. You win!
    19. Please input your guess.
    20. quit
    21. thread 'main' panicked at 'Please type a number!'

    啊哈!quit确实退出了。就像任何其它非数字输入。好吧,这至少不是最差的想法。首先,如果你赢得了游戏,那我们就真的退出它:

    1. extern crate rand;
    2. use std::io;
    3. use std::cmp::Ordering;
    4. use rand::Rng;
    5. fn main() {
    6. println!("Guess the number!");
    7. let secret_number = rand::thread_rng().gen_range(1, 101);
    8. println!("The secret number is: {}", secret_number);
    9. loop {
    10. println!("Please input your guess.");
    11. let mut guess = String::new();
    12. io::stdin().read_line(&mut guess)
    13. .expect("failed to read line");
    14. let guess: u32 = guess.trim().parse()
    15. .expect("Please type a number!");
    16. println!("You guessed: {}", guess);
    17. match guess.cmp(&secret_number) {
    18. Ordering::Less => println!("Too small!"),
    19. Ordering::Greater => println!("Too big!"),
    20. Ordering::Equal => {
    21. println!("You win!");
    22. break;
    23. }
    24. }
    25. }
    26. }

    通过在You win!后增加break,我们将在你赢了后退出循环。退出循环也意味着退出程序,因为它是main()中最后的东西。我们仅仅需要再做一个小修改:当谁输入了一个非数字,我们并不想退出,我们就想忽略它。我们可以这么做:

    1. extern crate rand;
    2. use std::io;
    3. use std::cmp::Ordering;
    4. use rand::Rng;
    5. fn main() {
    6. println!("Guess the number!");
    7. let secret_number = rand::thread_rng().gen_range(1, 101);
    8. println!("The secret number is: {}", secret_number);
    9. loop {
    10. println!("Please input your guess.");
    11. let mut guess = String::new();
    12. io::stdin().read_line(&mut guess)
    13. .expect("failed to read line");
    14. let guess: u32 = match guess.trim().parse() {
    15. Ok(num) => num,
    16. Err(_) => continue,
    17. };
    18. println!("You guessed: {}", guess);
    19. match guess.cmp(&secret_number) {
    20. Ordering::Less => println!("Too small!"),
    21. Ordering::Greater => println!("Too big!"),
    22. Ordering::Equal => {
    23. println!("You win!");
    24. break;
    25. }
    26. }
    27. }
    28. }

    这是改变了的行:

    1. let guess: u32 = match guess.trim().parse() {
    2. Ok(num) => num,
    3. Err(_) => continue,
    4. };

    这是你如何大体上从“错误就崩溃”移动到“确实处理错误”,通过从expect()切换到一个match语句。parse()返回的Result就是一个像Ordering一样的枚举,不过在这个例子中,每个变量有一些数据与之相关:Ok是一个成功,而Err是一个失败。每一个都包含更多信息:成功的解析为整型,或一个错误类型。在这个例子中,我们matchOk(num),它设置了Ok内叫做num的值,接着在右侧返回它。在Err的情况下,我们并不关心它是什么类型的错误,所以我们仅仅使用_而不是一个名字。这忽略错误,并continue造成我们进行loop的下一次迭代。

    现在应该搞定了!试试看:

    1. $ cargo run
    2. Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
    3. Finished debug [unoptimized + debuginfo] target(s) in 0.57 secs
    4. Running `target/guessing_game`
    5. Guess the number!
    6. The secret number is: 61
    7. Please input your guess.
    8. 10
    9. You guessed: 10
    10. Too small!
    11. Please input your guess.
    12. 99
    13. You guessed: 99
    14. Too big!
    15. Please input your guess.
    16. foo
    17. Please input your guess.
    18. 61
    19. You guessed: 61
    20. You win!

    狂拽炫酷!通过一个最后的修改,我们就完成了猜猜看游戏。你能想到它是什么吗?对了,我们并不想打印出秘密数字。它有利于测试,不过有点毁游(san)戏(guan)的味道。这是最终源码:

    1. extern crate rand;
    2. use std::io;
    3. use std::cmp::Ordering;
    4. use rand::Rng;
    5. fn main() {
    6. println!("Guess the number!");
    7. let secret_number = rand::thread_rng().gen_range(1, 101);
    8. loop {
    9. println!("Please input your guess.");
    10. let mut guess = String::new();
    11. io::stdin().read_line(&mut guess)
    12. .expect("failed to read line");
    13. let guess: u32 = match guess.trim().parse() {
    14. Ok(num) => num,
    15. Err(_) => continue,
    16. };
    17. println!("You guessed: {}", guess);
    18. match guess.cmp(&secret_number) {
    19. Ordering::Less => println!("Too small!"),
    20. Ordering::Greater => println!("Too big!"),
    21. Ordering::Equal => {
    22. println!("You win!");
    23. break;
    24. }
    25. }
    26. }
    27. }

    完成!

    这第一个项目展示了:letmatch、方法、关联函数、使用外部包装箱等。

    此刻,你成功地构建了猜猜看游戏!恭喜!