Rust 从零开始学:基础语法与实践入门(详细版)
Rust 是一门赋予每个人构建可靠且高效软件能力的系统编程语言。它由 Mozilla 主导开发,并以其内存安全、并发性和性能三大优势在开发者社区中迅速崛起。如果你厌倦了 C/C++ 带来的悬垂指针和内存泄漏风险,又或者觉得 Python/Java 等语言在性能上有所欠缺,那么 Rust 可能是你理想的选择。
本文旨在为完全没有 Rust 基础的初学者提供一份详尽的入门指南,我们将从 Rust 的核心理念出发,逐步深入基础语法,并通过实践来巩固学习成果。准备好了吗?让我们一起踏上 Rust 的学习之旅!
一、 为什么选择 Rust?
在深入语法之前,我们先理解一下 Rust 为何如此特别,以及学习它能带来什么好处:
- 性能卓越:Rust 编译为本地机器码,其性能与 C/C++ 相当。它没有运行时(Runtime)或垃圾回收器(Garbage Collector, GC)的开销,使得程序运行速度极快,内存占用低。这使其非常适合性能敏感的场景,如游戏引擎、操作系统、浏览器组件、嵌入式设备等。
- 内存安全:这是 Rust 最核心的特性。通过其创新的所有权(Ownership)系统、借用(Borrowing)检查器和生命周期(Lifetimes),Rust 能够在编译时就消除绝大多数内存安全问题(如空指针解引用、悬垂指针、数据竞争等),而无需 GC。这意味着你可以在享受高性能的同时,获得类似 Java 或 Python 的内存安全保证。
- 并发安全:Rust 的所有权和类型系统也延伸到了并发编程领域。它能在编译时防止数据竞争(Data Races),让编写并发代码变得更加“无畏”(Fearless Concurrency)。你不再需要小心翼翼地处理锁和线程同步,编译器会帮你检查很多潜在问题。
- 现代化工具链:Rust 拥有极其出色的构建系统和包管理器——Cargo。Cargo 负责处理项目构建、依赖管理、测试、文档生成等一系列任务,极大地简化了开发流程。它的体验远超许多传统语言的工具链。
- 强大的类型系统和模式匹配:Rust 拥有丰富的类型系统,支持泛型、Trait(类似于接口或抽象类)等特性,有助于编写抽象且可复用的代码。其
enum
和match
表达式提供了强大的模式匹配能力,让处理复杂状态和数据结构变得优雅而安全。 - 活跃的社区和生态:Rust 拥有一个充满活力、乐于助人的社区。生态系统(通过 Crates.io 分发)正在快速发展,涵盖了 Web 开发(Actix, Rocket)、网络编程(Tokio)、数据科学、游戏开发(Bevy)等多个领域。
二、 准备工作:安装 Rust 环境
Rust 的安装过程非常简单。官方推荐使用 rustup
,这是一个 Rust 版本管理工具。
- 访问官网:打开 Rust 官方网站 https://www.rust-lang.org/,点击 “Get Started”。
- 根据操作系统指示安装:
- 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(如果尚未安装)。
- Linux / macOS: 打开终端,运行命令:
- 验证安装:安装完成后,在终端或命令提示符中运行以下命令,检查 Rust 编译器 (
rustc
) 和 Cargo (cargo
) 是否安装成功:
bash
rustc --version
cargo --version
如果能看到版本号输出,说明安装成功。
rustup
还可以方便地更新 Rust 版本 (rustup update
)、安装不同通道的版本(stable, beta, nightly)以及管理工具链(如 rustfmt
代码格式化工具、clippy
代码 Lint 工具)。
三、 第一个 Rust 程序:Hello, World!
学习任何语言的传统都是从 “Hello, World!” 开始。
- 创建项目目录:在你喜欢的位置创建一个文件夹,例如
rust_projects
。 - 创建源文件:在
rust_projects
目录下创建一个名为main.rs
的文件(.rs
是 Rust 源文件的扩展名)。 -
编写代码:在
main.rs
文件中输入以下代码:rust
// main.rs
fn main() {
println!("Hello, world!");
}fn main()
: 这是程序的入口点。fn
关键字用于声明一个函数,main
是这个特殊函数的名字。{ ... }
: 大括号包围了函数体。println!("Hello, world!");
: 这行代码做了两件事:println!
是一个 宏(Macro),而不是普通函数(注意末尾的!
)。宏是 Rust 元编程的一部分,可以在编译时生成代码。println!
用于将文本输出到控制台(标准输出)。"Hello, world!"
是一个字符串字面量,作为参数传递给println!
宏。- 语句以分号
;
结尾。
-
编译和运行:
- 打开终端或命令提示符,切换到包含
main.rs
的目录。 - 使用 Rust 编译器
rustc
编译代码:
bash
rustc main.rs
这会生成一个可执行文件(在 Linux/macOS 上通常是main
,在 Windows 上是main.exe
)。 - 运行可执行文件:
- Linux/macOS:
./main
- Windows:
.\main.exe
- Linux/macOS:
- 你应该能在终端看到输出:
Hello, world!
- 打开终端或命令提示符,切换到包含
四、 Cargo:Rust 的构建系统与包管理器
手动使用 rustc
编译单个文件还可以,但对于复杂的项目,我们需要更强大的工具。这就是 Cargo 发挥作用的地方。
-
创建 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.rsCargo.toml
: 这是 Cargo 的清单文件,采用 TOML 格式。它包含了项目的元数据(名称、版本、作者等)和依赖项列表。src/main.rs
: Cargo 默认将源代码放在src
目录下,并且入口文件仍然是main.rs
。它已经为你生成了 “Hello, world!” 的代码。
-
构建项目:在
hello_cargo
目录下,运行:
bash
cargo build
Cargo 会编译你的项目及其所有依赖(目前还没有),并将可执行文件放在target/debug/
目录下(例如target/debug/hello_cargo
)。 -
运行项目:你可以直接运行:
bash
cargo run
Cargo 会先编译(如果需要),然后运行生成的可执行文件。你应该再次看到Hello, world!
输出。 -
检查项目:运行:
bash
cargo check
这个命令会快速检查代码是否能通过编译,但不生成可执行文件,速度比cargo build
快,适合在开发过程中频繁检查。 -
发布构建:当你准备好发布优化过的版本时,运行:
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
)。isize
和usize
的大小取决于目标机器的架构(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
类型,只有两个可能的值:true
和false
。
rust
let is_rust_fun: bool = true; - 字符 (Characters):
char
类型,表示单个 Unicode 标量值,用单引号'
包裹。占用 4 个字节。
rust
let initial = 'R';
let emoji = '🚀';
- 整数 (Integers): 有符号 (
-
复合类型 (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.4let 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]
``
Vec
注意:如果你需要一个可变大小的集合,应该使用**向量 (Vector)**`,它属于标准库的集合类型,我们稍后会简单提及。
-
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)**:
rust
* **语句**是执行某些操作但不返回值的指令,以分号结尾。例如 `let x = 5;`。
* **表达式**会计算并产生一个值。例如 `5 + 6`, `add_one(10)`, 甚至 `{ ... }` 代码块也可以是表达式。
fn main() {
let y = {
let x = 3;
x + 1 // 这个块的值是 x + 1 的结果,即 4
}; // 注意这里没有分号,因为整个 { … } 是一个表达式,它的值赋给了 yprintln!("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 提供了三种循环结构。
rust
* **`loop`**: 无限循环,需要使用 `break` 来退出。可以从 `break` 返回值。
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 能够在没有垃圾回收器的情况下保证内存安全。
核心规则:
- 每个值都有一个变量,称为其所有者(Owner)。
- 一个值在同一时间只能有一个所有者。
- 当所有者离开作用域(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 moveprintln!(“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 的值被拷贝给 yprintln!(“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
// 使用 match 处理 Option
fn plus_one(x: 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
非常强大,可以用于任何类型,并支持复杂的模式匹配,如匹配范围、解构嵌套结构等。
十一、 基础实践:一个简单的猜数游戏
让我们结合所学知识,编写一个简单的命令行猜数游戏。
- 创建新项目:
bash
cargo new guessing_game
cd guessing_game -
添加依赖: 我们需要从标准输入读取用户输入,并生成随机数。生成随机数需要用到外部库(称为 Crate)。打开
Cargo.toml
文件,在[dependencies]
部分添加rand
crate:
“`toml
[package]
name = “guessing_game”
version = “0.1.0”
edition = “2021”[dependencies]
rand = “0.8” # 使用一个较新的稳定版本
``
src/main.rs`)**:
3. **编写代码 (“`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 循环 } } }
}
“` -
运行游戏:
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
(序列化/反序列化) 等。
推荐学习资源:
- 《The Rust Programming Language》 (官方书/TRPL): 最权威、最全面的 Rust 教程。在线免费阅读:https://doc.rust-lang.org/book/
- Rustlings: 通过修复一系列小程序来学习 Rust 语法的练习项目:https://github.com/rust-lang/rustlings
- Exercism Rust Track: 通过解决编程练习并获得社区反馈来学习:https://exercism.org/tracks/rust
- Rust By Example: 通过大量可运行的示例代码学习:https://doc.rust-lang.org/rust-by-example/
- Rust 官方文档: https://doc.rust-lang.org/
十三、 结语
Rust 是一门设计精良、功能强大且富有挑战性的语言。它的学习曲线可能比一些动态语言陡峭,特别是所有权和生命周期的概念需要时间消化。但请保持耐心和实践,一旦你掌握了这些核心概念,就能体会到 Rust 带来的“无畏编程”的快感——编译器会成为你最得力的助手,帮助你构建出既安全又高效的软件。
不要害怕犯错,积极查阅文档,参与社区讨论,动手编写代码是最好的学习方式。祝你在 Rust 的学习旅程中一帆风顺,收获满满!