Rust 是一门着重于安全 (Safety)性能 (Performance)并发 (Concurrency) 的现代系统编程语言。它旨在解决 C/C++ 等传统系统语言中常见的内存安全问题,同时又保持了零开销抽象和裸机控制的能力。Rust 通过其独特的所有权 (Ownership) 系统、借用 (Borrowing) 和生命周期 (Lifetimes) 规则,在编译时强制执行内存安全,无需垃圾回收器,从而避免了数据竞争和空指针解引用等常见错误。

核心思想:在保证与 C/C++ 匹敌性能的同时,通过严格的编译时检查(所有权系统)来消除内存安全漏洞和数据竞争,使开发者可以专注于业务逻辑而非底层内存管理。


一、变量和可变性 (Variables and Mutability)

Rust 的变量绑定默认是不可变的,这鼓励开发者编写更安全、更易于理解的代码。

1.1 let 绑定

使用 let 关键字声明的变量默认是不可变的 (immutable)。一旦绑定了一个值,就不能再改变它。

1
2
3
4
5
fn main() {
let x = 5; // x 是不可变的
println!("The value of x is: {}", x);
// x = 6; // 这会导致编译错误:cannot assign twice to immutable variable `x`
}

1.2 mut 关键字

如果需要使变量可变,可以使用 mut 关键字。

1
2
3
4
5
6
fn main() {
let mut x = 5; // x 是可变的
println!("The value of x is: {}", x);
x = 6; // 允许修改
println!("The value of x is: {}", x);
}

1.3 常量 (Constants)

常量使用 const 关键字声明,并且必须显式地指定类型。常量在程序运行的整个生命周期中都有效,可以声明在任何作用域,包括全局作用域。常量不能使用 mut

1
const MAX_POINTS: u32 = 100_000; // 常量名通常采用全大写和下划线

constlet 的主要区别:

  • const 必须显式注明类型,let 可以根据值推断类型。
  • const 不能使用 mut
  • const 可以在任意作用域声明,包括全局。
  • const 只能绑定到常量表达式,不能是函数调用的结果或任何在运行时计算的值。

1.4 遮蔽 (Shadowing)

Rust 允许使用相同名称声明新变量,这会“遮蔽” (shadow) 掉前一个同名变量。这意味着在当前作用域内,新变量将覆盖旧变量。遮蔽与可变性不同,它可以改变变量的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
fn main() {
let x = 5; // x 是整数

let x = x + 1; // 第一次遮蔽,x 现在是 6

{
let x = x * 2; // 在内部作用域第二次遮蔽,x 现在是 12
println!("The value of x in the inner scope is: {}", x);
} // 内部作用域结束,内部的 x 失效

println!("The value of x in the outer scope is: {}", x); // 外部的 x 仍然是 6
}```

## 二、数据类型(标量与复合)

Rust 是一种静态类型语言,它在编译时知道所有变量的类型。

### 2.1 标量类型 (Scalar Types)

标量类型代表一个单一的值:

* **整数 (Integers)**:
* 有符号整数 (signed integers, `i` 前缀):`i8`, `i16`, `i32` (默认), `i64`, `i128`, `isize` (与 CPU 架构的指针大小相同)。
* 无符号整数 (unsigned integers, `u` 前缀):`u8`, `u16`, `u32` (默认), `u64`, `u128`, `usize` (与 CPU 架构的指针大小相同)。
* 可以添加类型后缀 (如 `57u8`) 或使用可视分隔符 (如 `1_000`)。
* **浮点数 (Floating-Point Numbers)**:
* `f32` (单精度)
* `f64` (双精度,默认)
* **布尔值 (Booleans)**:`bool` 类型,只有 `true` 和 `false` 两个值。
* **字符 (Characters)**:`char` 类型,表示 Unicode 标量值,用单引号 `' '` 包裹。

```rust
fn main() {
let _guess: u32 = "42".parse().expect("Not a number!"); // 显式类型注解
let x = 2.0; // f64 默认
let y: f32 = 3.0; // f32
let t = true; // bool
let f: bool = false; // bool
let c = 'z'; // char
let z = 'ℤ'; // char (Unicode)
let heart_eyed_cat = '😻'; // char (Unicode emoji)
}

2.2 复合类型 (Compound Types)

复合类型可以将多个值组合成一个类型:

  • 元组 (Tuples)

    • 元组可以将多种不同类型的值组合在一个固定大小的集合中。
    • 一旦声明,元组的长度是固定的。
    • 可以通过模式匹配或者索引来访问元组的元素。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
    let (x, y, z) = tup; // 模式匹配解构
    println!("The value of y is: {}", y); // 输出 6.4

    let five_hundred = tup.0; // 通过索引访问
    let six_point_four = tup.1;
    let one = tup.2;
    }
  • 数组 (Arrays)

    • 数组可以将多个相同类型的值组合在一个固定大小的集合中。
    • 数组的长度在编译时是已知的,一旦声明,长度不可改变。
    • 如果需要可变长的列表,应使用 Vec (向量),这是一种标准库集合类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    fn main() {
    let a = [1, 2, 3, 4, 5]; // 隐式类型推断和长度
    let months: [&str; 12] = [ // 显式类型 [类型; 长度]
    "January", "February", "March", "April", "May", "June", "July",
    "August", "September", "October", "November", "December"
    ];
    let b = [3; 5]; // 等同于 [3, 3, 3, 3, 3]

    let first = a[0]; // 通过索引访问
    let second = a[1];

    // 访问越界会发生运行时错误并 panic
    // let index = 10;
    // let element = a[index]; // 运行时 panic: index out of bounds
    }

三、函数与作用域 (Functions and Scope)

Rust 代码通常通过函数组织。函数也是作用域的边界。

3.1 函数定义

使用 fn 关键字定义函数。函数可以接收参数,也可以返回一个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
fn main() {
println!("Hello, world!");
another_function();
say_hello_to("Alice");
print_labeled_measurement(5, 'h');
let result = five();
println!("The value of five is: {}", result);
let x = plus_one(5);
println!("The value of x is: {}", x);
}

// 无参数无返回值函数
fn another_function() {
println!("Another function.");
}

// 接收一个参数的函数,参数必须声明类型
fn say_hello_to(name: &str) {
println!("Hello, {}!", name);
}

// 接收多个参数的函数
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {}{}", value, unit_label);
}

// 返回值的函数,返回值类型在参数列表后用 `->` 指定
fn five() -> i32 {
5 // 这是一个表达式,隐式返回。注意没有分号。
}

fn plus_one(x: i32) -> i32 {
x + 1 // 表达式,隐式返回。
// x + 1; // 如果加了分号,会变成一个语句,不返回值,导致编译错误
}

3.2 表达式与语句 (Expressions vs Statements)

  • 语句 (Statements):执行一个动作,但不返回值。例如 let y = 6;
  • 表达式 (Expressions):求值并产生一个值。例如 5 + 6,函数调用,以及代码块。

在 Rust 中,函数体由一系列语句组成,并可选地以一个表达式结尾。表达式末尾没有分号,加分号会将其转换为语句,使其不再返回值。

1
2
3
4
5
6
7
fn main() {
let y = {
let x = 3;
x + 1 // 这是一个表达式,它的值是 4,赋值给 y
};
println!("The value of y is: {}", y);
}

3.3 作用域 (Scope)

Rust 拥有块级作用域。变量在声明它们的代码块内是有效的,并在块结束时超出作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
fn main() {
let s = "hello"; // s 在这里生效

{ // 新的作用域开始
let t = "world"; // t 在这里生效
println!("Inside scope: {} {}", s, t);
} // 新的作用域结束,t 在这里失效

// println!("Outside scope: {} {}", s, t); // 编译错误:cannot find value `t` in this scope
println!("Outside scope: {}", s); // s 仍然有效
}```

## 四、流程控制 (Control Flow)

### 4.1 `if/else if/else` 表达式

`if` 表达式用于根据条件执行不同的代码块。在 Rust 中,`if` 不仅可以作为语句,也可以作为表达式返回值。

```rust
fn main() {
let number = 7;

if number < 5 { // if 条件不需要括号
println!("condition was true");
} else {
println!("condition was false");
}

let condition = true;
let number = if condition { 5 } else { 6 }; // if 作为一个表达式返回值
println!("The value of number is: {}", number);

// if/else if/else
let num = 6;
if num % 4 == 0 {
println!("number is divisible by 4");
} else if num % 3 == 0 {
println!("number is divisible by 3");
} else if num % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2"); // 这里的 num=6 会输出 "number is divisible by 3"
}
}

注意if 表达式的每个分支返回的类型必须相同。

4.2 loop 循环

loop 关键字创建一个无限循环。可以使用 break 关键字退出循环,或者使用 continue 跳过当前迭代。loop 也可以返回一个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break 后可以跟一个表达式,作为 loop 表达式的返回值
}
};
println!("The result is {}", result); // 输出 20
}```

### 4.3 `while` 循环

`while` 循环当条件为 `true` 时重复执行代码块。

```rust
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}

4.4 for 循环

for 循环用于遍历集合中的元素。它是 Rust 中最常用的循环结构,安全且简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let a = [10, 20, 30, 40, 50];

for element in a.iter() { // 遍历数组或集合的元素
println!("the value is: {}", element);
}

// 使用范围 (range) 遍历
for number in (1..4).rev() { // (1..4) 是 [1, 2, 3],.rev() 反转迭代器
println!("{}!", number); // 输出 3! 2! 1!
}
println!("LIFTOFF!!!");
}

五、所有权与借用 (Ownership and Borrowing)

所有权是 Rust 最独特且最重要的特性,它使得 Rust 无需垃圾回收器即可保证内存安全。

5.1 所有权规则

  1. 每个值都有一个所有者 (owner)
  2. 值在任何时候只能有一个所有者
  3. 当所有者离开作用域时,值会被丢弃 (drop)

5.2 所有权转移 (Move 语义)

当一个值被赋值给另一个变量或作为参数传递给函数时,所有权会发生转移。

对于栈上数据(如整数、布尔值、固定大小数组),因为 Copy Trait 的实现,它们会被复制而不是移动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fn main() {
let s1 = String::from("hello"); // s1 拥有 "hello" 这个 String 数据
let s2 = s1; // s1 的所有权移动到 s2。s1 不再有效。

// println!("s1: {}", s1); // 编译错误:value borrowed here after move

let x = 5; // x 是栈上数据,实现了 Copy Trait
let y = x; // x 的值被复制到 y。x 仍然有效。
println!("x: {}, y: {}", x, y); // 输出 x: 5, y: 5

takes_ownership(s2); // s2 的所有权移动到函数 takes_ownership
// println!("s2: {}", s2); // 编译错误:value borrowed here after move

makes_copy(x); // x 的值被复制到函数 makes_copy
println!("x: {}", x); // x 仍然有效
} // s1, s2, x, y 在 main 作用域结束时被 drop

fn takes_ownership(some_string: String) { // some_string 进入作用域
println!("{}", some_string);
} // some_string 离开作用域,调用 drop

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{}", some_integer);
} // some_integer 离开作用域

5.3 克隆 (Cloning)

如果希望复制堆上的数据而不是转移所有权,可以使用 clone 方法。

1
2
3
4
5
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // s1 和 s2 现在都拥有独立的数据
println!("s1 = {}, s2 = {}", s1, s2); // s1 和 s2 都有效
}

六、引用与切片 (References and Slices)

6.1 引用 (References)

& 符号表示引用,它允许你在不获取所有权的情况下使用值。这种行为称为借用 (borrowing)。引用本身是不可变的,但它们指向的值可以是不可变或可变的。

借用规则 (Borrowing Rules)
在任何给定时间,你只能拥有:

  1. 一个可变引用 (mutable reference),或者
  2. 任意数量的不可变引用 (immutable references)。
    引用必须始终是有效的。
  • 不可变引用 &T

    • 可以通过 & 创建。
    • 允许读取数据,但不允许修改数据。
    • 可以有多个不可变引用同时存在。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s); // 传递不可变引用
    println!("The length of '{}' is {}.", s, len); // s 仍然有效
    }

    fn calculate_length(s: &String) -> usize { // s 是对一个 String 的引用
    s.len()
    } // s 离开作用域,但它没有所有权,所以不会 drop s
  • 可变引用 &mut T

    • 可以通过 &mut 创建。
    • 允许读取和修改数据。
    • 在特定作用域内,只能有一个可变引用存在。不能与任何其他引用(不可变或可变)同时存在。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    fn main() {
    let mut s = String::from("hello");
    change(&mut s); // 传递可变引用
    println!("{}", s); // 输出 "hello, world"

    let r1 = &mut s;
    // let r2 = &mut s; // 编译错误:cannot borrow `s` as mutable more than once at a time
    // println!("{}, {}", r1, r2); // 错误发生在引用使用时
    }

    fn change(some_string: &mut String) { // some_string 是对一个可变 String 的引用
    some_string.push_str(", world");
    }
  • 悬垂引用 (Dangling References)

    • Rust 编译器会在编译时防止悬垂引用,即指向已被释放内存的引用。
    1
    2
    3
    4
    // fn dangle() -> &String { // 编译错误:this function's return type contains a borrowed value, but there is no value for it to borrow
    // let s = String::from("hello"); // s 在 dangle 内部创建
    // &s // 返回 s 的引用
    // } // s 在这里超出作用域并被 drop,其内存被释放。但是我们试图返回一个指向该内存的引用。

    正确做法是返回 String 本身(转移所有权)。

6.2 切片 (Slices)

切片是对集合中一部分连续元素的引用,它在不获取所有权的情况下引用数据。

  • 字符串切片 &str

    • 表示对 String 的一部分的引用。
    • &str 是不可变的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    fn main() {
    let s = String::from("hello world");
    let hello = &s[0..5]; // 从索引 0 开始到(不包含)索引 5
    let world = &s[6..11];
    println!("{} {}", hello, world);

    // 获取整个字符串的切片
    let whole_slice = &s[..];
    println!("{}", whole_slice);

    // 字面量字符串是 &str 类型
    let s_literal = "hello world"; // s_literal 的类型是 &str
    }
  • 数组切片 &[T]

    • 对数组或其他集合中连续元素的引用。
    1
    2
    3
    4
    5
    fn main() {
    let a = [1, 2, 3, 4, 5];
    let slice = &a[1..3]; // slice 的类型是 &[i32]
    assert_eq!(slice, &[2, 3]);
    }

切片让我们可以安全高效地操作集合的一部分。

七、结构体 (Struct) 和方法 (Method)

结构体是一种自定义复合数据类型,允许我们将相关联的数据打包成一个有意义的结构。

7.1 定义和实例化结构体

结构体是用户自定义的类型。可以定义带命名字段的结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}

fn main() {
let user1 = User { // 实例化结构体
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};

println!("User email: {}", user1.email);

let mut user2 = User { // 将实例声明为可变以修改其字段
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: false,
sign_in_count: 1,
};
user2.email = String::from("newemail@example.com"); // 修改字段

// 结构体更新语法 (Struct Update Syntax)
let user3 = User {
email: String::from("third@example.com"),
username: String::from("user3"),
..user1 // 其余字段从 user1 中获取
};
}

// 示例函数:返回一个 User 实例
fn build_user(email: String, username: String) -> User {
User {
email, // 字段初始化简写语法:参数名与字段名相同
username,
active: true,
sign_in_count: 1,
}
}

7.2 元组结构体 (Tuple Structs)

元组结构体是具有元组特点的结构体,但有名称。

1
2
3
4
5
6
7
8
struct Color(i32, i32, i32); // 具名元组
struct Point(i32, i32, i32);

fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// black 和 origin 是不同类型,即使它们的内部字段类型相同
}

7.3 单元结构体 (Unit-Like Structs)

单元结构体不包含任何字段,通常用于需要实现某个 Trait 但不需要存储任何数据的场景。

1
2
3
4
5
6
struct AlwaysEqual;

fn main() {
let subject = AlwaysEqual;
// ...
}

7.4 方法 (Methods)

方法是与结构体(或枚举)关联的函数,并且其第一个参数必须是 self&self&mut self

使用 impl 块来定义方法。

  • &self:方法借用所有者(不可变)。
  • &mut self:方法可变地借用所有者。
  • self:方法获取所有权,所有者在方法结束时被 Drop。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
// 方法:计算矩形面积 (借用不可变 self)
fn area(&self) -> u32 {
self.width * self.height
}

// 方法:检查矩形能否包含另一个矩形 (借用不可变 self 和另一个矩形)
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}

// 关联函数 (Associated Function):不接收 self 参数,类似于静态方法
// 通常用作构造函数
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}

fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle { width: 60, height: 45 };

println!(
"The area of the rectangle is {} square pixels.",
rect1.area() // 使用点运算符调用方法
);

println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));

let sq = Rectangle::square(25); // 调用关联函数
println!("The area of the square is {}", sq.area());
}

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

8.1 枚举 (Enums)

枚举允许你通过定义一个类型,使其拥有一组可能的值。Rust 的枚举是强大的,它们可以存储数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
enum IpAddrKind {
V4,
V6,
}

// 枚举可以包含关联数据
enum IpAddr {
V4(String), // 可以存储 String 类型
V6(String),
}

// 甚至可以存储不同类型和数量的数据
enum Message {
Quit,
Move { x: i32, y: i32 }, // 包含一个匿名结构体
Write(String),
ChangeColor(i32, i32, i32), // 包含一个元组
}

impl Message {
fn call(&self) {
// 在这里定义方法
match self {
Message::Quit => println!("Quitting."),
Message::Move { x, y } => println!("Moving to x={}, y={}", x, y),
_ => println!("Other message."), // _ 匹配剩下的所有情况
}
}
}

fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

let msg = Message::Write(String::from("hello"));
msg.call();
let move_msg = Message::Move { x: 10, y: 20 };
move_msg.call();
}

Option 枚举
Rust 标准库中定义了一个非常有用的枚举 Option<T>,用于处理值可能存在或不存在的场景,避免了空指针的危险。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
enum Option<T> { // 由标准库定义
None, // 没有值
Some(T), // 有一个值,类型为 T
}

fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None; // 必须指定类型,因为无法推断 None 的 T

// 无法直接使用 Option<T> 中的值
// let x: i32 = some_number; // 编译错误!
let x: Option<i32> = Some(5);
let y: i32 = 5;
// let sum = x + y; // 编译错误!
}```

### 8.2 模式匹配 (Pattern Matching)

`match` 表达式允许你将一个值与一系列模式进行比较,并根据匹配的模式执行相应的代码。`match` 表达式是穷尽的,必须覆盖所有可能性。

```rust
fn main() {
let coin = Coin::Quarter(UsState::Alabama);
let price = value_in_cents(coin);
println!("Price is {}", price);

let maybe_five = Some(5);
let five = plus_one(maybe_five);
let none = plus_one(None);
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // 关联值
}

#[derive(Debug)] // 允许打印 State
enum UsState {
Alabama,
Alaska,
// ...
}

fn value_in_cents(coin: Coin) -> u8 {
match coin { // 匹配 coin 的值
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => { // 匹配关联值
println!("State quarter from {:?}!", state);
25
},
}
}

// 匹配 Option<i32>
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1), // 匹配并解构 Some 中的值
}
}

if let 语法
if let 是一种处理 match 表达式只关心一个匹配模式的简写方式。

1
2
3
4
5
6
7
8
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max { // 如果 config_max 匹配 Some(max),则执行代码块
println!("The maximum is configured to be {}", max);
} else {
println!("No maximum configured.");
}
}

九、常见集合类型及常用操作

Rust 标准库提供了几种常用的集合类型来存储数据。与内置数组不同,这些集合存储在堆上,并且可以根据需要增长或缩小。

9.1 Vec<T> (向量)

Vec<T> 是一个可变长的同类型列表。
它类似于其他语言中的动态数组或 ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
fn main() {
// 创建空 Vec
let mut v: Vec<i32> = Vec::new(); // 显式类型
v.push(5);
v.push(6);
v.push(7);
v.push(8);

// 使用宏创建 Vec,并自动推断类型
let v2 = vec![1, 2, 3];

// 访问元素
let third: &i32 = &v[2]; // 通过索引访问,可能 panic
println!("The third element is {}", third);

match v.get(2) { // 使用 get 方法安全访问,返回 Option<&T>
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}

// 遍历 Vec
for i in &v { // 遍历不可变引用
println!("{}", i);
}

for i in &mut v { // 遍历可变引用并修改
*i += 50; // 解引用修改
}
println!("{:?}", v); // 输出 [55, 56, 57, 58]
}

9.2 String

String 是一个可变的、堆分配的、UTF-8 编码的字符串类型。它是 Vec<u8> 的一个封装,并保证存储的是有效的 UTF-8 序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fn main() {
let mut s1 = String::new(); // 创建空 String
s1.push_str("hello"); // 追加字符串切片
s1.push(','); // 追加字符
s1.push_str(" world");
s1.push('!');
println!("{}", s1); // 输出 "hello, world!"

let s2 = String::from("Rust"); // 从字符串字面量创建
let s3 = " programming".to_string(); // 从 &str 转换为 String

// 字符串拼接
let s4 = s1 + &s2; // s1 被“移动”到 s4,s1 不再有效
println!("{}", s4);
// println!("{}", s1); // 编译错误:value borrowed here after move

let s5 = format!("{}-{}-{}", s2, s3, "language"); // format! 宏不会获取所有权
println!("{}", s5);

// 遍历字符 (UTF-8 字符)
for c in "你好Rust".chars() {
println!("{}", c);
}

// 遍历字节
for b in "你好Rust".bytes() {
println!("{}", b);
}
}

注意String 不支持像 C 语言那样直接通过索引访问字符,因为 UTF-8 编码的字符长度不固定,索引操作不明确。如果需要,应使用 chars() 迭代器。

9.3 HashMap<K, V> (哈希映射)

HashMap<K, V> 存储键值对,键和值可以是不同类型。它通过哈希函数将键映射到内存位置,实现快速查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use std::collections::HashMap;

fn main() {
let mut scores = HashMap::new(); // 创建空 HashMap

scores.insert(String::from("Blue"), 10); // 插入键值对
scores.insert(String::from("Yellow"), 50);

// 使用 collect 方法从元组 Vec 创建 HashMap (需要指定类型)
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores_collected: HashMap<_, _> =
teams.into_iter().zip(initial_scores.into_iter()).collect();

// 获取值
let team_name = String::from("Blue");
let score = scores.get(&team_name); // get 返回 Option<&V>

match score {
Some(s) => println!("Blue team score: {}", s),
None => println!("Blue team not found."),
}

// 遍历 HashMap
for (key, value) in &scores {
println!("{}: {}", key, value);
}

// 插入条件(仅当键不存在时)
scores.entry(String::from("Green")).or_insert(30);
scores.entry(String::from("Blue")).or_insert(50); // "Blue" 已存在,不会修改

println!("{:?}", scores); // 输出 HashMap {"Blue": 10, "Yellow": 50, "Green": 30}
}

注意:对于 HashMap,键和值的所有权会被移动到 HashMap 中。如果想要使用引用,必须确保引用是有效的。

十、模块系统和包管理 (Module System and Package Management)

Rust 的模块系统和包管理工具 Cargo 紧密相关,它们共同协作帮助组织和管理 Rust 项目。

10.1 模块 (Modules)

模块允许你将代码组织成组,提高可读性和重用性。模块可以嵌套。

  • mod 关键字:用于定义模块。
  • pub 关键字:使项(函数、结构体、枚举、模块等)对外可见。默认情况下,所有项都是私有的。
  • use 关键字:将模块中的路径引入当前作用域,以便更方便地使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// main.rs
mod front_of_house { // 定义一个模块
pub mod hosting { // 定义一个公共子模块
pub fn add_to_waitlist() { // 定义一个公共函数
println!("Adding to waitlist!");
}

fn seat_at_table() { // 私有函数
println!("Seating at table!");
}
}

mod serving { // 私有子模块
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}

// 引入路径到当前作用域
use front_of_house::hosting;
// 或者更具体的引入函数
// use front_of_house::hosting::add_to_waitlist;

fn main() {
// 绝对路径
// crate::front_of_house::hosting::add_to_waitlist();

// 相对路径
// front_of_house::hosting::add_to_waitlist();

// 使用 use 引入后可以直接调用
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}

10.2 包 (Packages) 和 Crate

  • Crate (包):Rust 编译器的编译单元。
    • 二进制 Crate (Binary Crate):编译生成可执行文件(如 main.rs)。
    • 库 Crate (Library Crate):编译生成库文件(如 lib.rs)。
  • Package (包):包含一个或多个 Crate(最多一个库 Crate,任意数量的二进制 Crate)以及一个 Cargo.toml 文件,描述如何构建这些 Crate。

10.3 Cargo

Cargo 是 Rust 的构建系统和包管理器。它负责:

  • 创建项目cargo new <project_name>
  • 构建项目cargo build
  • 运行项目cargo run
  • 测试项目cargo test
  • 管理依赖:在 Cargo.toml 中声明依赖,Cargo 会自动下载和编译。

Cargo.toml 文件示例:

1
2
3
4
5
6
7
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]
rand = "0.8.5" # 声明外部依赖

十一、错误处理 (Error Handling)

Rust 对错误处理采取了严格的态度,区分了可恢复错误不可恢复错误

11.1 可恢复错误 (Recoverable Errors) - Result<T, E>

可恢复错误(如文件未找到、网络连接中断)是预料之中且可以处理的。Rust 使用 Result<T, E> 枚举来处理这类错误。

1
2
3
4
enum Result<T, E> {
Ok(T), // 操作成功,返回 T 类型的值
Err(E), // 操作失败,返回 E 类型的错误
}

处理 Result 的常用方法:

  • match 表达式:最彻底和灵活的方式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    use std::{fs::File, io::ErrorKind};

    fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
    Ok(file) => file,
    Err(error) => match error.kind() {
    ErrorKind::NotFound => match File::create("hello.txt") {
    Ok(fc) => fc,
    Err(e) => panic!("Problem creating the file: {:?}", e),
    },
    other_error => {
    panic!("Problem opening the file: {:?}", other_error);
    }
    },
    };
    // greeting_file 是一个 File 类型
    }
  • unwrap()expect():简写方式,如果 ResultErr 则直接 panic!expect() 允许提供自定义 panic 消息。它们不应用于生产代码中的可恢复错误。

    1
    2
    3
    // let greeting_file = File::open("hello.txt").unwrap(); // 如果是 Err 会直接 panic
    // let greeting_file = File::open("hello.txt")
    // .expect("hello.txt should be included in this project"); // 带消息的 panic
  • ? 运算符:用于传播错误,是 match 表达式的语法糖。只能在返回 Result 的函数中使用。如果 ResultErr,它会立即从当前函数返回 Err;否则,它会解包 Ok 中的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    use std::{fs::File, io::{self, Read}};

    fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?; // 如果 File::open 返回 Err,则此函数返回 Err
    let mut s = String::new();
    f.read_to_string(&mut s)?; // 如果 read_to_string 返回 Err,则此函数返回 Err
    Ok(s) // 成功则返回 Ok(s)
    }

    // 更简洁的写法 (链式调用)
    fn read_username_from_file_chaining() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
    }

11.2 不可恢复错误 (Unrecoverable Errors) - panic!

不可恢复错误(如数组越界、逻辑错误)表示程序处于无法挽回的错误状态,通常需要终止程序。Rust 使用 panic! 宏来处理这类错误。

panic! 发生时,程序会默认展开 (unwind) 栈并清理数据,然后退出。也可以配置为直接终止 (abort) 进程,这会使二进制文件更小。

1
2
3
4
5
6
fn main() {
// panic!("crash and burn"); // 调用 panic! 宏,程序会终止

let v = vec![1, 2, 3];
// v[99]; // 数组越界访问,也会导致 panic!
}

十二、泛型 (Generics)

泛型允许你编写更抽象、可重用的代码,它可以在编译时适用于多种类型。

12.1 函数中的泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T { // T 必须实现 PartialOrd 和 Copy Trait
let mut largest = list[0];
for &item in list.iter() {
if item > largest { // 需要 PartialOrd Trait 来比较
largest = item;
}
}
largest
}

fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);

let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}

12.2 结构体中的泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct Point<T> { // 结构体字段可以使用泛型类型
x: T,
y: T,
}

struct PointMixed<T, U> { // 多个泛型类型参数
x: T,
y: U,
}

impl<T> Point<T> { // 在 impl 块中为 Point<T> 定义方法
fn x(&self) -> &T {
&self.x
}
}

// 只有当 T 类型为 f32 时,Point 才会有 distance_from_origin 方法
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}


fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
// let wont_work = Point { x: 5, y: 4.0 }; // 编译错误!x 和 y 必须是同类型 T

let mixed = PointMixed { x: 5, y: 4.0 };

println!("integer.x = {}", integer.x());
// println!("float distance from origin: {}", float.distance_from_origin()); // 可以调用
}

12.3 枚举中的泛型

Option<T>Result<T, E> 本身就是泛型枚举的经典例子。

1
2
3
4
enum MyOption<T> {
MySome(T),
MyNone,
}

十三、Trait 与 Trait Bound

13.1 Trait (特性)

Trait 是 Rust 的核心特性之一,它定义了共享行为的接口。Trait 可以看作是其他语言中的接口 (interface) 或抽象基类 (abstract base class),但 Rust 的 Trait 更灵活。

  • 定义 Trait

    1
    2
    3
    4
    5
    6
    7
    8
    pub trait Summary {
    fn summarize(&self) -> String; // Trait 方法签名

    // 可以提供默认实现
    fn summarize_author(&self) -> String {
    String::from("Rustacean")
    }
    }
  • 为类型实现 Trait

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
    }

    impl Summary for NewsArticle { // 为 NewsArticle 实现 Summary Trait
    fn summarize(&self) -> String {
    format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
    // summarize_author 使用默认实现
    }

    pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
    }

    impl Summary for Tweet { // 为 Tweet 实现 Summary Trait
    fn summarize(&self) -> String {
    format!("{}: {}", self.username, self.content)
    }
    // summarize_author 方法可以被覆盖
    fn summarize_author(&self) -> String {
    format!("@{}", self.username)
    }
    }

13.2 Trait Bounds (特性约束)

Trait Bounds 用于对泛型类型参数施加约束,确保它们实现了特定的 Trait,从而保证在泛型代码内部可以使用这些 Trait 定义的方法。

  • 在函数中使用 Trait Bound

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 语法糖:impl Trait
    pub fn notify(item: &impl Summary) { // 任何实现了 Summary Trait 的类型都可以作为参数
    println!("Breaking news! {}", item.summarize());
    }

    // 等价于更长的 Trait Bound 语法
    pub fn notify_verbose<T: Summary>(item: &T) { // T 必须实现 Summary Trait
    println!("Breaking news! {}", item.summarize());
    }

    // 多个 Trait Bound
    pub fn notify_multiple(item: &(impl Summary + Display)) { /* ... */ } // item 须实现 Summary 和 Display

    // 泛型类型参数的多个 Trait Bound
    pub fn notify_multiple_verbose<T: Summary + Display>(item: &T) { /* ... */ }

    // where 从句简化复杂的 Trait Bound
    fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
    U: Clone + Debug
    {
    // ...
    42
    }

13.3 Trait 作为返回类型

函数可以返回实现了 Trait 的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn returns_summarizable() -> impl Summary { // 返回实现了 Summary 的类型(但调用者不知具体类型)
Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
}
}

// 注意:如果返回不同类型,即使它们都实现了同一个 Trait,也无法编译
// fn returns_summarizable_error(switch: bool) -> impl Summary {
// if switch {
// NewsArticle { /* ... */ }
// } else {
// Tweet { /* ... */ } // 编译错误!
// }
// }

13.4 Newtype 模式和孤儿规则 (Orphan Rule)

为了避免破坏现有的类型,Rust 有一个“孤儿规则”:只能为你 crates 中的类型实现 Trait,或者为你的 Trait 在你的 crates 中实现类型。这意味着不能为外部类型实现外部 Trait。

当需要为外部类型(如 Vec<i32>)实现外部 Trait (如 Display) 时,可以使用 Newtype 模式:将外部类型封装在新结构体中。

1
2
3
4
5
6
7
8
struct MyVec(Vec<i32>); // 新类型 MyVec 拥有 Vec<i32>

// 现在可以在你的 crate 中为 MyVec 实现 Display Trait
// impl std::fmt::Display for MyVec {
// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// write!(f, "{:?}", self.0)
// }
// }

十四、生命周期 (Lifetimes)

生命周期是 Rust 编译器确保所有引用在程序执行期间都有效的一种机制。它们是编译器在编译时进行静态分析的工具,不涉及运行时开销。

14.1 核心概念

  • 借用检查器 (Borrow Checker):Rust 编译器的一部分,负责比较作用域来确定所有借用都是有效的。
  • 生命周期注解 (Lifetime Annotations):用 'a'b 等表示,用于告诉借用检查器引用之间生命周期的关系。
  • 生命周期省略规则 (Lifetime Elision Rules):在许多情况下,Rust 编译器可以自动推断生命周期,不需要手动注解。

14.2 函数中的生命周期注解

当函数参数是引用,并且返回值也是引用时,需要生命周期注解来明确输入引用和输出引用之间的生命周期关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 'a 是生命周期参数,它表示返回的引用将与两个输入引用中生命周期较短的那个保持一致
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

fn main() {
let string1 = String::from("abcd");
let string2 = "xyz"; // &'static str,生命周期贯穿整个程序

let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);

let string3 = String::from("long string is long");
{
let string4 = String::from("xyz");
let result2 = longest(string3.as_str(), string4.as_str()); // result2 的生命周期被 string4 限制
println!("The longest string is {}", result2);
}
// println!("Result2 after string4 scope: {}", result2); // 编译错误!result2 不再有效
}

14.3 结构体中的生命周期注解

如果结构体包含引用,那么结构体的定义需要生命周期注解。

1
2
3
4
5
6
7
8
9
10
struct ImportantExcerpt<'a> { // 结构体包含一个引用,其生命周期为 'a
part: &'a str,
}

fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = ImportantExcerpt { part: first_sentence };
// i 的生命周期被 first_sentence (即 novel)的生命周期所限制。
}

14.4 静态生命周期 'static

'static 是一个特殊的生命周期,表示引用在整个程序的生命周期中都是有效的。字符串字面量拥有 'static 生命周期。

1
let s: &'static str = "I have a static lifetime.";

十五、常用标准库函数与实用宏

Rust 的标准库 (std) 提供了大量实用的函数、宏和类型。

15.1 常用类型与 Trait

  • std::collectionsVec, HashMap, BTreeMap, HashSet, BTreeSet, LinkedList 等。
  • std::io:输入输出操作,如 File, BufReader, stdin(), stdout()
  • std::fs:文件系统操作,如 File::open, fs::read_to_string
  • std::fmt:格式化输出相关的 Trait (Display, Debug) 和宏 (format!, print!, println!)。
  • std::env:访问环境变量和命令行参数。
  • std::path:路径操作。
  • std::time:时间相关的类型和函数。

15.2 实用宏 (Macros)

Rust 宏在编译时扩展代码,提供了元编程能力。

  • println! / eprintln!:用于打印到标准输出/标准错误。

  • dbg!:在调试时打印表达式的值和文件名、行号等,并返回表达式本身。非常方便。

    1
    2
    3
    4
    5
    fn main() {
    let x = 5;
    let y = dbg!(x * 2); // 打印 "src/main.rs:4:13: 10" 然后 y = 10
    println!("y = {}", y);
    }
  • vec!:创建 Vec 的宏。

  • assert! / assert_eq! / assert_ne!:测试宏,用于检查条件是否为 true,或者比较两个值是否相等/不相等。

  • panic!:用于不可恢复错误,终止程序。

  • format!:格式化字符串并返回 String

    1
    2
    let name = "Alice";
    let greeting = format!("Hello, {}!", name); // greeting 是一个 String
  • include_str! / include_bytes!:在编译时将文件内容作为字符串/字节数组嵌入到二进制文件中。

    1
    const MY_FILE_CONTENT: &str = include_str!("my_data.txt"); // 将文件内容编译进程序

十六、Rust 中的异步编程 (Async/Await)

Rust 通过 async/await 语法和 Future Trait 提供了零开销的异步编程模型,无需操作系统线程即可实现高效的并发。

16.1 核心概念

  • Future TraitFuture 表示一个异步计算,它可能尚未完成。当 Futurepoll 方法调用时,它会尝试执行一些工作并返回 Poll::Pending(如果未完成)或 Poll::Ready(T)(如果已完成并返回一个值 T)。
  • async 关键字:用于定义异步函数或异步块。async fn 返回一个 impl Future 类型。
  • await 关键字:用于暂停异步函数的执行,直到一个 Future 完成,并获取其结果。await 只能在 async fnasync 块中使用。
  • Executor (执行器):负责 poll Future,驱动异步任务前进。标准库不提供执行器,需要依赖第三方 runtime,如 Tokio, async-std

16.2 基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// main.rs
use tokio::runtime::Runtime; // 异步运行时,通常需要添加 tokio 依赖

async fn fetch_data_from_url(url: &str) -> String {
println!("Fetching data from: {}", url);
// 模拟异步网络请求
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; // await 暂停当前任务
format!("Data from {}", url)
}

async fn do_something_async() {
println!("Start async operation");

// 同时运行多个 Future 并等待它们全部完成
let (data1, data2) = tokio::join!(
fetch_data_from_url("example.com/data1"),
fetch_data_from_url("example.com/data2")
);

println!("{}", data1);
println!("{}", data2);

println!("End async operation");
}

fn main() {
// 创建一个 Tokio 运行时
let rt = Runtime::new().unwrap();
// 运行异步顶层 Future
rt.block_on(do_something_async());
}

Cargo.toml 依赖示例:

1
2
3
[dependencies]
async-std = { version = "1.12", features = ["attributes"] } # 或
tokio = { version = "1", features = ["full"] }

上述示例使用了 tokio 运行时。

16.3 异步编程的优点

  • 高并发性:允许程序在单个线程上同时处理大量 I/O 密集型任务,而不需要创建大量操作系统线程(每个线程都有自己的内存和上下文切换开销)。
  • 高效利用资源:当任务等待 I/O 完成时,CPU 不会空闲,而是可以切换到其他等待执行的任务。
  • 避免阻塞:通过 await 非阻塞地等待操作完成,而不是阻塞整个线程。

16.4 异步编程的挑战

  • 生态系统:需要选择合适的异步运行时 (runtime),如 Tokio 或 async-std。
  • 学习曲线:理解 async/awaitFuture Trait 和执行器的工作原理需要一定时间。
  • 调试:异步代码的调用栈可能不那么直观,调试可能更具挑战性。

总结

Rust 是一门功能强大、设计精良的系统编程语言。通过其独特的所有权与借用系统,它在编译时提供了 C/C++ 的性能和低级控制,同时消除了内存安全问题。模式匹配使得处理枚举和复杂数据类型变得简洁而富有表现力。Trait 系统促进了代码的抽象和重用。生命周期机制在编译时保证了引用的有效性。而 Cargo 极大地简化了项目管理。

虽然 Rust 的学习曲线相对陡峭,特别是在理解所有权和生命周期方面,但其带来的内存安全保障、优秀的性能表现以及日渐成熟的异步编程能力,使其在网络服务、嵌入式系统、WebAssembly、命令行工具等众多领域展现出巨大的潜力,正在被越来越多的开发者和企业所采用。掌握这些核心主题是成为一名高效 Rust 程序员的关键。