Skip to content

首先请新建工作空间:

shell
cargo new gessing_game

现在在命令行中打开工作空间并尝试运行:

shell
cargo run

将得到一行打印为 Hello, world 的文本.

本小节目标

  1. 让玩家完成一次猜测;
  2. 打印玩家猜的的内容.

代码编写

请复制粘贴以下代码:

rust
// ./src/main.rs
use std::io;

fn main() {
    println!("请猜测一个数");
    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("读取有误时的报错");
    println!("你猜的是: {}", guess);
}

下面详细讲解代码:

  1. let mut guess = String::new(); 定义 (令, let) 了一个可变的 (mutable) 变量, 名为 guess, 并且将其赋值为一个新建 (new)的空字符串 (String). 其中
    • let 关键字用于定义变量. 不同于 C/C++, Rust 不以类型名为定义变量的关键字;
    • mut 表示当前变量 (variable) 可变. 尽管变量一词无论从中文还是英文看都理应可变, 但实际上 Rust 中的变量默认不可变, 用户必须通过手动输入 mut 关键字显式声明变量可变, 否则变量在初次赋值后不再可变;
    • = 左右连接欲赋值的变量与将赋的值;
    • String::new字符串 (String) 类型下的 新建 (new) 方法, 返回一个空字符串;

TIP

定义变量语句可以与赋值语句分开写:

rust
let guess;
guess = String::new();

TIP

语法将在下一章系统性介绍.

  1. io::stdin().read_line(&mut guess).expect("读取有误时的报错"); 调用了标准库 (standard crate) 下的 输入输出 (input & output) 子库的 标准输入 (standard input) 方法读取用户输入, 而后通过 read_line() 方法读取用户输入并写入可变变量 guess 的可变借用 (&mut), 最后调用 .except() 方法指明前一方法失败时的处理方式. 其中
    • iostd::io 被引入当前 crate 后的简写, 引入这一操作由第一行的 use std::io; 完成. std::io 即 Rust 标准库里的 IO 库, 负责一系列标准输入输出;
    • io::stdin() 表示调用了 io 库里的 stdin() 函数, 该函数不接收参数, 返回一个 Stdin 类型, 将为命令行输入完成一系列准备工作;
    • .read_line(&mut guess)Stdin 类型的方法. 接收参数 (&self, &mut String), 此处的 &self 为对自身也就是 Stdin 类型数据的引用, 当作为这种 eoo().foo() 的一部分时, 第一个对自身的引用须略去; 而第二个参数类型 &mut String 即字符串类型 String 的一个可变引用, 引用说明需要获取数据本身, 而非获取变量, 而可变引用表示不仅要获取数据, 还要修改数据. 这一方法会在其内部通过可变引用直接更改变量 guess, 而不将其返回. 事实上该方法返回类型为 Result<usize> 的数据, usize 即用于表示大小的无符号整数类型, 这里的返回值实际为用户输入内容的字符数; 而 usize 被外层的 Result 枚举类型包裹, 泛型枚举类型 Result<T, E> 可能返回 Ok(T) 变体, 表示运行顺利得到的 T 类型输出, 或返回 Err(E) 变体表示捕获并输出运行错误, 错误类型为 E, 这意味着需要处理可能的失败情况;
    • .expect("字符串"); 为泛型方法, 用于处理 Result 枚举类型的结果. 接收参数 (self: T, &str) 并返回 T, 第一个参数即其需处理的被 Result 包裹起来的数据 Result<T, E>, 第二个参数 str 决定其收到 Err(E) 变体时打印的错误信息, 而如果接收到 Ok(T) 变体则提取并返回其中的 T 类型数据;
    • println!("你猜的是: {}", guess); 使用了 println! 这个宏, 功能就是格式化打印字符串, 其中的花括号 "..{}" 表示其中填入参数, 而参数即后面的变量 guess. 此外这里也可以写作 println!("你猜的是: {guess}");.

注意

.except() 收到错误之后不仅会打印错误, 还会立即中止程序, 类似的方法还有 .unwrap(), 这类方法仅推荐用于初期 demo 代码, 成熟代码中应当彻底替换为更妥善的错误处理流程, 否则为什么要用 Rust? 参考 Claudflare 崩溃事件. 尽管事件中 .unwrap() 仅仅是业务逻辑纰漏的导火索, 但从其造成的破坏来看, 未经妥善处理的错误仍然相当危险.

函数与方法

函数或方法由如下方式定义:

rust
fn foo(arg1:T1,..)->T{...}

简单来看, 如果参数列表的第一个参数为 self, &self, &mut self 之一, 则 foo 为方法 (method), 否则为函数 (function). 方法和函数均可以直接调用, 如:

rust
let a = foo(args..);

但是只有方法可以被链式调用:

rust
fn f1() -> T1 {..}
fn f2(self:T1) -> T2 {..}
fn f3(self:T2) -> T3 {..}
fn f4(self:&T3) {..} // 等价于 fn f4(T3) -> () {..}

let a = f1().f2().f3();
(&a).f4();

链式调用时, 方法的第一个参数略去, 而以 arg1.foo(arg2,..) 的形式传入 arg1 作为第一个参数.

TIP

值得指出, 方法在技术上仅仅是语法糖, arg1.foo(arg2,..) 等价于 Type::foo(arg1,arg2,..)Type::foo(&arg1,arg2,..)Type::foo(&mut arg1,arg2,..), 具体如何实现取决于方法的第一个参数以何种形式传入.

对我们上面总计三个方法的链式调用:

rust
io::stdin()
    .read_line(&mut guess)
    .expect("读取有误时的报错");

可作如下理解:

预导入模块

可以注意到, 必须 use std::io; 手动导入 IO 库后方可调用之, 然而同样作为标准库之一的 std::String 却无需如此, 而仅需直接调用. 因为 std::String 属于 Rust 的预导入 (Prelude) 模块, 代码编译时会默认导入这部分库.