二、一个简单的猜数字游戏
写在前面
我看原书里面把这一章放在安装之后,但是我觉得有点困难了,因为本章涉及到的知识点包括:
- 模块的引入与安装
- 标准输入输出的使用
- 变量定义
- 变量转换
- 模块中方法的使用
- trait、modules、Struct、Function概念
- match的用法、Result类型的用法
- 一个循环的语法
我的想法:对于一个rust的小白来说,其实我对基本的语法都不太清楚,看这一章确实只能看个热闹,但是作为一个小白这章的笔记还是先拿上来,很多都是自己根据自己的JS语法迁移过来的一些理解,可能并不正确,后期学完相应的知识后,应该也会做相应的调整。
2.1 需求分析
- 这里其实想做的一件事是:
- 随机产生一个数字
- 让用户输入一个数字
- 比对两个数字的结果
- 根据对比结果输出相应的对比结果,一直循环上述
步骤二和步骤三直到用户输入和随机数相同 - 猜中数字,结束循环
既然看起来很简单的需求,那么就来coding把
2.2 随机生成一个数
2.2.1 模块的安装
可以通过引入rand模块的方式,来实现随机数的创建,由于这里rand是个第三方模块,所以需要我们先安装。
安装步骤:
- 在catgo.toml的packages依赖中,增加rand模块,版本为0.8.3
- 使用cargo update更新下镜像
- 使用cargo run的时候模块会被自动安装
换源步骤(参考文档)
- 在根目录下的.cargo创建config文件
- 根据上述网址中对应的内容复制过去就行
- cargo update一下
2.2.2 代码编写
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number_1 = rand::thread_rng().gen_range(1..10);
}
FAQ
Q1: 为什么use rand::Rng不导入的时候,这个gen_range就报错?
A1: 因为Rng这个==trait==定义了随机数方法的实现,所以在这个地方现有了实现的引用,然后这边调用gen_range才能调用
Q2: 1..10写法的意思?
A2:这里有两种写法1..10和1..=10,前者是[1, 10)这个区间,后者是[1, 10]这个区间
Q3:如何去看这些函数怎么调用?
A3: 可以通过cargo doc --open方法来打开帮助文档根据文档使用对应的包
Q4:如何调用该函数方法?
A4: 使用::的语法
这里,我们使用rand::tread_rng()生成了一个区间1~10间的数字
2.3 获取用户输入
2.3.1 代码编写
use rand::Rng;
// 使用第三方库std中的io模块
// 在js里面相当于 import { io } from 'std' 这个类比是我自己理解的不一定正确
use std::io;
fn main() {
// 省略
println!("Please input your guess");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Fail to read line");
}
FAQ
Q1: mut是什么意思?有mut和没有有什么区别?
A1: 在rust中所有变量默认是immutable的,所以你应该懂了 + 有mut定义:代表参数是mutable的 + 无mut定义:代表参数是immutable的
Q2: 实例如何创建?
A2: 通过类::方法的方式,表示调用了类的一个方法,这里调用的是一new的一个构造函数,new我理解是rust中一个通用的名称,用来创建某个类新的值
Q3:这里的&mut guess是什么意思?
A4: 在rust中,这里通过&来表明拿的数,是对应数在内存中的引用,可以保证在代码中多个地方拿同一个值,不需要拷贝多份,这是一种非常复杂的特性。
Q4: expect是用来干什么的
A3: 在rust中,很多方法返回的类型是Result,这个类型的意思是,如果成功就返回一个Ok的枚举值,如果失败就返回一个Error的枚举值,那么这里失败的时候,如果我们expect传入了一个字符串,失败的msg就会是我们传入的字符串,可用来定位错误信息
Q5:Result中的几种形式
A5:具体的用法有两种类型
-
类型一:支持通过except方法,当执行结果为成功的时候,直接跳过,错误的时候抛出expect中给出的错误信息(类似上面的写法)
-
类型二:通过Ok和Err两个变量,来对成功和失败分别做操作,但是目前实测这种用法,就是Err和Ok的写法要配合match关键词才能生效具体例子如下:
fn main { let mut guess = String::new(); match io::stdin().read_line(&mut guess) { Ok(n) => { println!("{} bytes read", n); println!("{}", guess); } Err(error) => println!("error: {}", error), }; }
我理解是,类似Promise一样,成功通过then的回调做一些操作,失败通过catch来对异常做一些操作,当然只是这个回调的概念,便于理解,其实两者是完全不能划等号做对比的~
2.4 变量类型转换
2.4.1 类型比较
目前,因为我们从命令行中获取的guess类型是String,但是我们实际通过rand生成的数字是Number类型,这两个类型不匹配是无法比较的,所以必须进行类型的转换,静态语言对于类型定义的严谨性
2.4.2 代码编写
fn main {
// 省略
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
// 这个continue是为了后续做循环的时候,直接跳过后续操作用的
// 先忽略
Err(_) => continue,
};
}
FAQ
Q1: 如何定义一个变量的类型?
A1: 通过类似TS中的:告诉rust,这个数字变量的类型,这里的u32代表的是一个32位的变量,类似一个数字变量的声明。
Q2: 为什么use rand::Rng不导入的时候,这个gen_range就报错?
A2: 因为Rng这个trait(特性)定义了随机数方法的实现,所以在这个地方现有了实现的引用,然后这边调用gen_range才能调用
【理解】参考文章(这个地方对于模块化的理解不一定对,后续会改)。另外,现在我看doc中有这么几个模块的概念:
-
modules:模块,这里就是我们用到的库的概念,比如rand、std等,这些本身在声明的时候是通过
mod来声明的,引入的如果是子模块,也可以通过use mod::sub_mod这样的方式来引入子模块 -
traits:是一个方法的集合(这个是我自己的理解),因为在Rust——理解trait (一)这里给出的例子中,其实是在实现上会更加灵活,即可以在特定的条件下表现出特定的特性,更加灵活
-
Functions:通过
somemod::method的方法来引用,相应模块下的方法
Q3: trim和parse的用法
A3: trim用于去除前后的空格和js中的trim用法一样,parse用于将string转换成对应的数字类型。parse这里返回的也是一个Result类型,这里看上去是不是特别像Promise.then和catch呢。(说的很小声,很心虚)
2.5 比较
2.5.1 match关键字
对比多个数值,类似switch...case的这种语法,在rust中和match有点类似,其作用是,在某种场景下可以返回某些值。后续应该会详细的介绍(至少文中是这么说的)
其具体语法是:
// 这里的xxx是一个操作返回的结果
match xxxx() {
A1(a1) => XXX,
A2(a2) => XXX,
A3(a3) => XXX,
}
2.5.2 代码编写
use std::cmp::Ordering;
fn main () {
// 省略上述代码
// 这里的&secret_number_1只是为了取他的引用值
match guess.cmp(&secret_number_1) {
// 对比结果是less的话
Ordering::Less => println!("Too Small!"),
// 对比结果是greater的话
Ordering::Greater => println!("Too Big!"),
// 相等怎么操作
Ordering::Equal => {
println!("You win!");
// 这里是做loop循环的时候,猜对了直接跳出循环用的
break;
}
}
}
FAQ
Q1: Ordering是啥?
A1:只是一个枚举值~但是也要引入才能用
2.6 完整代码
// 使用第三方库std中的io模块
// 在js里面相当于 import { io } from 'std' 这个类比是我自己理解的不一定正确
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
// 这里有个语法没弄明白
// 为什么use rand::Rng不导入的时候,这个gen_range就报错
// 不知道这个地方的导入关系是咋样
// 后期要再看看
// 文中写的是因为Rng是定义了随机数方法的实现
// 所以在这个地方现有了实现的引用,然后这边调用gen_range才能调用
// 这种写法指的是给与了一个range,[1, 10)
let secret_number_1 = rand::thread_rng().gen_range(1..10);
// 这种写法是闭区间[1, 10]
// let secret_number_2 = rand::thread_rng().gen_range(1..=10);
loop {
println!("Please input your guess");
// 变量定义
// 和js不同的是这里有mut
// 在rust中所有变量默认是immutable的,所以你应该懂了
// 有mut定义:代表参数是mutable的
// 无mut定义:代表参数是immutable的
// 通过类::方法的方式,表示调用了类的一个方法
// 这里调用的是一new的一个构造函数
// new我理解是rust中一个通用的名称,用来创建某个类新的值
let mut guess = String::new();
// 调用io模块中的stdin方法
// 这里也可以直接使用std::io::stdin
match io::stdin()
// 调用read_line方法
// &代表是引用类型,给引用赋值,代码中的多个部分接入同一个引用就是同一个值
// 因为之前定义的guess是mutable的所以这里要街上mut的修饰
// 这里一定要是mut的类型,因为read_line要求就是mutable的
.read_line(&mut guess) {
Ok(n) => {
println!("{} bytes read", n);
println!("{}", guess);
}
Err(error) => println!("error: {}", error),
};
// 在expect的实现中,成功执行就会跳过,执行失败会返回expect传入的error message
// expect用于判断上一个操作是否成功
// 这个expect是io::Result定义的
// .expect("Fail to read line");
// 这里的parse的返回值也是一个Result
// 也是可以通过expect来怕段是否满足了转换的预期
// 转换不成功就报错
// 通过类似ts的:来告诉rust,这个变量期待的类型
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
match guess.cmp(&secret_number_1) {
Ordering::Less => println!("Too Small!"),
Ordering::Greater => println!("Too Big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}