当前位置: 首页 > news >正文

趣味学习Rust基础篇(用Rust做一个猜数字游戏)

这篇文章不仅带你一步步完成游戏,更会清晰地解释 Rust 的核心概念,让你在实践中真正理解这门语言。

各位亲爱的小伙伴们,想学一门强大的编程语言吗?今天,我们就用 Rust 来做一个超经典的“猜数字”游戏。一边玩,一边就把 Rust 最重要的几个“法宝”给学明白了!准备好了吗?Let’s go!

游戏规则很简单

  1. 电脑心里想一个 1 到 100 之间的秘密数字(每次都不一样!)。
  2. 你来猜这个数字。
  3. 电脑告诉你:你猜大了、猜小了,还是猜对了!
  4. 猜对了,游戏结束,你赢了!

我们的目标就是用 Rust 代码实现这个游戏。

第一步:创建你的第一个 Rust 项目

打开你的终端(命令行工具),输入这两条命令:

cargo new guess_game
cd guess_game
  • cargo new guess_game:Cargo(Rust 的“包管理器和项目构建工具”)会为你创建一个叫 guess_game 的新项目。
  • cd guess_game:进入这个新项目文件夹。

现在,你的项目里已经有了一个“Hello, World!”程序。让我们先运行一下,看看 Rust 环境是不是正常:

cargo run

你应该能看到:

Hello, world!

太棒了,环境没问题!现在,我们要把 src/main.rs 这个文件里的代码,从"Hello,world!"改成我们的猜数字游戏。

第二步:让程序和你“对话”(输入与输出)

打开 src/main.rs 文件,把里面的内容全删掉,换成下面这些:

use std::io; // 引入“输入输出”工具箱fn main() {println!("猜数字游戏开始啦!🎉");println!("请输入一个 1 到 100 之间的数字:");let mut guess = String::new(); // 创建一个叫 guess 的“空盒子”,用来装你输入的数字(字符串形式)io::stdin() // 获取键盘输入.read_line(&mut guess) // 把你输入的内容读进来,放进 guess 这个盒子里.expect("哎呀,读取输入失败了!"); // 如果读取失败(比如键盘坏了?),程序就崩溃并显示这个错误信息println!("你猜的是:{}", guess); // 把你猜的数字打印出来
}

我们来拆解一下这段代码里的 Rust 核心思想

  1. use std::io;:导入工具箱

    • std::io 是 Rust 标准库里的“输入输出”工具箱。use 就是把这工具箱“拿”过来,后面才能用它。
  2. fn main() { ... }:程序的“入口”

    • 这是每个 Rust 程序都必须有的“主函数”。程序一运行,就从这里开始执行。
  3. println!:打印信息

    • 这个感叹号 ! 很关键!它表示 println! 不是一个普通函数,而是一个(macro)。宏就像“代码生成器”,能帮你做更复杂的事情。在这里,它就是把括号里的内容打印到屏幕上,关于宏的介绍我们在后面的章节再详细介绍
  4. let mut guess = String::new();:创建变量(可变的)

    • let:意思是“创建一个新变量”。
    • mut:这是 Rust 的核心特性之一——默认不可变!在 Rust 里,你创建的变量默认是“锁死”的,不能改。如果你想让它能变(比如,要装用户输入的新值),就必须加上 mut(mutable 的缩写)。
    • guess:这是变量的名字,就像给盒子贴的标签。
    • String::new()String 是 Rust 里表示“文本”(字符串)的类型。::new() 是一个关联函数,意思是“创建一个新的 String 实例”。所以这行代码就是:创建一个叫 guess 的、可变的、空的“文本盒子”
  5. io::stdin().read_line(&mut guess).expect(...);:读取用户输入

    • io::stdin():获取键盘输入的“句柄”(你可以想象成一个连接键盘的“管道”)。
    • .read_line(&mut guess):调用这个“管道”上的 read_line 方法,把用户输入的整行内容读进来。&mut guess 里的 & 表示引用(reference)。这很关键!它不是把 guess 盒子里的东西复制一份,而是直接告诉 read_line:“嘿,就用我这个 guess 盒子,把内容塞进去就行!” mut 表示这个引用是可变的,read_line 有权修改盒子里的内容。
    • .expect("...")read_line 返回一个 Result 类型。这是 Rust 核心错误处理机制Result 有两种状态:Ok(成功)Err(失败).expect() 的意思是:“我坚信这一步不会失败。如果失败了(Err),程序就立刻停止,并打印我括号里的错误信息”。这是一种“快速失败”策略,方便调试。如果不用 .expect() 或其他方式处理 Result,Rust 编译器会警告你,因为它强制要求你处理可能的错误!
  6. println!("你猜的是:{}", guess);:格式化输出

    • {} 是一个“占位符”,像一个小钩子,等着钩住后面的值。guess 这个变量的值就会被放到 {} 的位置打印出来。

现在,运行 cargo run,试试看!输入一个数字,看看程序是不是能正确读取并打印出来。

第三步:生成秘密数字(引入外部“库”)

我们的游戏还缺最关键的部分:那个神秘的数字!Rust 标准库没有生成随机数的功能,但别担心,Rust 社区有现成的“轮子”可以用!

  1. 添加依赖:打开项目根目录下的 Cargo.toml 文件。在 [dependencies] 下面添加一行:

    [dependencies]
    rand = "0.8.5"
    

    这行代码告诉 Cargo:“我的项目需要一个叫 rand 的外部库,版本号是 0.8.5”。

  2. 写代码:回到 src/main.rs,在文件开头添加:

    use rand::Rng; // 引入 rand 库里的 Rng 特性
    

    然后,在 main 函数里,println! 语句之后,创建 guess 变量之前,加上:

    let secret_number = rand::thread_rng().gen_range(1..=100);
    

    这行代码做了什么?

    • rand::thread_rng():获取一个随机数生成器。
    • .gen_range(1..=100):调用它的 gen_range 方法,生成一个从 1 到 100(包含 100)之间的随机整数。
    • let secret_number = ...:把这个随机数存进一个叫 secret_number 的变量里。

注意secret_number 没有 mut,因为我们生成一次后就不再改它了!这体现了 Rust 的默认不可变性,让代码更安全。

第四步:比较大小(Rust 的“模式匹配”)

现在我们有用户的猜测 guess 和秘密数字 secret_number,怎么比大小呢?用 match

// 把原来的 println!("你猜的是:{}", guess); 这行暂时注释掉或删掉
// 然后加上:match guess.cmp(&secret_number) {std::cmp::Ordering::Less => println!("太小了!"),std::cmp::Ordering::Greater => println!("太大了!"),std::cmp::Ordering::Equal => println!("恭喜你,猜对了!"),
}

核心概念:match 模式匹配

  • guess.cmp(&secret_number):调用 guesscmp(compare)方法,和 secret_number 比较。它会返回一个 std::cmp::Ordering 枚举。
  • match:这是 Rust 的“瑞士军刀”!它会检查 cmp 返回的值,然后和下面的“分支”(=> 左边的部分)进行精确匹配
  • std::cmp::Ordering::Less:如果比较结果是“小于”,就执行 => 右边的代码,打印“太小了!”。
  • GreaterEqual 同理。

match 要求你必须处理所有可能的情况!这确保了你的代码不会遗漏任何分支,非常安全。

第五步:循环猜,直到猜对(循环)

现在程序一运行,猜一次就结束了。我们得让它能一直猜!

loop 关键字创建一个无限循环:

// 把从 "请输入..." 到 "match..." 之间的所有代码,都用 loop { ... } 包起来!loop {println!("请输入一个 1 到 100 之间的数字:");let mut guess = String::new();io::stdin().read_line(&mut guess).expect("哎呀,读取输入失败了!");// ... (处理输入的代码,见下一步)match guess.cmp(&secret_number) {std::cmp::Ordering::Less => println!("太小了!"),std::cmp::Ordering::Greater => println!("太大了!"),std::cmp::Ordering::Equal => {println!("恭喜你,猜对了!");break; // 当猜对时,执行 break,跳出循环,游戏结束!}}
}

loop 就是“一直循环下去”。break 是“跳出循环”的指令。

第六步:处理“捣乱”的输入(更健壮的错误处理)

如果用户不输入数字,而是输入“abc”或者“你好”,程序会崩溃!因为 guess 是个 String,我们得把它转换成数字(比如 u32)才能比较。

原来的代码是:

// 这行代码会崩溃,如果输入不是数字!
let guess: u32 = guess.trim().parse().expect("请输入一个数字!");

我们用更优雅的 match 来处理:

// 在 read_line 之后,match 之前,添加:let guess: u32 = match guess.trim().parse() {Ok(num) => num,        // 如果转换成功(Ok),就把里面的数字(num)拿出来Err(_) => continue,    // 如果转换失败(Err),就忽略这次输入,回到循环开头,让用户再猜一次
};
  • guess.trim()trim() 方法去掉字符串前后的空格和换行符。
  • .parse():尝试把字符串解析成一个数字。它返回 Result<u32, ParseIntError>
  • match:再次使用模式匹配!
    • Ok(num):匹配成功的情况,num 是解析出来的数字。
    • Err(_):匹配所有错误情况(_ 是通配符,表示“任何错误我都接受”),然后执行 continue,跳过本次循环剩下的代码,直接开始下一次循环。

这就是 Rust 的哲学:用强大的类型系统(Result)和模式匹配(match)来强制你处理错误,而不是让程序默默崩溃或产生难以预料的行为。

大功告成!完整代码

use std::io;
use rand::Rng;
use std::cmp::Ordering;fn main() {println!("猜数字游戏开始啦!");let secret_number = rand::thread_rng().gen_range(1..=100);loop {println!("请输入一个 1 到 100 之间的数字:");let mut guess = String::new();io::stdin().read_line(&mut guess).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;}}}
}

通过这个游戏,你学到了 Rust 的哪些核心?

  1. 默认不可变性 (mut):变量默认不能改,想改必须加 mut。这大大减少了因意外修改数据导致的 bug。
  2. 所有权与引用 (&)&mut guess 展示了“借用”(borrowing)。你可以把数据的“引用”借给别人用,而不必转移所有权(复制数据),这既高效又安全。
  3. 错误处理 (Result & match):Rust 不用“异常”,而是用 Result 类型来表示操作可能成功或失败。你必须用 match.expect() 等方式明确处理错误,这让你的程序更健壮。
  4. 模式匹配 (match)match 是 Rust 的核心控制流,它能穷尽所有可能性,让你的代码逻辑清晰且无遗漏。
  5. 包管理 (Cargo)Cargo.toml 定义依赖,cargo build/run 管理项目。简单高效。
  6. 类型系统String, u32, Result, Ordering 都是强大的类型。编译器在编译时就能帮你发现很多错误。
  7. 宏 (println!):以 ! 结尾的都是宏,它们能生成代码,实现更复杂的功能。

恭喜你!你不仅做了一个游戏,还亲手触摸到了 Rust 这门现代系统编程语言的精髓:安全、并发、高性能。继续加油!

http://www.dtcms.com/a/353340.html

相关文章:

  • PAT 1087 All Roads Lead to Rome
  • 嵌入式学习资料分享
  • java中的数据类型
  • 《FastAPI零基础入门与进阶实战》第14篇:ORM之第一个案例改善-用户查询
  • 【图文介绍】PCIe 6.0 Retimer板来了!
  • 快速上手对接币安加密货币API
  • 《Linux 网络编程四:TCP 并发服务器:构建模式、原理及关键技术(以select )》
  • 3 无重复字符的最长子串
  • Windows系统之不使用第三方软件查看电脑详细配置信息
  • 基于linux系统的LIRC库学习笔记
  • Ubuntu 的磁盘管理
  • [java] 控制三个线程按顺序交替输出数字1、2、3
  • 【新版发布】Apache DolphinScheduler 3.3.1 正式上线:更稳、更快、更安全!
  • TensorFlow 面试题及详细答案 120道(21-30)-- 模型构建与神经网络
  • 数据结构:创建堆(或者叫“堆化”,Heapify)
  • 增强CD47检查点免疫治疗:高通量发现增强巨噬细胞吞噬作用的小分子协同剂
  • nestjs 连接redis
  • HIVE的Window functions窗口函数【一】
  • 手写题(面试)
  • LeetCode算法日记 - Day 24: 颜色分类、排序数组
  • LeetCode - 155. 最小栈
  • Python Imaging Library (PIL) 全面指南:PIL基础入门-跨平台安装与环境配置
  • Redis 数据结构
  • Linex系统网络管理(二)
  • 【yocto】Yocto Project 核心:深入了解.inc文件
  • Java中使用Spring Boot+Ollama构建本地对话机器人
  • Maven 依赖传递与排除基础逻辑
  • Astah UML 中,状态机(State Machine)的建模最合适使用「UML 状态图(State Diagram)」
  • 轻量级自动驾驶多视图视觉问答模型-EM-VLM4AD
  • 鸿蒙HarmonyOS状态管理装饰器详解