第2章 猜数字游戏 (Guessing Game)

2026-09-07 00:00    #读书笔记  

本章通过一个完整的猜数字游戏项目,一次性介绍 Rust 的核心工作机制:letmatchuse、外部 crate、类型转换、循环、错误处理等。不深入原理,重在直觉建立。

最终完整代码:

 1use rand::Rng;
 2use std::cmp::Ordering;
 3use std::io;
 4
 5fn main() {
 6    println!("Guess the number!");
 7
 8    let secret_number = rand::thread_rng().gen_range(1..101);
 9
10    loop {
11        println!("Please input your guess.");
12
13        let mut guess = String::new();
14
15        io::stdin()
16            .read_line(&mut guess)
17            .expect("Failed to read line");
18
19        let guess: u32 = match guess.trim().parse() {
20            Ok(num) => num,
21            Err(_) => continue,
22        };
23
24        println!("You guessed: {}", guess);
25
26        match guess.cmp(&secret_number) {
27            Ordering::Less => println!("Too small!"),
28            Ordering::Greater => println!("Too big!"),
29            Ordering::Equal => {
30                println!("You win!");
31                break;
32            }
33        }
34    }
35}

2.1 项目初始化

1cargo new guessing_game
2cd guessing_game

2.2 读取用户输入

use 引入标准库

1use std::io;

std::io 不在 Rust 的 prelude 中,需要显式 use。prelude 是 Rust 自动引入每个程序的一小部分标准库项。

可变变量

1let mut guess = String::new();

读入一行

1io::stdin()
2    .read_line(&mut guess)
3    .expect("Failed to read line");

Result 与 expect

Result 是枚举(enum),有两个变体(variant):

.expect(msg) — 如果是 Ok,取出内部值;如果是 Err,程序崩溃并打印 msg

不处理 Result 会编译警告

如果 Result 未被使用,Rust 编译器给出 unused Result that must be used 警告。Rust 强制你显式处理错误。

println! 占位符

1println!("You guessed: {}", guess);

{} 是占位符,按顺序替换为后面的参数。等价于 format!("You guessed: {}", guess)

2.3 生成随机数

添加外部 crate

编辑 Cargo.toml

1[dependencies]
2rand = "0.8.3"

0.8.3 是语义化版本的简写,等价于 ^0.8.3>=0.8.3, <0.9.0)。

cargo build 时 Cargo 会:

  1. 从 Crates.io 下载 rand 及其传递依赖
  2. 编译所有依赖
  3. 生成 Cargo.lock(锁定确切版本,保证可重现构建)

Cargo.lock

第一次 build 时产生,锁定实际解析的依赖版本。之后所有 build 复用这个版本,除非手动 cargo update 或修改 Cargo.toml 版本号。

生成随机数

1use rand::Rng;
2
3let secret_number = rand::thread_rng().gen_range(1..101);

2.4 比较数字

Ordering 枚举

1use std::cmp::Ordering;
2
3match guess.cmp(&secret_number) {
4    Ordering::Less    => println!("Too small!"),
5    Ordering::Greater => println!("Too big!"),
6    Ordering::Equal   => println!("You win!"),
7}

类型不匹配编译错误

第一次写出来会编译失败:

1error[E0308]: mismatched types
2expected reference `&String`
3   found reference `&{integer}`

原因:guessStringsecret_number 是整数(默认 i32)。Rust 不会隐式转换,必须手动做。

类型转换与变量遮蔽

1let guess: u32 = guess.trim().parse().expect("Please type a number!");

parse 返回 Result,此时用 expect 处理——输入非数字就崩溃。后面会改进。

2.5 循环

loop

1loop {
2    // ...
3}

loop 创建无限循环,需要手动 break 或 ctrl-c。

猜中后退出

1Ordering::Equal => {
2    println!("You win!");
3    break;  // 退出 loop
4},

break 退出当前(最内层)循环。猜中后程序结束。

用 match 代替 expect 处理无效输入

1let guess: u32 = match guess.trim().parse() {
2    Ok(num) => num,
3    Err(_) => continue,  // 忽略错误,回到 loop 顶部请求新输入
4};
match 穷举性

match 必须覆盖所有可能的分支。ResultOkErr 两个变体,两个都要处理才不会编译报错。

总结:本章引入的 Rust 概念

概念位置说明
useuse std::io将标准库/外部 crate 的项引入作用域
let mutlet mut guess变量默认不可变,mut 使其可变
::String::new()关联函数(类型的函数),:: 前缀
& / &mut&mut guess引用,&mut 是可变的引用(第4章详述)
Resultread_line 返回值Ok/Err 枚举,强制错误处理
expect.expect("msg")Ok 时取值,Err 时崩溃
{}println!("{}", x)println! 中字符串插值占位符
外部 craterand = "0.8.3"Cargo.toml[dependencies] 声明依赖
matchmatch guess.cmp(...)模式匹配,按分支执行,覆盖必须穷举
类型标注let guess: u32冒号后写类型,也帮助类型推断
shadowinglet guess = ...同名 let 遮蔽旧变量,用于类型转换
loop / break / continue循环控制无限循环和退出/跳过
_ 通配符Err(_)match 中匹配任意值
关键心态

这一章是快速概览,不是深入讲解。每章只引入了概念的表层,后面章节会逐步展开原理。如果某个概念(如引用 &、Result)让你困惑,很正常——继续学,第 4~10 章会一个个吃透。