前言

Rust 连续多年被 Stack Overflow 评为「最受喜爱的编程语言」。它的核心卖点是在不依赖垃圾回收器的情况下保证内存安全。实现这一奇迹的关键机制就是——所有权系统

Rust 代码

为什么需要所有权

C/C++ 的内存管理之痛

在 C 语言中,内存管理全由程序员手动控制:

1
2
3
4
5
6
// C 语言中可能的错误
char* buffer = malloc(1024);
// 1. 忘记 free → 内存泄漏
// 2. free 后继续使用 → 悬垂指针
// 3. 多次 free → 双重释放崩溃
free(buffer);

垃圾回收的代价

Java、Go 等语言使用垃圾回收器 (GC),但:

  • Stop-the-world 暂停影响延迟
  • 运行时开销
  • 不适合系统编程场景

Rust 的第三条路

Rust 通过编译时检查的所有权规则,在编译阶段就消除了内存错误,同时不产生运行时开销。

所有权规则

三条铁律

1
2
3
4
5
6
7
8
9
10
11
12
// 规则 1:每个值有且只有一个所有者
let s1 = String::from("hello");
let s2 = s1;
// s1 的所有权已转移 (move) 给 s2
// println!("{}", s1); // 编译错误!s1 已失效

// 规则 2:当所有者离开作用域,值被自动释放
{
let s = String::from("hello");
} // s 离开作用域,内存被释放

// 规则 3:同一时刻只能有一个可变引用或多个不可变引用

Move 语义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn main() {
let s1 = String::from("hello");

takes_ownership(s1);
// s1 在此处已失效

let x = 5;
makes_copy(x);
// x 仍然有效,因为 i32 实现了 Copy trait
}

fn takes_ownership(s: String) {
println!("{}", s);
} // s 被释放

fn makes_copy(n: i32) {
println!("{}", n);
}

引用与借用

不可变引用

1
2
3
4
5
6
7
8
9
10
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 借用 s1,不获取所有权

println!("'{}' 的长度是 {}", s1, len); // s1 仍可用
}

fn calculate_length(s: &String) -> usize {
s.len()
} // s 离开作用域,但它不拥有数据,所以不释放

可变引用

1
2
3
4
5
6
7
8
9
10
fn main() {
let mut s = String::from("hello");

change(&mut s); // 可变引用
println!("{}", s); // "hello, world"
}

fn change(s: &mut String) {
s.push_str(", world");
}

引用的规则限制

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let mut s = String::from("hello");

let r1 = &s; // 不可变引用
let r2 = &s; // 不可变引用(允许多个)
// let r3 = &mut s; // ❌ 不能同时有可变和不可变引用

println!("{} and {}", r1, r2);
// r1 和 r2 在此之后不再使用

let r3 = &mut s; // ✅ 可以,因为之前的不可变引用已不使用
println!("{}", r3);
}

核心规则:在任意给定时间,要么只能有一个可变引用,要么只能有任意数量的不可变引用。

生命周期

生命周期是 Rust 编译器用来确保所有借用都是有效的一种标注机制。

生命周期标注语法

1
2
3
4
5
6
// 'a 读作 "生命周期 a"
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

// 编译器需要知道返回值的生命周期与哪个参数关联

结构体中的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 包含引用的结构体必须标注生命周期
struct Excerpt<'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 excerpt = Excerpt {
part: first_sentence,
};

println!("{}", excerpt.part);
}

生命周期省略规则

Rust 编译器有三条省略规则,让大多数情况下不需要显式标注:

1
2
3
4
规则 1:每个引用参数都有各自的生命周期参数
规则 2:如果只有一个输入生命周期,它被赋给所有输出
规则 3:如果方法有 &self 或 &mut self 参数,
self 的生命周期被赋给所有输出

常用数据结构

String vs &str

1
2
3
4
5
6
7
// String:拥有所有权的堆分配字符串
let s1 = String::from("hello");
let s2 = "world".to_string();

// &str:字符串切片(引用)
let s3: &str = "hello world";
let s4: &str = &s1[0..3]; // "hel"

Vec 动态数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
v.push(3);

// 用宏创建
let v2 = vec![1, 2, 3, 4, 5];

// 遍历
for i in &v2 {
println!("{}", i);
}

// 修改
for i in &mut v {
*i += 50; // 解引用后修改
}

HashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

// 获取值
let team = String::from("Blue");
match scores.get(&team) {
Some(score) => println!("{} team: {}", team, score),
None => println!("{} team not found", team),
}

// entry API:只在键不存在时插入
scores.entry(String::from("Blue")).or_insert(60);

错误处理

Result 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");

let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};

let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}

// 等价于(使用 ? 运算符):
fn read_username_from_file_short() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}

Option vs Result

类型 用途 Some/Ok None/Err
Option 值可能为空 Some(value) None
Result 操作可能失败 Ok(value) Err(error)

实战:简单 CLI 工具

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
use std::env;
use std::fs;
use std::process;

struct Config {
query: String,
filename: String,
}

impl Config {
fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("参数不足:需要搜索内容和文件路径");
}
let query = args[1].clone();
let filename = args[2].clone();
Ok(Config { query, filename })
}
}

fn main() {
let args: Vec<String> = env::args().collect();

let config = Config::new(&args).unwrap_or_else(|err| {
eprintln!("错误: {}", err);
process::exit(1);
});

let contents = fs::read_to_string(&config.filename)
.unwrap_or_else(|err| {
eprintln!("读取文件失败: {}", err);
process::exit(1);
});

for line in search(&config.query, &contents) {
println!("{}", line);
}
}

fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}

总结

Rust 的所有权系统初学时可能让人沮丧,但一旦理解了它背后的设计哲学——在编译时而不是运行时保证内存安全——你就会发现这套系统既是挑战,也是 Rust 最强大的武器。

学习曲线大致是:所有权 → 借用 → 生命周期 → 泛型 + trait → 并发。按这个顺序,一个概念接一个概念地攻克,最终你会爱上这门语言。