比较
既然我们获得了用户的输入,让我们把猜测的数据跟神秘数字做比较。这是我们的下一步,尽管它还不能真正工作:
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。我们带来了一个新类型叫做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。Ordering是一个枚举(enum),‘enumeration’的缩写,看起来是这样子的:
enum Foo{ Bar, Baz, }
有了这个定义,所有是Foo类型的不是Foo::Bar就是Foo::Baz。我们使用::来指明特定枚举变量的命名空间。
Ordering枚举类型有三种可能的变化:Less、Equal和Greater。match声明使用一种类型的值,并为每种可能的值创建一个分支‘arm’。因为我们有三种类型的Ordering,我们有三种分支(arms):
match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), }
如果是Less,我们打印Too small!,如果是Greater,打印Too big!,如果是Equal,打印You win!。match很有用,在Rust中经常用到。
我提到过这个程序不能工作。让我们试一下:
$ 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`.
这是一个严重的错误。主要的愿意是我们有‘不匹配的类型’。Rust有一个强、静态类型系统。然而,它有类型推断功能。当我们写let guess = String::new()时,Rust能够推断出guess是一个String类型,所以它没有要求我们写出类型。对于我们的secret_number,有一些类型可以有1-100之间的值:i32,一个32位的数,或者u32,一个无符号32位数,或i64,一个64位数。或者其它的。迄今为止,这都不是问题,Rust默认是一个i32.然而,在这里,Rust不知道怎么比较guess和secret_number。他们需要是相同的类型。最终,我们想将我们读取到的String转换成一个真正的数字类型。我们可以通过额外的三行代码实现。这是我们的新程序:
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!"), } }
新的3行是:
let guess: u32 = guess.trim().parse() .ok() .expect("Please type a number!");
等一下,我们不是已经有一个guess了吗?我们确实有了,但是Rust允许我们使用一个新的隐藏(shadow)先前的。在这种情况下经常使用,当guess以String开始,但是我们想将其转换成一个u32类型的数据。隐藏技术允许我们重用guees名字,而不是强制我们想出两个不一样的名字,例如guess_str和guess,或者其它的。
我们将guess绑定到一个与我们先前看起来有些相像的表达式上:
guess.trim().parse()
跟随着一个ok().expect()调用。这里guess指的是老guess,也就是保存我们输入的那个String。String上的trim()方法将会消除字符串开头和结尾的所有空白字符。这个很重要,因为我们要满足read_line()必须按下回车键(return)。这意味着如果我们输入5并敲击回车键,guess看起来将会这样:5\n。\n代表换行(newline),输入(enter)键。trim()去掉它,留下我们的字符串仅仅包含5。字符串上的的parse()方法将字符串解析成某种类型的数字。因为它可以解析多种数字,我们需要给Rust一个暗示,来获取我们想要的数字的类型。因此,let
guess: u32。guess后面的冒号(:)告诉Rust我们将阐明它的类型。u32是一个无符号、32位整数。Rust有一些内建数字类型,但是我们已经选择u32.对于一个小正整数这是一个很好的默认选择。
跟read_line()一样。我们的对parse()的调用会产生一个错误。如果我们的字符串包含A%将会怎样?将没有方法将其转换成数字。同样,我们将会跟read_line()一样做同样的事情:如果有错误发生,使用ok()和expect()方法使程序崩溃。
让我们试一下程序!
$ cargo run Compiling guessing_game v0.0.1 (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。多运行几次程序,验证猜数字程序正常工作。
现在我们可以是程序的大部分工作,但是我们只能裁一次。让我们使用循环改改它!