产生一个神秘数字
接下来,我们需要产生一个神秘数字。Rust在它的标准库中还没有包括随机数函数。然而,Rust团队确实提供了一个一个rand crate。‘crate’是Rust代码的一个包。我们已经构建了一个二进制crate,是可执行的。rand是一个库crate‘library crate’,包括可以被其他程序使用的代码。
使用外部crate是Cargo真正的闪光点。在我们可以使用rand写代码之前,我们需要修改我们的Cargo.toml。打开该文件,并在底部添加下列代码:
[dependencies] rand="0.3.0"
Cargo.toml文件的[dependencies]节跟[package]节一样:所有跟随在它之后的都是它的一部分,直到下一节的开始。Cargo使用dependencies节得知你需要什么样的外部crate和需要什么版本的。在该例子中我们使用0.3.0版本。Cargo知道语义化版本,语义化版本是一个书写版本号的标准。如果要使用最新版本,我们可以使用*或我们使用一个版本边界。Cargo的文档包含更多细节。
现在,无需修改我们的代码,让我们构建我们的项目:
$ 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从registry获取最新版本的所有东西,从Crates.io处的数据拷贝。Crates.io是在Rust系统中的人们提交他们可供他人使用的开源项目的地方。
更新完registry之后,Cargo检查我们的[dependencies]并下载我们还没有的东西。在这里,尽管我们说我们依赖于rand,我们也抓取了libc的拷贝。这是因为rand依赖于libc来工作。下载它们之后,编译它们,最后编译我们的项目。
如果我们再次运行cargo build,我们将会得到不同的输出:
$ cargo build
这是对的,没有输出!Cargo知道我们的项目已经被构建,它所有的依赖都已经构建,所以没有理由再做这些工作。无事可做,它简单退出了。如果我们打开src/main.rs,做一个不重要的修改,保存,我们将会看到一行:
$ cargo build Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
我们告诉Cargo我们需要任何0.3.x版本的rand,所以它取回了当前的最新版本v0.3.8。但是下个星期会发生什么呢,版本v0.3.9出来了,修复了一个重要bug?修复bug是重要的,如果0.3.9包含退化,破坏我们的代码怎么办?
这个问题的答案是在你的工程目录里你将会找到Cargo.lock文件。但你第一次构建你的工程时,Cargo指出所有适合你条件的版本,并把它们写回Cargo.lock文件。当你未来构建你的项目时,Cargo将会看到Cargo.lock文件存在,并会使用特定的版本,而不会做些再次指出版本的工作。这允许你可以重复的自动创建。换句话说,我们将会停留在0.3.8版本,除非我们明确地升级,对于共享我们代码的任何人都是这样的,感谢lock文件。
当我们想使用v0.3.9时怎么办呢?Cargo有另外一个命令,update,意思是说‘忽略lock文件,找出所有的适合我们知名的所有最新版本。如果它们好使,就把它们吸出到lock文件’。但是,默认情况下Cargo只会查找大于0.3.0小于0.4.0的版本。如果我们想使用0.4.x,我们不得不直接修改Cargo.toml。当我们这么做了,下一次我们cargo build时,Cargo将会升级索引并重新评估我们的rand需求。
对于Cargo和它的‘生态系统(ecosystem)’要说的还有很多,但不是现在,我们已经知道了需要知道的。Cargo使重用库变的很容易,所以Rustaceans趋向写一些小程序,这些小程序会包含一些子包。
让我们继续使用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。因为我们在我们的[dependencies]定义了rand,我们可以使用extern
crate让Rust知道我们将会使用它。着等价于use rand;所以我们可以通过它的前缀rand::使用rand中的所有东西。
接下来,我们增加了另外一个use行:use rand::Rng。在某刻我们将会使用一个方法,该方法需要Rng在作用域内。基本的思想是:方法定义在称作‘traits’的上面,为了是方法正常工作,它需要‘trait’在作用域内。更多细节,查阅traits节。
我们中间我们增加了额外的两行:
let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number);
我们使用rand::thread_rng()函数获得随机数生成器的一个拷贝,该生成器定位到我们执行的本地特定线程。因为我们上面使用rand::Rng,它有一个gen_range()方法可用。这个方法接受两个参数,并产生一个介于它们之间的数。包括下界,不包括上界,所以我们需要1和101产生一个1和100之间的数。
第二行仅仅打印出神秘数字。在我们的开发程序时是有用的,这样我们可以容易测试。但是在最终版本我们会删掉它。如果在你启动的时候它打印出了答案,它将不再是一个游戏。
花一些时间运行我们的新程序:
$ 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
非常好!下一步:让我们猜测的数字跟什么数字做比较。