这篇文章不仅带你一步步完成游戏,更会清晰地解释 Rust 的核心概念,让你在实践中真正理解这门语言。
各位亲爱的小伙伴们,想学一门强大的编程语言吗?今天,我们就用 Rust 来做一个超经典的"猜数字"游戏。一边玩,一边就把 Rust 最重要的几个"法宝"给学明白了!准备好了吗?Let's go!
游戏规则很简单
- 电脑心里想一个 1 到 100 之间的秘密数字(每次都不一样!)。
- 你来猜这个数字。
- 电脑告诉你:你猜大了、猜小了,还是猜对了!
- 猜对了,游戏结束,你赢了!
我们的目标就是用 Rust 代码实现这个游戏。
第一步:创建你的第一个 Rust 项目
打开你的终端(命令行工具),输入这两条命令:
bash
cargo new guess_game
cd guess_game
cargo new guess_game:Cargo(Rust 的"包管理器和项目构建工具")会为你创建一个叫guess_game的新项目。cd guess_game:进入这个新项目文件夹。
现在,你的项目里已经有了一个"Hello, World!"程序。让我们先运行一下,看看 Rust 环境是不是正常:
bash
cargo run
你应该能看到:
Hello, world!
太棒了,环境没问题!现在,我们要把 src/main.rs 这个文件里的代码,从"Hello,world!"改成我们的猜数字游戏。
第二步:让程序和你"对话"(输入与输出)
打开 src/main.rs 文件,把里面的内容全删掉,换成下面这些:
rust
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 核心思想:
-
use std::io;:导入工具箱std::io是 Rust 标准库里的"输入输出"工具箱。use就是把这工具箱"拿"过来,后面才能用它。
-
fn main() { ... }:程序的"入口"- 这是每个 Rust 程序都必须有的"主函数"。程序一运行,就从这里开始执行。
-
println!:打印信息- 这个感叹号
!很关键!它表示println!不是一个普通函数,而是一个宏(macro)。宏就像"代码生成器",能帮你做更复杂的事情。在这里,它就是把括号里的内容打印到屏幕上,关于宏的介绍我们在后面的章节再详细介绍
- 这个感叹号
-
let mut guess = String::new();:创建变量(可变的)let:意思是"创建一个新变量"。mut:这是 Rust 的核心特性之一------默认不可变 !在 Rust 里,你创建的变量默认是"锁死"的,不能改。如果你想让它能变(比如,要装用户输入的新值),就必须加上mut(mutable 的缩写)。guess:这是变量的名字,就像给盒子贴的标签。String::new():String是 Rust 里表示"文本"(字符串)的类型。::new()是一个关联函数 ,意思是"创建一个新的String实例"。所以这行代码就是:创建一个叫guess的、可变的、空的"文本盒子"。
-
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 编译器会警告你,因为它强制要求你处理可能的错误!
-
println!("你猜的是:{}", guess);:格式化输出{}是一个"占位符",像一个小钩子,等着钩住后面的值。guess这个变量的值就会被放到{}的位置打印出来。
现在,运行 cargo run,试试看!输入一个数字,看看程序是不是能正确读取并打印出来。
第三步:生成秘密数字(引入外部"库")
我们的游戏还缺最关键的部分:那个神秘的数字!Rust 标准库没有生成随机数的功能,但别担心,Rust 社区有现成的"轮子"可以用!
-
添加依赖 :打开项目根目录下的
Cargo.toml文件。在[dependencies]下面添加一行:toml[dependencies] rand = "0.8.5"这行代码告诉 Cargo:"我的项目需要一个叫
rand的外部库,版本号是 0.8.5"。 -
写代码 :回到
src/main.rs,在文件开头添加:rustuse rand::Rng; // 引入 rand 库里的 Rng 特性然后,在
main函数里,println!语句之后,创建guess变量之前,加上:rustlet 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!
rust
// 把原来的 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):调用guess的cmp(compare)方法,和secret_number比较。它会返回一个std::cmp::Ordering枚举。match:这是 Rust 的"瑞士军刀"!它会检查cmp返回的值,然后和下面的"分支"(=>左边的部分)进行精确匹配。std::cmp::Ordering::Less:如果比较结果是"小于",就执行=>右边的代码,打印"太小了!"。Greater和Equal同理。
match 要求你必须处理所有可能的情况!这确保了你的代码不会遗漏任何分支,非常安全。
第五步:循环猜,直到猜对(循环)
现在程序一运行,猜一次就结束了。我们得让它能一直猜!
用 loop 关键字创建一个无限循环:
rust
// 把从 "请输入..." 到 "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)才能比较。
原来的代码是:
rust
// 这行代码会崩溃,如果输入不是数字!
let guess: u32 = guess.trim().parse().expect("请输入一个数字!");
我们用更优雅的 match 来处理:
rust
// 在 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)来强制你处理错误,而不是让程序默默崩溃或产生难以预料的行为。
大功告成!完整代码
rust
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 的哪些核心?
- 默认不可变性 (
mut) :变量默认不能改,想改必须加mut。这大大减少了因意外修改数据导致的 bug。 - 所有权与引用 (
&) :&mut guess展示了"借用"(borrowing)。你可以把数据的"引用"借给别人用,而不必转移所有权(复制数据),这既高效又安全。 - 错误处理 (
Result&match) :Rust 不用"异常",而是用Result类型来表示操作可能成功或失败。你必须用match或.expect()等方式明确处理错误,这让你的程序更健壮。 - 模式匹配 (
match) :match是 Rust 的核心控制流,它能穷尽所有可能性,让你的代码逻辑清晰且无遗漏。 - 包管理 (
Cargo) :Cargo.toml定义依赖,cargo build/run管理项目。简单高效。 - 类型系统 :
String,u32,Result,Ordering都是强大的类型。编译器在编译时就能帮你发现很多错误。 - 宏 (
println!) :以!结尾的都是宏,它们能生成代码,实现更复杂的功能。
恭喜你!你不仅做了一个游戏,还亲手触摸到了 Rust 这门现代系统编程语言的精髓:安全、并发、高性能。继续加油!