Rust官方文档猜数游戏解析记录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::io;

fn main() {
    println!("开始猜数");
    println!("请输入你想猜的数:");
    let mut guess = String::new();
    io::stdin()
    .read_line(&mut guess)
    .expect("读取失败!");
    println!("你猜的数是:{guess}");
}

其中use std::io中的std代表标准库,main函数是程序入口点。

rust中变量的设置分为可变和不可变 其区别在于: 这行代码新建了一个叫做 apples 的变量并把它绑定到值 5 上。在 Rust 中,变量默认是不可变的,这意味着一旦我们给变量赋值,这个值就不再可以修改了。下面的例子展示了如何在变量名前使用 mut 来使一个变量可变:

1
2
let apples = 5; // 不可变
let mut bananas = 5; // 可变

::new 那一行的 :: 语法表明 newString 类型的一个 关联函数associated function)。关联函数是针对类型实现的,在这个例子中是 String,而不是 String 的某个特定实例。一些语言中把它称为 静态方法static method)。

new 函数创建了一个新的空字符串,你会发现很多类型上有 new 函数,因为它是创建类型实例的惯用函数名。

代码的下一部分,.read_line(&mut guess),调用 read_line 方法从标准输入句柄获取用户输入。我们还将 &mut guess 作为参数传递给 read_line() 函数,让其将用户输入储存到这个字符串中。read_line 的工作是,无论用户在标准输入中键入什么内容,都将其追加(不会覆盖其原有内容)到一个字符串中,因此它需要字符串作为参数。这个字符串参数应该是可变的,以便 read_line 将用户输入附加上去。

& 表示这个参数是一个 引用reference),它允许多处代码访问同一处数据,而无需在内存中多次拷贝。引用是一个复杂的特性,Rust 的一个主要优势就是安全而简单的操纵引用。现在,我们只需知道它像变量一样,默认是不可变的。因此,需要写成 &mut guess 来使其可变,而不是 &guess

之前提到了 read_line 会将用户输入附加到传递给它的字符串中,不过它也会返回一个类型为 Result 的值。 Result 是一种枚举类型,通常也写作 enum。枚举类型变量的值可以是多种可能状态中的一个。我们把每种可能的状态称为一种 枚举成员(variant)

Result 的成员是 OkErrOk 成员表示操作成功,内部包含成功时产生的值。Err 成员则意味着操作失败,并且包含失败的前因后果。

这些 Result 类型的作用是编码错误处理信息。Result 类型的值,像其他类型一样,拥有定义于其上的方法。Result 的实例拥有 expect 方法。如果 io::Result 实例的值是 Errexpect 会导致程序崩溃,并显示当做参数传递给 expect 的信息。如果 read_line 方法返回 Err,则可能是来源于底层操作系统错误的结果。如果 Result 实例的值是 Okexpect 会获取 Ok 中的值并原样返回。在本例中,这个值是用户输入到标准输入中的字节数。

如果不调用 expect,程序也能编译,不过会出现一个警告,消除警告的正确做法是实际去编写错误处理代码,不过由于我们就是希望程序在出现问题时立即崩溃,所以直接使用 expect

占位符{},里面的 {} 是预留在特定位置的占位符:把 {} 想象成小蟹钳,可以夹住合适的值。当打印变量的值时,变量名可以写进大括号中。当打印表达式的执行结果时,格式化字符串(format string)中大括号中留空,格式化字符串后跟逗号分隔的需要打印的表达式列表,其顺序与每一个空大括号占位符的顺序一致。在一个 println! 调用中打印变量和表达式的值看起来像这样:

1
2
3
4
let x = 5;
let y = 10;

println!("x = {x} and y + 2 = {}", y + 2);

打印结果:x = 5 and y + 2 = 12


范围应该在 1 到 100 之间,这样才不会太困难。Rust 标准库中尚未包含随机数功能。然而,Rust 团队还是提供了一个包含上述功能的 rand crate

记住,crate 是一个 Rust 代码包。我们正在构建的项目是一个 二进制 crate,它生成一个可执行文件。 rand crate 是一个 库 crate,库 crate 可以包含任意能被其他程序使用的代码,但是不能自执行。

Cargo 对外部 crate 的运用是其真正的亮点所在。在我们使用 rand 编写代码之前,需要修改 Cargo.toml 文件,引入一个 rand 依赖。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::io;
use rand::Rng;

fn main() {
    println!("开始猜数");
    let sercet_number= rand::thread_rng().gen_range(1..=100);
    println!("生成的随机数为:{sercet_number}");
    println!("请输入你想猜的数:");
    let mut guess = String::new();
    io::stdin()
    .read_line(&mut guess)
    .expect("读取失败!");

    println!("你猜的数是:{guess}");
}

我们新增了一行 use rand::Rng;Rng 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中。第十章会详细介绍 trait。

第一行调用了 rand::thread_rng 函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取 seed。接着调用随机数生成器的 gen_range 方法。这个方法由 use rand::Rng 语句引入到作用域的 Rng trait 定义。gen_range 方法获取一个范围表达式(range expression)作为参数,并生成一个在此范围之间的随机数。这里使用的这类范围表达式使用了 start..=end 这样的形式,也就是说包含了上下端点,所以需要指定 1..=100 来请求一个 1 和 100 之间的数。 上面的功能不可能凭空知道,请查阅文档:

Cargo 有一个很棒的功能是:运行 cargo doc --open 命令来构建所有本地依赖提供的文档,并在浏览器中打开。例如,假设你对 rand crate 中的其他功能感兴趣,你可以运行 cargo doc --open 并点击左侧导航栏中的 rand

未完待续……