Rust 走马观花(二)—— 猜数字游戏
本文是《Rust 程序设计语言 - 猜数字游戏》的学习笔记
会涉及到:条件判断, 循环, 获取输入内容, 异常捕获 等知识点
建议有一定编程基础的小伙伴阅读~
一、需求描述
猜数字游戏的规则如下:
1. 程序运行之后,会随机生成一个 1 ~ 100 之间的整数,这个数字是不可见的;
2. 需要玩家输入一个数字作为猜测;
3. 如果猜错,程序提示猜测数字偏大还是偏小,玩家需要重新输入数字;
4. 如果猜对,程序提示祝贺信息并退出。
先用 cargo 创建一个新项目
cargo new hello-rust
接下来删除 src/main.rs 中的已有内容,let's coding...
二、生成随机数
Rust 标准库中尚未包含随机数功能,需要引入 rand
cargo add rand
然后修改 main.rs 为:
use rand::{thread_rng, Rng};
fn main() {
// gen_range 这个方法,在不同版本的 rand 包中接收的参数不同
let secret_number = thread_rng().gen_range(1..=100);
// 打印秘密数字,调试用,开发完成后应当删除这行打印
println!("生成的秘密数字为:{secret_number}");
}
这里的第一行代码 use ::rand::{thread_rng, Rng} 指的是从 rand 这个代码包中导出 thread_rng 和 Rng
类似于 JS 中的 import { thread_rng, Rng } from 'rand'
这里的 Rng 是一个 trait,是一种将方法签名组合起来的方法,类似于 TypeScript 中的 interface
但 trait 只是作为签名,不能直接使用(ts 中的 interface 也不能作为值使用)
比如上面的代码中,Rng trait 下面有一个 thread_rng 方法,但 Rng.thread_rng() 这种写法会报错
需要改为 rand::thread_rng() 才能正确解析,而第一行代码已经从 rand 中解构出了 thread_rng,所以可以直接调用
然后 cargo run 运行一下代码,看看效果
三、输入猜测数字
在 main() 函数中追加一行代码
let mut guess = String::new();
这里创建了一个可变的字符串变量 guess,用来接收玩家的输入
对比上面创建随机数的代码 let secret_number = xxx,这里多了一个 mut 关键字
Rust 中创建的变量默认是不可变的,所以下面这样的代码会报错:
而加上 mut 关键字,这个变量就是一个可变变量,可以在后续的代码中重新赋值
接下来将 main.rs 改为如下内容:
use rand::Rng;
use std::io;
fn main() {
println!("猜数字游戏!");
println!("请输入你的猜测:");
let secret_number = rand::thread_rng().gen_range(1..=100);
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("读取输入内容失败");
println!("生成的秘密数字为:{secret_number}");
println!("你的猜测为:{guess}");
}
这里使用了标准库 std 里面的 io输入/输出库,用于获取用户输入
// Rust 会默认引入 std 中的预导入内容,其外的内容需要手动 use
这里 read_line 的作用就是读取一行用户输入
传给 read_line 的参数用到了&,这是指针的标记,类似于 JS 中的引用类型
如果不标记为指针,read_line 就不会修改 guess 的值,也就拿不到用户输入
另外,最后一行的 expect 是一个错误处理,会在第六小节介绍
四、比较数字
接下来追加这部分代码:
match guess.cmp(&secret_number) {
Ordering::Less => println!("你的猜测太小"),
Ordering::Greater => println!("你的猜测太大"),
Ordering::Equal => println!("你猜对了~"),
}
这里使用了 cmp 方法,用输入的猜测数字 guess 和目标数字 secret_number 作对比
cmd 的返回值是 Ordering 类型,需要从标准库引入:
use std::cmp::Ordering;
Ordering 是一个枚举,也就是说 cmp() 的结果会包含多个可能性
可以使用 match 语句来处理枚举类型的多种结果,类似于 JS 中的 switch...case
但上面的代码还存在问题,错误信息为“类型不匹配”:
这是因为 guess 是一个 String 类型,而 secret_number 是通过 gen_range 方法生成的整数,默认为 i32 类型
Rust 是静态强类型语言,不同类型的变量无法直接比较。这里需要将 guess 转为数字类型:
use rand::Rng;
use std::io;
use std::cmp::Ordering;
fn main() {
println!("猜数字游戏!");
println!("请输入你的猜测:");
let secret_number = rand::thread_rng().gen_range(1..=100);
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("读取输入内容错误");
let guess: u32 = guess.trim().parse().expect("请输入数字!");
println!("你的猜测为:{guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("你的猜测太小"),
Ordering::Greater => println!("你的猜测太大"),
Ordering::Equal => println!("你猜对了~"),
}
}
新增的代码为:
let guess: u32 = guess.trim().parse().expect("请输入数字!");
这里将 guess 转为数字类型 u32,但由于 guess 在声明的时候已经是 String 类型,所以需要使用 let 重新声明一个变量
虽然之前已经创建了一个 guess 变量,但 Rust 允许同名变量的覆盖(隐藏 shadowing)
这个功能在类型转换的场景很常用,它允许我们复用变量名,而不用创建两个不同变量
五、多次输入
现在整个程序的主流程已经完成,可以 run 一下看看效果
现在只能猜一次数字,我们还需要加一个循环,让用户能一直输入猜测,直到猜对
在 Rust 中可以通过 loop 创建一个无限循环
fn main() {
println!("猜数字游戏!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("请输入你的猜测:");
// ...
match guess.cmp(&secret_number) {
Ordering::Less => println!("你的猜测太小"),
Ordering::Greater => println!("你的猜测太大"),
Ordering::Equal => {
println!("你猜对了~");
break;
},
}
}
}
和其他语言的 for 语句类似,loop 也有 continue、break 关键字
continue 用于跳过当前循环,直接进入下一个循环
break 用于结束整个循环
六、错误处理
上面的代码有两个地方( parse、read_line )用到了 expect 方法,这是一个错误处理
比如 read_line 方法,它会返回一个 Result,这也是一个枚举,其成员为 Ok 和 Err
// 有点像 JS 中的 Promise,返回 then 和 catch
Result 实例拥有 expect 方法,这个方法会原样返回 Ok。而对于 Err,expect 会打印报错信息(函数入参),并退出程序
对于 read_line,报错的情况下直接退出程序,这是我们期望的结果
但对于 parse,报错之后应该让用户重新输入,就不合适用 expect 来处理错误
由于 Result 是一个枚举类型,可以用 match 来处理错误
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
最终完整的代码如下:
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("猜数字游戏!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("请输入你的猜测:");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("读取输入内容错误");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("你的猜测为:{guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("你的猜测太小"),
Ordering::Greater => println!("你的猜测太大"),
Ordering::Equal => {
println!("你猜对了~");
break;
}
}
}
}