趣味学习Rust基础篇(用Rust做一个猜数字游戏)
这篇文章不仅带你一步步完成游戏,更会清晰地解释 Rust 的核心概念,让你在实践中真正理解这门语言。
各位亲爱的小伙伴们,想学一门强大的编程语言吗?今天,我们就用 Rust 来做一个超经典的“猜数字”游戏。一边玩,一边就把 Rust 最重要的几个“法宝”给学明白了!准备好了吗?Let’s go!
游戏规则很简单
- 电脑心里想一个 1 到 100 之间的秘密数字(每次都不一样!)。
- 你来猜这个数字。
- 电脑告诉你:你猜大了、猜小了,还是猜对了!
- 猜对了,游戏结束,你赢了!
我们的目标就是用 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 核心思想:
-
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]
下面添加一行:[dependencies] rand = "0.8.5"
这行代码告诉 Cargo:“我的项目需要一个叫
rand
的外部库,版本号是 0.8.5”。 -
写代码:回到
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)
:调用guess
的cmp
(compare)方法,和secret_number
比较。它会返回一个std::cmp::Ordering
枚举。match
:这是 Rust 的“瑞士军刀”!它会检查cmp
返回的值,然后和下面的“分支”(=>
左边的部分)进行精确匹配。std::cmp::Ordering::Less
:如果比较结果是“小于”,就执行=>
右边的代码,打印“太小了!”。Greater
和Equal
同理。
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 的哪些核心?
- 默认不可变性 (
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 这门现代系统编程语言的精髓:安全、并发、高性能。继续加油!