教程 Rust book stable - 3.1 - guessing game

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的一个命令:runcargo 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,Greatermatch语句提取类型中的值,并允许你为每个可能的值创建一个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_numberi32类型,而guessString类型。而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我们想要的类型是u32u32是无符号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,等等。我们的下一个项目将会展示更多的东西。

转载于:https://my.oschina.net/misaka15842/blog/486341

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值