Rust 从零开始学:基础语法与实践入门 – wiki基地


Rust 从零开始学:基础语法与实践入门(详细版)

Rust 是一门赋予每个人构建可靠且高效软件能力的系统编程语言。它由 Mozilla 主导开发,并以其内存安全并发性性能三大优势在开发者社区中迅速崛起。如果你厌倦了 C/C++ 带来的悬垂指针和内存泄漏风险,又或者觉得 Python/Java 等语言在性能上有所欠缺,那么 Rust 可能是你理想的选择。

本文旨在为完全没有 Rust 基础的初学者提供一份详尽的入门指南,我们将从 Rust 的核心理念出发,逐步深入基础语法,并通过实践来巩固学习成果。准备好了吗?让我们一起踏上 Rust 的学习之旅!

一、 为什么选择 Rust?

在深入语法之前,我们先理解一下 Rust 为何如此特别,以及学习它能带来什么好处:

  1. 性能卓越:Rust 编译为本地机器码,其性能与 C/C++ 相当。它没有运行时(Runtime)或垃圾回收器(Garbage Collector, GC)的开销,使得程序运行速度极快,内存占用低。这使其非常适合性能敏感的场景,如游戏引擎、操作系统、浏览器组件、嵌入式设备等。
  2. 内存安全:这是 Rust 最核心的特性。通过其创新的所有权(Ownership)系统借用(Borrowing)检查器生命周期(Lifetimes),Rust 能够在编译时就消除绝大多数内存安全问题(如空指针解引用、悬垂指针、数据竞争等),而无需 GC。这意味着你可以在享受高性能的同时,获得类似 Java 或 Python 的内存安全保证。
  3. 并发安全:Rust 的所有权和类型系统也延伸到了并发编程领域。它能在编译时防止数据竞争(Data Races),让编写并发代码变得更加“无畏”(Fearless Concurrency)。你不再需要小心翼翼地处理锁和线程同步,编译器会帮你检查很多潜在问题。
  4. 现代化工具链:Rust 拥有极其出色的构建系统和包管理器——Cargo。Cargo 负责处理项目构建、依赖管理、测试、文档生成等一系列任务,极大地简化了开发流程。它的体验远超许多传统语言的工具链。
  5. 强大的类型系统和模式匹配:Rust 拥有丰富的类型系统,支持泛型、Trait(类似于接口或抽象类)等特性,有助于编写抽象且可复用的代码。其 enummatch 表达式提供了强大的模式匹配能力,让处理复杂状态和数据结构变得优雅而安全。
  6. 活跃的社区和生态:Rust 拥有一个充满活力、乐于助人的社区。生态系统(通过 Crates.io 分发)正在快速发展,涵盖了 Web 开发(Actix, Rocket)、网络编程(Tokio)、数据科学、游戏开发(Bevy)等多个领域。

二、 准备工作:安装 Rust 环境

Rust 的安装过程非常简单。官方推荐使用 rustup,这是一个 Rust 版本管理工具。

  1. 访问官网:打开 Rust 官方网站 https://www.rust-lang.org/,点击 “Get Started”。
  2. 根据操作系统指示安装
    • Linux / macOS: 打开终端,运行命令:
      bash
      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

      按照提示进行安装。安装完成后,可能需要重新启动终端或运行 source $HOME/.cargo/env (或类似命令) 来使环境变量生效。
    • Windows: 下载并运行 rustup-init.exe 安装程序。它会引导你完成安装,并可能提示你需要安装 C++ build tools for Visual Studio(如果尚未安装)。
  3. 验证安装:安装完成后,在终端或命令提示符中运行以下命令,检查 Rust 编译器 (rustc) 和 Cargo (cargo) 是否安装成功:
    bash
    rustc --version
    cargo --version

    如果能看到版本号输出,说明安装成功。

rustup 还可以方便地更新 Rust 版本 (rustup update)、安装不同通道的版本(stable, beta, nightly)以及管理工具链(如 rustfmt 代码格式化工具、clippy 代码 Lint 工具)。

三、 第一个 Rust 程序:Hello, World!

学习任何语言的传统都是从 “Hello, World!” 开始。

  1. 创建项目目录:在你喜欢的位置创建一个文件夹,例如 rust_projects
  2. 创建源文件:在 rust_projects 目录下创建一个名为 main.rs 的文件(.rs 是 Rust 源文件的扩展名)。
  3. 编写代码:在 main.rs 文件中输入以下代码:

    rust
    // main.rs
    fn main() {
    println!("Hello, world!");
    }

    • fn main(): 这是程序的入口点。fn 关键字用于声明一个函数,main 是这个特殊函数的名字。
    • { ... }: 大括号包围了函数体。
    • println!("Hello, world!");: 这行代码做了两件事:
      • println! 是一个 宏(Macro),而不是普通函数(注意末尾的 !)。宏是 Rust 元编程的一部分,可以在编译时生成代码。println! 用于将文本输出到控制台(标准输出)。
      • "Hello, world!" 是一个字符串字面量,作为参数传递给 println! 宏。
      • 语句以分号 ; 结尾。
  4. 编译和运行

    • 打开终端或命令提示符,切换到包含 main.rs 的目录。
    • 使用 Rust 编译器 rustc 编译代码:
      bash
      rustc main.rs

      这会生成一个可执行文件(在 Linux/macOS 上通常是 main,在 Windows 上是 main.exe)。
    • 运行可执行文件:
      • Linux/macOS: ./main
      • Windows: .\main.exe
    • 你应该能在终端看到输出:Hello, world!

四、 Cargo:Rust 的构建系统与包管理器

手动使用 rustc 编译单个文件还可以,但对于复杂的项目,我们需要更强大的工具。这就是 Cargo 发挥作用的地方。

  1. 创建 Cargo 项目:让我们用 Cargo 重新创建 “Hello, World!” 项目。在 rust_projects 目录下(不是 main.rs 所在的目录)运行:
    bash
    cargo new hello_cargo
    cd hello_cargo

    Cargo 会自动为你创建一个名为 hello_cargo 的新目录,其结构如下:
    hello_cargo/
    ├── Cargo.toml
    └── src/
    └── main.rs

    • Cargo.toml: 这是 Cargo 的清单文件,采用 TOML 格式。它包含了项目的元数据(名称、版本、作者等)和依赖项列表。
    • src/main.rs: Cargo 默认将源代码放在 src 目录下,并且入口文件仍然是 main.rs。它已经为你生成了 “Hello, world!” 的代码。
  2. 构建项目:在 hello_cargo 目录下,运行:
    bash
    cargo build

    Cargo 会编译你的项目及其所有依赖(目前还没有),并将可执行文件放在 target/debug/ 目录下(例如 target/debug/hello_cargo)。

  3. 运行项目:你可以直接运行:
    bash
    cargo run

    Cargo 会先编译(如果需要),然后运行生成的可执行文件。你应该再次看到 Hello, world! 输出。

  4. 检查项目:运行:
    bash
    cargo check

    这个命令会快速检查代码是否能通过编译,但不生成可执行文件,速度比 cargo build 快,适合在开发过程中频繁检查。

  5. 发布构建:当你准备好发布优化过的版本时,运行:
    bash
    cargo build --release

    Cargo 会进行优化编译,生成的可执行文件位于 target/release/ 目录下,运行速度更快,但编译时间更长。

Cargo 是 Rust 开发的核心,务必熟悉它的基本用法。

五、 Rust 基础语法

现在我们开始深入了解 Rust 的基本语法元素。

1. 变量与可变性 (Variables and Mutability)

  • 声明变量: 使用 let 关键字声明变量。Rust 具有类型推断能力,通常不需要显式指定类型。
    rust
    let x = 5; // x 被推断为 i32 (默认整数类型)
    let y = 2.0; // y 被推断为 f64 (默认浮点数类型)
    let z = 'z'; // z 被推断为 char
    let is_active = true; // is_active 被推断为 bool
  • 不可变性 (Immutability): 默认情况下,Rust 中的变量是不可变的。一旦绑定了一个值,就不能再改变它。
    rust
    let x = 5;
    // x = 6; // 这行代码会编译错误!Cannot assign twice to immutable variable `x`

    这种设计有助于编写更安全、更易于推理的代码。
  • 可变性 (Mutability): 如果需要一个可以改变值的变量,需要使用 mut 关键字显式声明。
    rust
    let mut count = 0;
    println!("Initial count: {}", count); // 输出: Initial count: 0
    count = 1;
    println!("Updated count: {}", count); // 输出: Updated count: 1
  • 常量 (Constants): 使用 const 声明常量。常量必须显式指定类型,并且其值必须是编译时常量。常量在整个程序生命周期内都有效。
    rust
    const MAX_POINTS: u32 = 100_000; // 使用下划线提高可读性
  • 隐藏 (Shadowing): Rust 允许使用 let 关键字声明一个与之前变量同名的新变量,这称为隐藏。新变量会“隐藏”旧变量,并且新变量可以有不同的类型。
    rust
    let spaces = " "; // spaces 是 &str 类型
    let spaces = spaces.len(); // spaces 现在是 usize 类型,值为 3
    println!("Number of spaces: {}", spaces);

    隐藏与 mut 不同,隐藏是创建了一个全新的变量。

2. 数据类型 (Data Types)

Rust 是静态类型语言,所有变量的类型必须在编译时确定。主要分为两类:标量类型和复合类型。

  • 标量类型 (Scalar Types): 表示单个值。

    • 整数 (Integers): 有符号 (i8, i16, i32, i64, i128, isize) 和无符号 (u8, u16, u32, u64, u128, usize)。isizeusize 的大小取决于目标机器的架构(32位或64位),通常用于索引集合。
      rust
      let age: u8 = 30;
      let score: i32 = -10;
      let index: usize = 5;
    • 浮点数 (Floating-Point Numbers): f32 (单精度) 和 f64 (双精度,默认)。
      rust
      let pi = 3.14159; // f64 (默认)
      let temperature: f32 = 98.6;
    • 布尔值 (Booleans): bool 类型,只有两个可能的值:truefalse
      rust
      let is_rust_fun: bool = true;
    • 字符 (Characters): char 类型,表示单个 Unicode 标量值,用单引号 ' 包裹。占用 4 个字节。
      rust
      let initial = 'R';
      let emoji = '🚀';
  • 复合类型 (Compound Types): 将多个值组合成一个类型。

    • 元组 (Tuples): 固定大小的有序集合,可以包含不同类型的值。用圆括号 () 包裹。
      “`rust
      let tup: (i32, f64, u8) = (500, 6.4, 1);
      let (x, y, z) = tup; // 解构元组
      println!(“The value of y is: {}”, y); // 输出: The value of y is: 6.4

      let first_element = tup.0; // 通过索引访问元素 (从 0 开始)
      println!(“The first element is: {}”, first_element); // 输出: The first element is: 500
      * **数组 (Arrays)**: 固定大小的集合,所有元素必须具有**相同**的类型。用方括号 `[]` 包裹。数组存储在栈上。rust
      let a: [i32; 5] = [1, 2, 3, 4, 5]; // 类型是 [i32; 5] (类型; 长度)
      let first = a[0];
      let second = a[1];
      // let element = a[10]; // 编译时不会报错,但运行时会 panic (数组越界)

      let b = [3; 5]; // 创建一个包含 5 个元素,每个都为 3 的数组: [3, 3, 3, 3, 3]
      ``
      注意:如果你需要一个可变大小的集合,应该使用**向量 (Vector)**
      Vec`,它属于标准库的集合类型,我们稍后会简单提及。

3. 函数 (Functions)

我们已经见过 main 函数了。Rust 使用蛇形命名法(snake_case)来命名函数和变量。

  • 定义函数: 使用 fn 关键字。可以指定参数和返回类型。
    “`rust
    fn another_function(x: i32, y: f64) { // 参数需要显式声明类型
    println!(“The value of x is: {}”, x);
    println!(“The value of y is: {}”, y);
    }

    fn add_one(x: i32) -> i32 { // 使用 -> 指定返回类型
    x + 1 // 函数体最后一行表达式的值会自动作为返回值,无需 return 关键字和分号
    // 如果需要提前返回,可以使用 return 关键字: return x + 1;
    }

    fn main() {
    another_function(5, 6.7);
    let result = add_one(10);
    println!(“10 + 1 = {}”, result); // 输出: 10 + 1 = 11
    }
    * **语句 (Statements) vs 表达式 (Expressions)**:
    * **语句**是执行某些操作但不返回值的指令,以分号结尾。例如 `let x = 5;`。
    * **表达式**会计算并产生一个值。例如 `5 + 6`, `add_one(10)`, 甚至 `{ ... }` 代码块也可以是表达式。
    rust
    fn main() {
    let y = {
    let x = 3;
    x + 1 // 这个块的值是 x + 1 的结果,即 4
    }; // 注意这里没有分号,因为整个 { … } 是一个表达式,它的值赋给了 y

    println!("The value of y is: {}", y); // 输出: The value of y is: 4
    

    }
    “`
    函数体最后没有分号的表达式是函数的隐式返回值。

4. 注释 (Comments)

  • 单行注释: // 之后到行尾的内容会被忽略。
  • 多行注释: /* ... */ 可以跨越多行。
  • 文档注释: /// (用于模块、函数、结构体等项) 或 //! (用于包含项的模块本身)。它们支持 Markdown 格式,可以通过 cargo doc 生成 HTML 文档。

5. 控制流 (Control Flow)

  • if 表达式: 条件必须是 bool 类型。
    “`rust
    let number = 6;

    if number % 4 == 0 {
    println!(“number is divisible by 4”);
    } else if number % 3 == 0 {
    println!(“number is divisible by 3”);
    } else if number % 2 == 0 {
    println!(“number is divisible by 2”);
    } else {
    println!(“number is not divisible by 4, 3, or 2”);
    }

    // if 是一个表达式,可以在 let 语句中使用
    let condition = true;
    let value = if condition { 5 } else { 6 }; // if 和 else 分支的类型必须相同
    println!(“The value is: {}”, value); // 输出: The value is: 5
    * **循环 (Loops)**: Rust 提供了三种循环结构。
    * **`loop`**: 无限循环,需要使用 `break` 来退出。可以从 `break` 返回值。
    rust
    let mut counter = 0;
    let result = loop {
    counter += 1;
    if counter == 10 {
    break counter * 2; // 退出循环并返回 counter * 2 的值
    }
    };
    println!(“The result is {}”, result); // 输出: The result is 20
    * **`while`**: 条件循环。rust
    let mut number = 3;
    while number != 0 {
    println!(“{}!”, number);
    number -= 1;
    }
    println!(“LIFTOFF!!!”);
    * **`for`**: 遍历**迭代器 (Iterator)**。这是最常用和最安全的循环方式。rust
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() { // iter() 返回数组元素的迭代器
    println!(“the value is: {}”, element);
    }

    // 遍历范围 (Range)
    for number in (1..4).rev() { // (1..4) 是 1, 2, 3 (不含 4)
                                // .rev() 反转迭代器
        println!("{}!", number);
    }
    println!("LIFTOFF!!!"); // 输出: 3! 2! 1! LIFTOFF!!!
    ```
    

六、 Rust 的核心:所有权 (Ownership)

这是 Rust 最独特且最重要的概念,也是初学者可能遇到的第一个难点。它让 Rust 能够在没有垃圾回收器的情况下保证内存安全。

核心规则:

  1. 每个值都有一个变量,称为其所有者(Owner)。
  2. 一个值在同一时间只能有一个所有者。
  3. 当所有者离开作用域(Scope)时,该值将被丢弃(Dropped),其内存会被释放。

示例与解释:

  • 变量作用域:
    rust
    { // s 在这里无效,它尚未声明
    let s = "hello"; // 从这里开始 s 有效
    // 可以使用 s
    } // 这个作用域结束了,s 不再有效,内存被释放(对于 String 类型)
  • String 类型: 为了更好地演示所有权,我们使用 String 类型(存储在堆上,可变)而不是字符串字面量 &str(存储在代码中,不可变)。
    “`rust
    let s1 = String::from(“hello”); // s1 拥有 “hello” 字符串数据的所有权
    let s2 = s1; // 所有权从 s1 移动 (Move) 给了 s2
    // 此时 s1 不再有效!尝试使用 s1 会导致编译错误
    // println!(“s1 = {}”, s1); // 编译错误: value borrowed here after move

    println!(“s2 = {}”, s2); // 输出: s2 = hello
    ``
    对于像
    String` 这样存储在堆上的数据,赋值操作默认是移动所有权,而不是浅拷贝或深拷贝。这是为了防止二次释放 (Double Free) 错误(两个变量指向同一内存,都尝试释放它)。

  • 克隆 (Clone): 如果确实需要深拷贝堆上的数据,可以使用 clone() 方法。
    “`rust
    let s1 = String::from(“hello”);
    let s2 = s1.clone(); // s2 是 s1 数据的一个完整拷贝,s1 和 s2 都拥有各自的数据

    println!(“s1 = {}, s2 = {}”, s1, s2); // 输出: s1 = hello, s2 = hello
    * **栈上数据的拷贝 (Copy)**: 对于完全存储在栈上的简单类型(如整数、浮点数、布尔值、字符、只包含这些类型的元组),它们实现了 `Copy` trait。赋值操作会进行简单的位拷贝,旧变量仍然有效。rust
    let x = 5; // i32 实现了 Copy
    let y = x; // x 的值被拷贝给 y

    println!(“x = {}, y = {}”, x, y); // 输出: x = 5, y = 5 (x 仍然有效)
    * **所有权与函数**: 将值传递给函数或从函数返回值也会转移所有权。rust
    fn takes_ownership(some_string: String) { // some_string 获得所有权
    println!(“{}”, some_string);
    } // some_string 离开作用域,其内存被释放

    fn makes_copy(some_integer: i32) { // some_integer 获得值的拷贝
    println!(“{}”, some_integer);
    } // some_integer 离开作用域,但由于是 Copy,原始值不受影响

    fn gives_ownership() -> String {
    let some_string = String::from(“yours”);
    some_string // 返回值,所有权移动给调用者
    }

    fn takes_and_gives_back(a_string: String) -> String {
    a_string // 将接收到的所有权直接返回
    }

    fn main() {
    let s1 = String::from(“hello”);
    takes_ownership(s1); // s1 的所有权移动到函数内
    // println!(“{}”, s1); // 编译错误: s1 已失效

    let x = 5;
    makes_copy(x); // x 的值被拷贝到函数内
    println!("{}", x); // x 仍然有效
    
    let s2 = gives_ownership(); // s2 获得函数返回值的所有权
    println!("{}", s2);
    
    let s3 = String::from("original");
    let s4 = takes_and_gives_back(s3); // s3 的所有权移动给函数,然后又移动给 s4
    // println!("{}", s3); // 编译错误: s3 已失效
    println!("{}", s4);
    

    }
    “`

七、 引用与借用 (References and Borrowing)

如果每次传递值都转移所有权,会很不方便。我们经常需要访问一个值而不取得其所有权。这时就需要使用引用 (References),这个过程称为借用 (Borrowing)

  • 不可变引用 (&T): 允许你读取数据,但不允许修改。
    “`rust
    fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
    s.len()
    } // s 离开作用域,但因为它不拥有所有权,所以指向的数据不会被释放

    fn main() {
    let s1 = String::from(“hello”);
    let len = calculate_length(&s1); // 传递 s1 的引用,而不是所有权
    println!(“The length of ‘{}’ is {}.”, s1, len); // s1 仍然有效!
    }
    “`
    借用规则 1: 在任何给定时间,你可以拥有任意多个对同一数据的不可变引用

  • 可变引用 (&mut T): 允许你读取和修改数据。
    “`rust
    fn change(some_string: &mut String) {
    some_string.push_str(“, world”); // 修改引用的数据
    }

    fn main() {
    let mut s = String::from(“hello”); // 注意:要修改数据,原变量必须是 mut
    change(&mut s); // 传递 s 的可变引用
    println!(“{}”, s); // 输出: hello, world
    }
    “`
    借用规则 2: 在任何给定时间,你只能拥有一个对同一数据的可变引用
    借用规则 3: 在任何给定时间,你不能同时拥有一个可变引用和任何不可变引用(但可以在可变引用作用域结束后再创建不可变引用)。

    这些规则在编译时由借用检查器 (Borrow Checker)强制执行,旨在防止数据竞争 (Data Races)
    * 两个或多个指针同时访问同一数据。
    * 至少有一个指针用于写入数据。
    * 没有同步机制来协调访问。

    “`rust
    // 编译错误示例:
    fn main() {
    let mut s = String::from(“hello”);

    let r1 = &s; // ok: 不可变引用
    let r2 = &s; // ok: 多个不可变引用
    // let r3 = &mut s; // 错误!不能在存在不可变引用的同时创建可变引用
    // println!("{}, {}, and {}", r1, r2, r3);
    
    // ----
    
    let mut s = String::from("hello");
    let r1 = &mut s; // ok: 一个可变引用
    // let r2 = &mut s; // 错误!不能同时存在多个可变引用
    // println!("{}, {}", r1, r2);
    
    // ----
    
    let mut s = String::from("hello");
    let r1 = &s; // 不可变引用
    let r2 = &mut s; // 错误!不能在存在不可变引用的同时创建可变引用
    // println!("{}, {}", r1, r2);
    

    }
    “`

  • 悬垂引用 (Dangling References): Rust 编译器也会阻止你创建悬垂引用(指向无效内存的引用)。
    “`rust
    /*
    fn dangle() -> &String { // dangle 返回一个 String 的引用
    let s = String::from(“hello”); // s 在函数内部创建
    &s // 返回 s 的引用
    } // s 在这里离开作用域并被释放,其内存无效了!

    fn main() {
    let reference_to_nothing = dangle(); // 这个引用将指向无效内存
    // 编译错误: this function’s return type contains a borrowed value, but there is no value for it to be borrowed from
    }
    */
    // 正确的做法是直接返回 String,转移所有权:
    fn no_dangle() -> String {
    let s = String::from(“hello”);
    s
    }
    “`

八、 生命周期 (Lifetimes)

生命周期是 Rust 保证引用有效性的另一个机制,通常与借用检查器一起工作。它确保所有借用(引用)的有效作用域不超过其指向的数据的作用域。

对于许多简单情况,编译器可以自动推断生命周期,我们不需要显式标注。但当函数涉及多个引用,或者结构体包含引用时,有时需要我们手动添加生命周期注解 (Lifetime Annotations)

生命周期注解以撇号 ' 开头,后面跟着一个小写字母名称(通常是 'a, 'b 等)。它们不改变引用的实际存活时间,而是描述多个引用生命周期之间的关系,帮助编译器进行检查。

“`rust
// 一个需要生命周期注解的例子
// 这个函数接受两个字符串切片,并返回较长的那一个切片
// 如果不加生命周期注解,编译器不知道返回的引用是关联到 x 还是 y 的生命周期
fn longest<‘a>(x: &’a str, y: &’a str) -> &’a str {
// ‘a 定义了一个泛型生命周期参数
// x: &’a str 表示 x 是一个引用,其生命周期至少要和 ‘a 一样长
// y: &’a str 同理
// -> &’a str 表示函数返回的引用,其生命周期也至少和 ‘a 一样长
// 这意味着返回的引用与输入参数中生命周期较短的那个相关联
if x.len() > y.len() {
x
} else {
y
}
}

fn main() {
let string1 = String::from(“long string is long”);
let result;
{
let string2 = String::from(“xyz”);
result = longest(string1.as_str(), string2.as_str()); // 编译器确保 result 的生命周期不超过 string2
println!(“The longest string is {}”, result); // 输出: The longest string is long string is long
}
// println!(“The longest string is {}”, result); // 在这里访问 result 会报错,因为 string2 已经失效,result 的生命周期也结束了
// 如果 string1 和 string2 生命周期相同,则可以在这里访问
}
“`

生命周期是一个相对高级的主题,初学者可以先理解其目的(保证引用有效性),并在遇到编译器要求时再深入学习。

九、 结构体 (Structs)

结构体允许你创建自定义的数据类型,将相关的值组合在一起并命名。

“`rust
// 定义结构体
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}

fn main() {
// 创建结构体实例
let mut user1 = User {
email: String::from(“[email protected]”),
username: String::from(“someusername123”),
active: true,
sign_in_count: 1,
};

// 访问和修改字段 (如果实例是可变的)
user1.email = String::from("[email protected]");
println!("User email: {}", user1.email);

// 结构体更新语法 (Struct Update Syntax)
let user2 = User {
    email: String::from("[email protected]"),
    username: String::from("user2"),
    ..user1 // 使用 user1 的剩余字段来填充 user2
            // 注意:这会移动 user1 中非 Copy 的字段(如 String)的所有权
            // 所以 user1.username 在此之后就无效了
};
// println!("{}", user1.username); // 编译错误: use of moved value: `user1.username`
println!("User2 active status: {}", user2.active); // active 是 bool (Copy),所以 user1.active 仍然有效

// 元组结构体 (Tuple Structs): 字段没有名字,只有类型
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("First value of black color: {}", black.0);

// 单元结构体 (Unit-Like Structs): 没有任何字段,用于标记或实现 Trait
struct AlwaysEqual;
let subject = AlwaysEqual;
// 可以对 AlwaysEqual 实现某些行为

}

// 函数可以返回结构体实例
fn build_user(email: String, username: String) -> User {
User {
email, // 字段名和变量名相同时可以简写
username,
active: true,
sign_in_count: 1,
}
}
“`

十、 枚举与模式匹配 (Enums and Pattern Matching)

枚举(Enums)允许你定义一个类型,它可以是几个不同变体(Variants)中的一个。

“`rust
// 定义枚举
enum Message {
Quit, // 没有关联数据
Move { x: i32, y: i32 }, // 关联匿名结构体数据
Write(String), // 关联一个 String 数据
ChangeColor(i32, i32, i32), // 关联三个 i32 数据
}

// 枚举也可以有关联函数和方法 (使用 impl)
impl Message {
fn call(&self) {
// 方法体
println!(“Calling message…”);
}
}

fn main() {
// 创建枚举实例
let msg1 = Message::Quit;
let msg2 = Message::Move { x: 10, y: 20 };
let msg3 = Message::Write(String::from(“hello”));
let msg4 = Message::ChangeColor(255, 0, 0);

msg3.call(); // 调用方法

// 使用 match 处理枚举
process_message(msg2);
process_message(msg3);

}

fn process_message(msg: Message) {
match msg { // match 表达式必须穷尽所有可能的分支
Message::Quit => {
println!(“The Quit variant has no data to destructure.”);
}
Message::Move { x, y } => { // 解构出 x 和 y
println!(“Move in the x direction {} and y direction {}”, x, y);
}
Message::Write(text) => { // 绑定变量 text
println!(“Text message: {}”, text);
}
Message::ChangeColor(r, g, b) => { // 解构出 r, g, b
println!(“Change the color to red {}, green {}, and blue {}”, r, g, b);
}
// 如果不写全所有分支,编译器会报错
}
}

// Option 枚举:处理空值
// Rust 没有 null,而是使用 Option 枚举来表示一个值可能存在或不存在
// enum Option {
// Some(T), // 表示存在值 T
// None, // 表示不存在值
// }

let some_number = Some(5);
let some_string = Some(“a string”);
let absent_number: Option = None; // 需要显式指定类型,因为 None 没有关联值

// 使用 match 处理 Option
fn plus_one(x: Option) -> Option {
match x {
None => None,
Some(i) => Some(i + 1),
}
}

let five = Some(5);
let six = plus_one(five); // six = Some(6)
let none = plus_one(None); // none = None

// if let 语法糖:只关心一种匹配情况时
let some_u8_value = Some(3u8);
match some_u8_value {
Some(3) => println!(“three”),
_ => (), // _ 是通配符,匹配任何未被匹配的值,() 是单元类型,表示什么都不做
}

// 等价的 if let 写法
if let Some(3) = some_u8_value {
println!(“three”);
} else {
// 可选的 else 分支
println!(“Not three”);
}
“`

match 非常强大,可以用于任何类型,并支持复杂的模式匹配,如匹配范围、解构嵌套结构等。

十一、 基础实践:一个简单的猜数游戏

让我们结合所学知识,编写一个简单的命令行猜数游戏。

  1. 创建新项目:
    bash
    cargo new guessing_game
    cd guessing_game
  2. 添加依赖: 我们需要从标准输入读取用户输入,并生成随机数。生成随机数需要用到外部库(称为 Crate)。打开 Cargo.toml 文件,在 [dependencies] 部分添加 rand crate:
    “`toml
    [package]
    name = “guessing_game”
    version = “0.1.0”
    edition = “2021”

    [dependencies]
    rand = “0.8” # 使用一个较新的稳定版本
    ``
    3. **编写代码 (
    src/main.rs`)**:

    “`rust
    use std::io; // 标准库的输入/输出模块
    use rand::Rng; // rand crate 的 Rng trait (用于生成随机数)
    use std::cmp::Ordering; // 标准库的比较模块,包含 Ordering 枚举 (Less, Greater, Equal)

    fn main() {
    println!(“Guess the number!”);

    // 生成一个 1 到 100 之间的随机数
    let secret_number = rand::thread_rng().gen_range(1..=100); // 1..=100 是包含 1 和 100 的范围
    
    // println!("The secret number is: {}", secret_number); // 开发时可以取消注释方便调试
    
    loop { // 无限循环,直到猜对
        println!("Please input your guess.");
    
        let mut guess = String::new(); // 创建一个可变的空字符串来存储用户输入
    
        io::stdin()
            .read_line(&mut guess) // 从标准输入读取一行,追加到 guess 字符串中
            .expect("Failed to read line"); // read_line 返回 Result,expect 在出错时 panic
    
        // 将输入的字符串转换为 u32 数字
        // trim() 去除首尾空白(包括换行符 \n)
        // parse() 尝试将字符串解析为指定类型 (这里通过类型注解 : u32 推断)
        // parse() 返回 Result<T, E>
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num, // 如果解析成功 (Ok),返回数字
            Err(_) => { // 如果解析失败 (Err),下划线 _ 匹配任何错误类型
                println!("Please type a number!");
                continue; // 跳过本次循环的剩余部分,开始下一次循环
            }
        };
    
        println!("You guessed: {}", guess);
    
        // 比较猜测的数字和秘密数字
        match guess.cmp(&secret_number) { // cmp 方法返回 Ordering 枚举
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break; // 猜对了,跳出 loop 循环
            }
        }
    }
    

    }
    “`

  3. 运行游戏:
    bash
    cargo run

    Cargo 会自动下载 rand crate 及其依赖,编译并运行你的游戏。根据提示输入数字,看看你能不能猜中!

十二、 接下来学什么?

恭喜你,已经完成了 Rust 的基础入门!但这仅仅是个开始。Rust 的世界还有很多值得探索的内容:

  • 深入集合类型: Vec<T> (动态数组/向量), HashMap<K, V> (哈希映射), String 的更多操作。
  • 错误处理: 深入了解 Result<T, E>,使用 ? 操作符简化错误传递。
  • 泛型 (Generics): 编写适用于多种类型的代码。
  • Trait: 定义共享行为,类似于接口,是 Rust 实现多态的方式。
  • 生命周期进阶: 处理更复杂的引用场景。
  • 智能指针: Box<T>, Rc<T>, RefCell<T> 等,提供不同的内存管理和所有权模式。
  • 并发编程: 线程、消息传递、async/await 异步编程。
  • 模块系统:组织代码结构 (mod, use, pub)。
  • 测试: 编写单元测试和集成测试。
  • 宏 (Macros): 编写能生成代码的代码。
  • 常用库和框架: 根据你的兴趣方向选择,如 tokio (异步运行时), actix-web/rocket (Web 框架), serde (序列化/反序列化) 等。

推荐学习资源:

  1. 《The Rust Programming Language》 (官方书/TRPL): 最权威、最全面的 Rust 教程。在线免费阅读:https://doc.rust-lang.org/book/
  2. Rustlings: 通过修复一系列小程序来学习 Rust 语法的练习项目:https://github.com/rust-lang/rustlings
  3. Exercism Rust Track: 通过解决编程练习并获得社区反馈来学习:https://exercism.org/tracks/rust
  4. Rust By Example: 通过大量可运行的示例代码学习:https://doc.rust-lang.org/rust-by-example/
  5. Rust 官方文档: https://doc.rust-lang.org/

十三、 结语

Rust 是一门设计精良、功能强大且富有挑战性的语言。它的学习曲线可能比一些动态语言陡峭,特别是所有权和生命周期的概念需要时间消化。但请保持耐心和实践,一旦你掌握了这些核心概念,就能体会到 Rust 带来的“无畏编程”的快感——编译器会成为你最得力的助手,帮助你构建出既安全又高效的软件。

不要害怕犯错,积极查阅文档,参与社区讨论,动手编写代码是最好的学习方式。祝你在 Rust 的学习旅程中一帆风顺,收获满满!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部