菜鸡前端的Rust学习笔记(二)—一个猜数字游戏

761 阅读9分钟

二、一个简单的猜数字游戏

写在前面

我看原书里面把这一章放在安装之后,但是我觉得有点困难了,因为本章涉及到的知识点包括:

  1. 模块的引入与安装
  2. 标准输入输出的使用
  3. 变量定义
  4. 变量转换
  5. 模块中方法的使用
  6. trait、modules、Struct、Function概念
  7. match的用法、Result类型的用法
  8. 一个循环的语法

我的想法:对于一个rust的小白来说,其实我对基本的语法都不太清楚,看这一章确实只能看个热闹,但是作为一个小白这章的笔记还是先拿上来,很多都是自己根据自己的JS语法迁移过来的一些理解,可能并不正确,后期学完相应的知识后,应该也会做相应的调整。

2.1 需求分析

  • 这里其实想做的一件事是:
    • 随机产生一个数字
    • 让用户输入一个数字
    • 比对两个数字的结果
    • 根据对比结果输出相应的对比结果,一直循环上述步骤二步骤三直到用户输入和随机数相同
    • 猜中数字,结束循环

既然看起来很简单的需求,那么就来coding把

2.2 随机生成一个数

2.2.1 模块的安装

可以通过引入rand模块的方式,来实现随机数的创建,由于这里rand是个第三方模块,所以需要我们先安装。

安装步骤

  1. 在catgo.toml的packages依赖中,增加rand模块,版本为0.8.3
  2. 使用cargo update更新下镜像
  3. 使用cargo run的时候模块会被自动安装

换源步骤参考文档

  1. 在根目录下的.cargo创建config文件
  2. 根据上述网址中对应的内容复制过去就行
  3. 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..101..=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;
            }
        }
    }
}

2.7 效果

guess.gif