Loading

<<Rust程序设计语言>>个人版(1: 入门/2: 编写猜谜游戏)

安装

brew

brew install rust

其他(推荐)

brew 有时候会有问题

官网

校验是否安装成功

安装后在命令行输入 rustc --version 如果看到rust的版本号即可, 例如 rustc 1.42.0 (b8cedc004 2020-03-09)

打开内置的文档

命令行输入 rustup doc, 会调用默认浏览器打开本地的内置文档

VsCode配置插件

插件商城输入 rust, 排第一的就是, 安装后打开rust项目会提示下载依赖, 同意即可

Hello,world

编写一个输出 hello, world 的程序

编写与执行

在某个地方新建文件夹 hello_world , 在文件夹下新建文件 main.rs , 编写代码如下

fn main() {
    println!("hello, world");
}

保存后在当前目录下执行命令 rustc main.rs 会生成可执行文件 main, 执行这个文件, 打印 hello, world, 例如

➜  hello_world rustc main.rs 
➜  hello_world ./main 
hello, world
➜  hello_world 

解析

  • rust的代码写在 .rs 后缀的文件中
  • rust推荐使用下划线方法来命名, 比如 hello_world 而不是 helloWorld 或者 helloworld
  • rust的缩进是4个空格而不是一个tab

我们来解析代码

fn main() {
}

fn代表定义一个函数, 这里定义了一个 main 函数, main函数是特殊的函数, 每个可执行的rust程序都从main函数开始, main() 代表这个函数不需要任何参数, 如果有的话应该定义在 () 中.

() 后直接跟的 {} 代表函数本体, rust要求函数内部的代码都被这个大括号包裹, 而 main(){} 也代表了这个函数没有任何的返回值, 一般来说 { 与函数名在同一行, 并使用空格分开

rust的新版本自带了代码的格式化工具 rust fmt, 使用他可以将你的代码格式化的更加规范

在当前命令行中使用 rustfmt main.rs

查看格式化后的代码, 如果你的代码原来是

fn main() {println!("hello, world");}

格式化之后变成了

fn main() {
    println!("hello, world");
}

而其中的

    println!("hello, world");

则是函数的逻辑, 上面说到了缩进是四个空格

println! 是rust内置的宏(macro), 类似于内置的语法, 而引入内置的函数则是 println, 这个之后再说明

"hello, world"是传入这个宏的参数, 是一个字符串, 然后该字符串就会被输出到命令行中

rust中使用 ; 作为一行的结束, 代表这个表达式结束, 下一个表达式开始

编译

rust是一门静态语言, 他也是需要编译的, 还记得我们写完后在命令行输入 rustc main.rs 吗, 这就是在编译代码, 编译完成后会生成编译平台可执行的文件

hello, cargo

认识cargo

cargo 是rust的构建系统和依赖管理工具, 类似于golang的 go mod

在实际的编写项目中, 常常需要引入第三方的函数, 此时我们就需要 cargo 来帮助我们管理

cargo在安装 rust 时就已经安装到了你的电脑中

你可以在命令行中输入

cargo --version

来查看cargo的版本, 例如 cargo 1.42.0 (86334295e 2020-01-31)

使用cargo建立项目

正常情况下我们一般使用 cargo 来建立新项目, 而不是手动建立文件夹

➜  student ls
➜  student cargo new hello_cargo
     Created binary (application) `hello_cargo` package
➜  student ls
hello_cargo

可以看到, 在执行 cargo new hello_cargo 之后, 原本空的文件夹下出现了 hello_cargo 文件夹, 这与我们执行 new 时的输入是相同的

我们的项目就在这里

里面已经多了几个文件, 目录树如下

.
├── .git
│   ├── HEAD
│   ├── config
│   ├── description
│   ├── hooks
│   │   └── README.sample
│   ├── info
│   │   └── exclude
│   ├── objects
│   │   ├── info
│   │   └── pack
│   └── refs
│       ├── heads
│       └── tags
├── .gitignore
├── Cargo.toml
└── src
    └── main.rs

可以看到, cargo会在 hello-cargo 下初始化 git, 并且为我们生成了 .gitignore 忽略文件, 其中默认忽略了 /target 目录, 我们之后再介绍这个文件夹

同时生成了 cargo.toml 文件, toml格式是一种配置文件的格式, 他的内容大致如下

[package]
name = "hello_cargo"
version = "0.1.0"
authors = ["example <example@example.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

其中, package下为项目的信息, name为项目名, version为版本, authors为创建人(读取git的信息), edition这个项目创建时的大版本号, 目前是2018

里面的Value内容是可以修改的, 如果信息不准确你可以手动进行修改

我们真正的代码在 src/main.rs 中, 里面已经默认添加了 main 函数

fn main() {
    println!("Hello, world!");
}
  • rust希望你将所有的源代码放置在这个src目录下, 在根目录只放置 README.md/ 配置文件 等不涉及到代码的东西
  • 如果当前目录已经在 git 的控制下, 那么使用 cargo new 默认不会创建git相关的文件, 但是你可以使用参数 --vcs=git 来强制创建, 更多用法可查看 cargo new --help

执行cargo

cargo build

在命令行执行

➜  hello_cargo git:(master) ✗ cargo build
   Compiling hello_cargo v0.1.0 (/Users/Work/Code/Rust/student/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.76s

我们会发现在目录下生成文件夹 /target 在里面的 /debug 中则有可执行文件 hello_cargo

我们执行他

➜  hello_cargo git:(master) ✗ ./target/debug/hello_cargo
Hello, world!

所以, 我们 build 后会把生成的文件放置在 /target/debug 下, 当然 target 下还有很多文件, 我们之后再说

当某个项目第一次build, 还会在根目录生成 Cargo.lock 文件, 里面会存放一些具体的依赖, 一般不需要我们修改

cargo run

我们也可以使用 cargo run 来直接运行 build 出的程序, 与上面的方式没有区别

➜  hello_cargo git:(master) ✗ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/hello_cargo`
Hello, world!

更强大的是, 如果你修改了源代码, 此时你无需执行 build, 直接执行 run 可自行 build然后执行

比如我们将 main.rs 修改为

fn main() {
    println!("Hello, world!");
    println!("Hello, world!");
}

保存后直接执行 cargo run

➜  hello_cargo git:(master) ✗ cargo run
   Compiling hello_cargo v0.1.0 (/Users/Work/Code/Rust/student/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/hello_cargo`
Hello, world!
Hello, world!

如果你事先没有 build, 也就是没有 target 文件夹, 执行 cargo run也会自己build

cargo check

如果你只想确认你编写的代码是否可以运行而不想build出文件, 可以执行 cargo check, 可以在不生成可执行文件的前提下检查你的代码是否可以编译

➜  hello_cargo git:(master) ✗ cargo check
    Checking hello_cargo v0.1.0 (/Users/chenming/Work/Code/Rust/student/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.13s

cargo release

当你的项目需要发布或者迁出稳定版的时候, 使用 cargo build --release , 与普通的build不同的是, 该操作会优化生成的可执行文件, 提高执行速度等等, 当然, 因为优化原因, release 的耗时会比普通的 build 慢一些, 编译完成后会放置在 /target/release 文件夹下, 建议每次发布时都采用此方式编译

代码格式化

之前提到对某一个 rs 文件格式化代码使用 rustfmt xx.rs

如果对整个项目都格式化, 使用 cargo fmt 即可

如果你只想查看哪些是需要格式化的而不是直接修改好文件, 使用 cargo fmt -- --check 即可

最好对每个项目都是用targo来管理, 这样对项目来讲是最好的

设计猜谜游戏程序

这个程序的流程是, 用户输入一个数字, 然后程序自己随机出一个数字, 将这两个数字进行对比

建立新项目

使用 cargo 建立新项目并进入目录

➜  student cargo new guessing_game
     Created binary (application) `guessing_game` package
➜  student ls
guessing_game
➜  student cd guessing_game 

此时在 /src/main.rs 中已经有一个 main 函数了, 我们修改 main 函数

处理用户输入的数字

我们将代码修改成下面的

use std::io;  // 引入标准库

fn main() {
    println!("输入你的猜测数字: ");  // 打印
    let mut guess = String::new();  // 创建一个字符串变量guess
    io::stdin()  // 调用stdin
        .read_line(&mut guess)  // 调用stdin的方法read_line获取输入值
        .expect("读取失败");  // 如果获取错误打印警告
    println!("你猜测的数字是: {}", guess)  // 输出获取到的输入
}

值得注意的是, rust的注释是 //, 他不会编译//后的内容直到本行的结尾

这段代码的目的是读取用户输入的数字并输出

在第一行, 我们使用 use 语法来引入标准库 std::io, 类似于Python/Go的import

我们首先在输入框中输出提示 输入你的猜测数字:

let

在第5行, 我们使用 let 来创建一个 变量, 但是与其他语言不同的是, 如果你只是使用 let, 那么你创建的变量的值是不可变的, 例如以下代码

    let a = "1212";
    a = "1111" ;

我们使用 cargo check 来检查会爆出错误

error[E0384]: cannot assign twice to immutable variable `a`
 --> src/main.rs:5:5
  |
4 |     let a = "1212";
  |         -
  |         |
  |         first assignment to `a`
  |         help: make this binding mutable: `mut a`
5 |     a = "1111" ;
  |     ^^^^^^^^^^ cannot assign twice to immutable variable

error: aborting due to previous error

告诉我们不能对不可变的变量进行二次赋值

所以, 如果这个变量在以后需要重新赋值, 我们需要使用 let mut

    let mut a = "1212";
    a = "1111" ;

对于新建变量来说, 还是要根据用途来进行设计

    let mut a = "1212";  // 可变变量
    let b = "dddd"  // 不可变变量

回到代码中, 我们看到, 我们新建了一个可变变量 guess, 他的值是 String::new() , 代表着使用了 Stringnew 方法, 生成了一个 String 实例, String 代表着 rust 的字符串类型, 那么, String::new()代表生成了一个字符串类型的初始值, 也就是空字符串

::new 代表调用 new 方法, new对于String 来说是关联函数(associated function) , 通常我们认为newString静态方法, 一些面向对象的语言通常都有这个概念, 指的是类自有的方法, 而不需要对象去调用

在 rust 中, 新建一个新的类型的初始值通常使用 ::new

    io::stdin()  // 调用stdin
        .read_line(&mut guess)  // 调用stdin的方法read_line获取输入值
        .expect("读取失败");  // 如果获取错误打印警告

这三行代码其实是一条语句, 我们之前提到, ; 代表一段代码的结束, 所以rust会将这三行合并成一条语句来解析, 所以我们也可以将这三行缩短为, 这样会减少代码的可读性

    io::stdin().read_line(&mut guess).expect("读取失败");

这段代码的意思是调用 io 模块的静态方法 stdin, 此方法回传一个实例 stdin, 匹配标准输入控制

然后继续调用实例的 read_line 方法, 此方法对标准输入代码执行 read_line 命令, 来获取使用者的输入信息, 我们还将生成的可变变量 guess 传递进去, 代表将用户输入的结果传递给 guess

& 代表这个参数是引用(reference) , 这让代码中的多个部分共享一个值, 不需要每次都复制值到一个新的地址, 引用是个复杂的概念, rust 可以让你安全又轻松的使用引用, 这个之后再讲, 你现在需要知道的是, 引用默认也是不可变的, 因此在后面加上 mut 代表这个是可变引用, 因此你必须写 &mut guess 而不是 &guess

使用Result类型处理可能出现的错误

.expect("读取失败"); 这个代码处理了 read_line 方法可能出现的错误, 因为 read_line 返回了 io::Result 对象, 在 rust 中有很多库都有 Result类, Result实际上是种枚举(enumerations/enums), 枚举是拥有固定的值的类型, 这些值被称为枚举的变体(variants), 对于Result 来说, 变体有 okError, ok代表成功, error 代表失败, 返回 Result 的目的是让使用者在错误发生时去处理这个问题, io::Result 自带了方法 expect, 当Result返回 时便会触发 expect, 如果是 ok 则代表没有出现问题, expect 不会对数据进行任何操作

当我们不对 expect 进行捕捉, 我们在编译时就会出现如下警告

use std::io; // 引入标准库

fn main() {
    println!("输入你的猜测数字: "); // 打印
    let mut guess = String::new(); // 创建一个字符串变量guess
    io::stdin() // 调用函数stdin
        .read_line(&mut guess); // 调用stdin的方法read_line获取输入值
        // .expect("读取失败"); // 如果获取错误打印警告
    println!("你猜测的数字是: {}", guess) // 输出获取到的输入
}

➜  guessing_game git:(master) ✗ cargo build
  Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
warning: unused `std::result::Result` that must be used
--> src/main.rs:6:5
 |
6 | /     io::stdin() // 调用函数stdin
7 | |         .read_line(&mut guess); // 调用stdin的方法read_line获取输入值
 | |_______________________________^
 |
 = note: `#[warn(unused_must_use)]` on by default
 = note: this `Result` may be an `Err` variant, which should be handled

   Finished dev [unoptimized + debuginfo] target(s) in 0.62s

提示你没有处理可能出现的错误, 这里我们先添加 expect 保证其编译时不警告, 至于怎么去使用 expect 我们之后再说

println!打印输入的数字

println!("你猜测的数字是: {}", guess) // 输出获取到的输入

这里是将用户输入的数字打印出来, {}代表占位符, 将 guess 的值填充到 {} 中, {} 可以多个, 例如

let x = 5;
let y = 10;

println!("x = {} 而且 y = {}", x, y);

输出

x = 5 而且 y = 10

运行第一个部分

显然的, 程序还没有编写完成, 目前只能获取到用户的输入并打印出来

➜  guessing_game git:(master) ✗ cargo run
   Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.60s
     Running `target/debug/guessing_game`
输入你的猜测数字: 
3
你猜测的数字是: 3

生成随机的秘密数字

既然是猜谜游戏, 那么系统需要生成一个随机的数字来进行比对决定对错

使用Crate来获取更多功能

crate.io 是rust官方的仓库, 里面是rust各种各样的包(类似Python的pypi), 我们需要使用其中的一个包 rand 来帮助我们生成随机数字

如何将rand添加进我们的项目呢? 我们找到项目根目录下的Cargo.toml 文件, 并编辑

[package]
name = "guessing_game"
version = "0.1.0"
authors = ["xx <xx@Outlook.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.5"

我们在 [dependencies] 下新增包 rand 设置其版本为 0.5 ,这代表限制rand的版本在0.5`中, 避免出现兼容问题

然后我们使用 cargo build 能看到cargo自动下载了我们指定的包, 如果我们指定的包需要依赖其他的包, cargo也会自动帮我们下载(比如下面的 rand_core)

➜  guessing_game git:(master) ✗ cargo build
    Updating crates.io index
  Downloaded rand v0.5.6
  Downloaded rand_core v0.3.1
  ......
    Finished dev [unoptimized + debuginfo] target(s) in 11.13s

crate.io受网络影响, 在国内可能会出现通信问题, 可以使用第三方源比如USTC

如果你出现Blocking waiting for file lock on package cache错误, 尝试删除 ~/.cargo/.package-cache 文件

通过Cargo.lock确保项目依赖的正确性

当我们通过 cargo 安装新的依赖后, 我们会发现 Cargo.lock 文件也发生了变化, 增加了我们添加的依赖的具体的信息, 比如

[[package]]
name = "rand"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
dependencies = [
 "cloudabi",
 "fuchsia-cprng",
 "libc",
 "rand_core 0.3.1",
 "winapi",
]

cargo通过该文件来保证依赖的完整性和准确性, 当我们联合开发时, 只要将 Cargo.lock Cargo.toml 上传至项目中, 通过 cargo build 即可下载同样的依赖

升级Crate依赖

如果依赖有小版本的升级, 这通常是修复BUG和提高性能, 使用 cargo update 可以更新依赖, cargo会根据 cargo.toml 的语义化版本升级兼容该版本的小版本更新, 然后更新 cargo.tomlcargo.lock 中的内容

例如 你指定了rand 版本为 rand=0.8 则如果当前的版本是 0.8.3 新版本是 0.8.5会自动更新

如果新版本为 0.9.1 则与你指定的版本不同, 则不更新

如果你想更新到 0.9 版本, 需手动的编辑 toml 文件然后执行 update, 但是因为大版本的更新可能伴随着功能的修改, 必须要在手动升级前确认新版本的兼容性

生成随机数字

修改代码为

use std::io; // 引入标准库
use rand::Rng;

fn main() {
    println!("输入你的猜测数字: "); // 打印
    let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
    println!("生成的数字是: {}", secret_number);  // 打印生成的数字
    let mut guess = String::new(); // 创建一个字符串变量guess
    io::stdin() // 调用函数stdin
        .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
        .expect("读取失败"); // 如果获取错误打印警告
    println!("你猜测的数字是: {}", guess) // 输出获取到的输入
}

注意到我们新增了两行代码

    use rand::Rng;
    ....
		let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
    println!("生成的数字是: {}", secret_number);  // 打印生成的数字

在最开始, 我们引入了新的模块, rand::Rng, 需要注意的是, 我们在下面使用 rand 的时候并没有使用 Rng, 实际上这个 Rng 被称作 特征(trait)

它定义了数字生成器的工作方法, 必须将其引入才可以正常生成数字, 关于特征的更多情况, 我们之后再做讲解

重要的是生成随机数字这一段, 我们使用 rand包的thread_rng方法, 这会返回一个随机数字的生成器, 以当前的工作进程做为种子, 然后我们调用这个生成器的 gen_range 方法, 这个方法是由我们引入的 Rng 定义的, 他接受两个参数, 最大值和最小值, 我们传入 1, 101 代表生成 1-100的数字, 他是顾头不顾尾的, 所以我们要填到101

我们将其打印出来运行, 目的是测试功能是否正常, 事实上程序并未完成

➜  guessing_game git:(master) ✗ cargo run       
   Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.51s
     Running `target/debug/guessing_game`
输入你的猜测数字: 
生成的数字是: 6
1
你猜测的数字是: 1

将猜测的数字与生成的数字进行比较

在上面, 我们生成了数字并获取到了用户输入的数字, 这一步我们将这两个数字进行比较

use std::io; // 引入标准库
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    println!("输入你的猜测数字: "); // 打印
    let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
    println!("生成的数字是: {}", secret_number);  // 打印生成的数字
    let mut guess = String::new(); // 创建一个字符串变量guess
    io::stdin() // 调用函数stdin
        .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
        .expect("读取失败"); // 如果获取错误打印警告
    println!("你猜测的数字是: {}", guess); // 输出获取到的输入
    match guess.cmp(&secret_number){
        Ordering::Less => println!("太小了"),
        Ordering::Greater => println!("太大了"),
        Ordering::Equal => println!("正确"),
    }
}

我们先引入了 std::cmp::Ordering , Ordering是一个枚举, 其变体为 Less, Greater, Equal. 当你对两个数字进行比较时会有这三种结果

cmp方法会对数字进行比较, 我们传入引用 secret_number , 目的是将 guess 与 secret_number 进行比较, 他会回传我们引入的 Ordering 变体

match

有时候, 我们需要对某个枚举的变体进行分别处理, 比如这里, 我们根据数字比较结果来提示用户数字的范围

所以我们使用 match 语法

match 由分支(arms)组成, 每个分支包含一个模式(pattern)和他的处理逻辑, 当匹配到这个模式, 就会执行对应的逻辑. 在执行完毕后则退出 math 不再进行下面的匹配, match的具体细节会在之后讲解

但是我们运行时会发现他会报错

➜  guessing_game git:(master) ✗ cargo run
   Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
error[E0308]: mismatched types
  --> src/main.rs:15:21
   |
15 |     match guess.cmp(&secret_number){
   |                     ^^^^^^^^^^^^^^ expected struct `std::string::String`, found integer
   |
   = note: expected reference `&std::string::String`
              found reference `&{integer}`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `guessing_game`.

To learn more, run the command again with --verbose.

这是因为, guess的类型是字符串, 而secret_number是数值,这就会导致 型別無法配對(mismatched types), rust是静态类型的语言, 这两个是无法进行直接比较的

关于数值, rust有多个数值类型, 分别为 i3232位元数字, u32无符号32位元数字, i6464位元数字, u6464位无符号元数字等等, 默认的类型为 i32, 因此 secret_number 为 i32

同时我们也面临一个问题, 就是用户输入的不是数字怎么办

此时我们需要将guess转换为数字, 让他们可以比较, 将代码修改为

use std::io; // 引入标准库
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    println!("输入你的猜测数字: "); // 打印
    let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
    println!("生成的数字是: {}", secret_number);  // 打印生成的数字
    let mut guess = String::new(); // 创建一个字符串变量guess
    io::stdin() // 调用函数stdin
        .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
        .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 为 string 类型, 这里是将他重新定义为一个u32类型的数值, 这在rust中被称为遮蔽(shadow), 我们遮蔽了原来的guess, 遮蔽让我们可以重复的使用一个变量名, guess.trim()可以去除这个字符串前后的空白和换行, 而.parse()则可以将字符串转换为数值类型, 同时我们捕捉expect, 如果转换失败, 则告诉用户出现错误了, 我们来执行代码

➜  guessing_game git:(master) ✗ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/guessing_game`
输入你的猜测数字: 
生成的数字是: 54
  1
你猜测的数字是: 1
太小了
➜  guessing_game git:(master) ✗ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/guessing_game`
输入你的猜测数字: 
生成的数字是: 9
ffff111
thread 'main' panicked at '请输入正确的数字: ParseIntError { kind: InvalidDigit }', src/main.rs:13:22
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

使用户循环输入

目前的程序, 用户输入一次失败后就结束了程序, 为了提升体验, 我们需要让用户可以循环输入

loop

loop语法可以生成一个死循环, 我们将代码修改为

use std::io; // 引入标准库
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
    println!("生成的数字是: {}", secret_number);  // 打印生成的数字
    loop{
        println!("输入你的猜测数字: "); // 打印
        let mut guess = String::new(); // 创建一个字符串变量guess
        io::stdin() // 调用函数stdin
            .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
            .expect("读取失败"); // 如果获取错误打印警告
        let guess: u32 = guess.trim().parse().expect("请输入正确的数字");
        println!("你猜测的数字是: {}", guess); // 输出获取到的输入
        match guess.cmp(&secret_number){
            Ordering::Less => println!("太小了"),
            Ordering::Greater => println!("太大了"),
            Ordering::Equal => println!("正确"),
        }
    }
}

loop是一个死循环, 因此我们不停的输入数字去比较, 退出的方法只有强制关闭程序比如 ctrl+c, 或者输入一个无法解析为数值的字符串让他报错

➜  guessing_game git:(master) ✗ cargo run
   Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.40s
     Running `target/debug/guessing_game`
生成的数字是: 79
输入你的猜测数字: 
11
你猜测的数字是: 11
太小了
输入你的猜测数字: 
ff
thread 'main' panicked at '请输入正确的数字: ParseIntError { kind: InvalidDigit }', src/main.rs:14:26
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

break

我们需要在用户猜中之后让他结束这个游戏, 使用 break 语法可以退出循环

use std::io; // 引入标准库
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
    println!("生成的数字是: {}", secret_number);  // 打印生成的数字
    loop{
        println!("输入你的猜测数字: "); // 打印
        let mut guess = String::new(); // 创建一个字符串变量guess
        io::stdin() // 调用函数stdin
            .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
            .expect("读取失败"); // 如果获取错误打印警告
        let guess: u32 = guess.trim().parse().expect("请输入正确的数字");
        println!("你猜测的数字是: {}", guess); // 输出获取到的输入
        match guess.cmp(&secret_number){
            Ordering::Less => println!("太小了"),
            Ordering::Greater => println!("太大了"),
            Ordering::Equal => {
              println!("你猜中了, 游戏结束");
              break;  
            },
        }
    }
}

当猜中时, 使用break退出循环, 因为下面没有代码了,就直接退出

➜  guessing_game git:(master) ✗ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/guessing_game`
生成的数字是: 25
输入你的猜测数字: 
11
你猜测的数字是: 11
太小了
输入你的猜测数字: 
25
你猜测的数字是: 25
你猜中了, 游戏结束

对错误进行处理

现在, 用户输入一个无法转成数值的字符串会导致程序结束, 我们现在需要处理这个错误, 让他在错误时重新输入, 不让代码结束, 我们应该对每个错误都捕捉处理, 避免程序关闭, 这对每个程序都是一样的

我们将expect的处理变成一个match, 当分支检测到Error时进行特殊的处理

use std::io; // 引入标准库
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
    println!("生成的数字是: {}", secret_number);  // 打印生成的数字
    loop{
        println!("输入你的猜测数字: "); // 打印
        let mut guess = String::new(); // 创建一个字符串变量guess
        io::stdin() // 调用函数stdin
            .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
            .expect("读取失败"); // 如果获取错误打印警告
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("输入了一个无法解析的字符串");
                continue;
            },
        };
        println!("你猜测的数字是: {}", guess); // 输出获取到的输入
        match guess.cmp(&secret_number){
            Ordering::Less => println!("太小了"),
            Ordering::Greater => println!("太大了"),
            Ordering::Equal => {
              println!("你猜中了, 游戏结束");
              break;  
            },
        }
    }
}

我们在上面说过, parse返回一个枚举, 那我们就使用match判断这个枚举的分支, 他会有两种情况, OKErr, 如果是OK则代表正确, 我们将回传的num原样返回给guess, 如果Err 代表失败, 在失败时我们就不关心num是什么了, 所以我们使用_ 接受, 然后打印告诉用户出现了错误, 使用break关键字跳出当前的循环

➜  guessing_game git:(master) ✗ cargo run
   Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.44s
     Running `target/debug/guessing_game`
生成的数字是: 82
输入你的猜测数字: 
11
你猜测的数字是: 11
太小了
输入你的猜测数字: 
ff
输入了一个无法解析的字符串
输入你的猜测数字: 
82
你猜测的数字是: 82
你猜中了, 游戏结束

如果是真实的程序, 应该将程序生成的数值打印去除, 这里保留只是为了方便调试

posted @ 2021-03-25 16:22  ChnMig  阅读(213)  评论(0编辑  收藏  举报