1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 背景
本文基于 Rust
文档 Programming a Guessing Game 翻译整理而成。
3. 猜数字游戏
3.1 概述
程序随机生成一个数字,并告知用户数据所处区间范围;用户根据区间范围随意输入一个数字,程序读取用户输入的数字,将其和目标数字对比,并提示数字是大了还是小了;然后用户根据提示继续输入,直到猜中目标数字为止。
从前面的描述可以看到,猜数字游戏,程序需要处理:
- 随机数字生成
- 读取用户输入
- 数字比较
- 输出提示信息
- 循环逻辑
3.2 实现
首先,我们给出一个初始版本,这个版本没有随机生成目标数字、以及进行数字比较的功能,它仅仅是打印用户输入的数字。先用 Cargo
构建一个代码工程:
$ cargo new guessing_game
Created binary (application) `guessing_game` package
然后对代码文件 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)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
这里有几个需要了解的新知识点:
- use std::io;
这类似于C
语言的#include
,Java、Python
的import
,Perl
的use
,等等。
总之,这就是Rust
对其它库接口的导入方式
。这里是导入了Rust
标准 IO 库。 - String::new();
::
在这里用来引用某个作用域内的接口。这里创建了一个空的字符串类型对象。 - let mut guess = String::new();
let
关键字用来定义变量,而mut
是使得变量可以修改(mutable)
。
默认情况下,let
定义的变量是不可修改的(immutable)
。
我们注意到,Rust
定义变量,没有显式指定类型。 - io::stdin()
构建了一个std::io::Stdin
实例对象。 - io::stdin().read_line(&mut guess)
调用std::io::Stdin
实例对象的接口函数read_line()
,用于读取一行用户输入;
read_line(&mut guess)
的参数加&
表示使用guess
的引用,但默认
情况下,
对象的引用是只读
的,及read_line()
无法通过guess
的引用修改guess
,
这时候加上mut
修饰,使得read_line()
可以通过guess
的引用修改guess
。 - io::stdin().read_line(&mut guess).expect(“Failed to read line”);
std::io::Stdin
实例对象的接口函数read_line()
返回一个Result
类型的枚举值;
而Result
类型的枚举值
可以是Ok
和Err
:当为Ok
时,调用expect()
不
做任何操作,而当为Err
时,调用expect()
将打印信息Failed to read line
。
编译、运行:
$ cargo build
Compiling guessing_game v0.1.0 (/home/bill/Study/rust/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 4.42s
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
8
You guessed: 8
到此,初始版本已经构建完毕。接下来,我们引入随机数模块,随机生成 [1,100]
的数字,并增加对用户输入数字的判定。Rust 的标准库不包含随机数模块,随机数模块是一个第三方库。Rust
引入第三方库的方式是通过编辑 Cargo
构建工程的 Cargo.toml
文件的 [dependencies]
段。按如下编辑 guessing_game/Cargo.toml
的 [dependencies]
段引入 rand
库:
[dependencies]
rand = "0.8.5"
不改变任何代码
,再次构建工程,会发现构建过程下载了 rand
库:
$ cargo build
warning: `/home/XXX/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/XXX/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
Updating `tuna` index
remote: Enumerating objects: 1102, done.
remote: Counting objects: 100% (292/292), done.
remote: Total 1102 (delta 292), reused 292 (delta 292), pack-reused 810
Receiving objects: 100% (1102/1102), 527.38 KiB | 0 bytes/s, done.
Resolving deltas: 100% (809/809), completed with 130 local objects.
From https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index
* [new ref] -> origin/HEAD
Downloaded cfg-if v1.0.0 (registry `tuna`)
Downloaded rand_core v0.6.4 (registry `tuna`)
Downloaded rand_chacha v0.3.1 (registry `tuna`)
Downloaded byteorder v1.5.0 (registry `tuna`)
Downloaded quote v1.0.37 (registry `tuna`)
Downloaded unicode-ident v1.0.12 (registry `tuna`)
Downloaded zerocopy-derive v0.7.35 (registry `tuna`)
Downloaded proc-macro2 v1.0.86 (registry `tuna`)
Downloaded rand v0.8.5 (registry `tuna`)
Downloaded zerocopy v0.7.35 (registry `tuna`)
Downloaded syn v2.0.56 (registry `tuna`)
Downloaded libc v0.2.158 (registry `tuna`)
Downloaded 12 crates (1.5 MB) in 2m 57s
Compiling proc-macro2 v1.0.86
Compiling unicode-ident v1.0.12
Compiling libc v0.2.158
Compiling cfg-if v1.0.0
Compiling byteorder v1.5.0
Compiling quote v1.0.37
Compiling syn v2.0.56
Compiling getrandom v0.2.15
Compiling rand_core v0.6.4
Compiling zerocopy-derive v0.7.35
Compiling zerocopy v0.7.35
Compiling ppv-lite86 v0.2.20
Compiling rand_chacha v0.3.1
Compiling rand v0.8.5
Compiling guessing_game v0.1.0 (/home/bill/Study/rust/guessing_game)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 02s
如果你在国内,大概率会下载失败。如果遇到了这种情况,可参考下列链接配置 Crates.io 来解决问题:
笔者使用的 ~/.cargo/config
内容如下,读者可作为参考:
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'tuna'
#replace-with = 'ustc'
#replace-with = 'sjtu'
#replace-with = 'rsproxy'
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
[source.sjtu]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"
[source.rsproxy]
registry = "https://rsproxy.cn/crates.io-index"
[source.rustcc]
registry = "git://crates.rustcc.cn/crates.io-index"
[net]
git-fetch-with-cli=true
另外,还需要将 rustc 安装为更新版本,系统自带的版本可能导致无法下载第三方库
。可通过如下方式安装 Rust 最新版本:
$ export RUSTUP_UPDATE_ROOT=https://mirrors.aliyun.com/rustup/rustup
$ export RUSTUP_DIST_SERVER=https://mirrors.aliyun.com/rustup
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
好了,一切就绪,是时候修改代码引入目标数随机生成,以及用户输入数字比较判定功能了。修改后的代码如下:
use rand::Rng;
use std::io;
use std::cmp::Ordering;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=10);
println!("The secret number is at range: [1, 10]");
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
//let guess: u32 = guess.trim().parse().expect("Please type a number!");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num, // guess = num
Err(_) => continue, // _ to match all
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
//Ordering::Equal => println!("You win!"),
Ordering::Equal => {
println!("You win!");
break; // exit the loop
}
}
}
}
编译、运行:
$ cargo build
warning: `/home/bill/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/bill/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
Compiling guessing_game v0.1.0 (/home/bill/Study/rust/guessing_game)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s
bill@bill-virtual-machine:~/Study/rust/guessing_game$ cargo run
warning: `/home/bill/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/bill/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/guessing_game`
Guess the number!
The secret number is at range: [1, 10]
Please input your guess.
5
You guessed: 5
Too small!
Please input your guess.
8
You guessed: 8
You win!
相对第一版代码,修改后的代码又引入了几个新的知识点,总结如下:
- match 表达式
这类似于 C 语言的 switch / case 组合,不难理解。 - 同名变量隐藏(Shadowing)
代码里面两次定义了变量guess
,这有点类似Python
的同名变量,它们彼此可以有不同的类型,后一变量定义会隐藏前一定义。 - loop 循环
loop
关键字,组织了循环结构,类似于其它语言的while (1) { ... }
。