Rust 学习教程 · 核心到进阶
1 所有权系统
1.1 所有权基础
为什么需要所有权
C/C++ 中内存管理靠程序员手动 malloc/free,容易泄漏或悬垂。Java/Go 靠垃圾回收器(GC),但会带来运行时开销和停顿。Rust 选择了一条不同的路:在编译期通过所有权规则检查内存安全,不依赖 GC,也不依赖手动释放。
三条规则
- Rust 中每个值都有一个所有者(owner)。
- 同一时刻,一个值只能有一个所有者。
- 当所有者离开作用域,值会被自动丢弃。
fn main() {
let s = String::from("hello"); // s 是所有者
println!("{}", s);
} // s 离开作用域,内存被释放移动与拷贝
基础标量类型(如 i32、bool、char)默认实现 Copy trait,赋值时按位复制,原变量仍然可用。
let x = 5;
let y = x; // Copy
println!("{}", x); // 合法复杂类型(如 String、Vec)赋值时发生移动(move),原变量失效。
let s1 = String::from("hello");
let s2 = s1; // s1 被移动到 s2
println!("{}", s1); // 错误!value borrowed here after move1.2 借用与生命周期
不可变借用
不想转移所有权,可以使用引用。
fn calculate_length(s: &String) -> usize {
s.len()
}
fn main() {
let s = String::from("hello");
let len = calculate_length(&s);
println!("{} 的长度是 {}", s, len);
}可变借用
默认引用不可变。需要修改原数据时,使用 mut 引用。
fn change(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);
}借用规则
- 同一作用域内,要么只能有一个可变引用,要么只能有多个不可变引用。
- 引用必须始终有效,不能指向已经释放的内存。
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // 错误! cannot borrow as mutable more than once生命周期
生命周期用于确保引用在使用时仍然有效,标注形式为 'a。
fn longest<'a>(x: &'a str, y: &'a str) -&'a str {
if x.len() > y.len() {
x
} else {
y
}
}很多情况下编译器可以自动推断生命周期,不需要手动标注。
1.3 切片
字符串切片
String:拥有所有权的可变字符串,存储在堆上。&str:字符串切片,不拥有数据,只是引用。
let s = String::from("hello");
let slice: &str = &s[0..2]; // "he"数组切片
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3]; // [2, 3]2 结构体与枚举
2.1 结构体
定义与实例化
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user = User {
username: String::from("alice"),
email: String::from("alice@example.com"),
sign_in_count: 1,
active: true,
};方法与关联函数
方法写在 impl 块中,第一个参数是 self。不带 self 的函数称为关联函数,常用于构造函数。
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}2.2 枚举
枚举定义
枚举可以包含不同类型的数据。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}Option
Rust 没有空指针,用 Option<T> 表示可能不存在的值。
enum Option<T> {
Some(T),
None,
}
let some_number = Some(5);
let absent_number: Option<i32> = None;Result
Result<T, E> 表示可能失败的操作结果。
enum Result<T, E> {
Ok(T),
Err(E),
}
let result: Result<i32, ParseIntError> = "42".parse();if let 与 while let
简化对 Option/Result 的处理。
let some_value = Some(3);
if let Some(3) = some_value {
println!("three");
}
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
while let Some(top) = stack.pop() {
println!("{}", top);
}3 错误处理
3.1 不可恢复错误
panic!
panic! 用于不可恢复的错误,程序会终止。
panic!("crash and burn");3.2 可恢复错误
Result 与 match
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};? 运算符
? 可以把 Result 的 Err 直接返回给调用者。
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}unwrap 与 expect
.unwrap() 遇到 Err 会 panic,.expect() 可以自定义 panic 信息。
let f = File::open("hello.txt").unwrap();
let f = File::open("hello.txt").expect("Failed to open hello.txt");3.3 自定义错误
#[derive(Debug)]
struct MyError;
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "something went wrong")
}
}
impl Error for MyError {}4 泛型与 trait
4.1 泛型
泛型函数
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}泛型结构体
struct Point<T> {
x: T,
y: T,
}
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };4.2 trait
定义与实现
trait 定义了一组可以被共享的行为。
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {}", self.headline, self.location)
}
}trait bound
约束泛型参数必须实现某些 trait。
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}常用标准库 trait
| trait | 作用 |
|---|---|
Debug | 用于 {:?} 格式化输出 |
Display | 用于 {} 格式化输出 |
Clone | 显式深拷贝 |
Copy | 隐式按位复制 |
PartialEq / Eq | 相等比较 |
PartialOrd / Ord | 大小比较 |
Default | 提供默认值 |
5 模块与包管理
5.1 模块系统
四个层次
Rust 的模块系统分为四个层次:
- 包(Package):一个 Cargo 项目,包含一个或多个 crate。
- Crate:一个编译单元,可以是库或二进制可执行文件。
- 模块(Module):用
mod组织代码,控制可见性。 - 路径(Path):用于引用模块中的项。
定义与引用模块
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}pub 可见性
默认模块内所有项都是私有的,用 pub 公开。
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String, // 私有
}
}use 与 pub use
use 把路径引入当前作用域。pub use 可以再导出,让外部代码也能通过这个路径访问。
pub use crate::front_of_house::hosting;5.2 Cargo 与依赖
添加依赖
Cargo.toml 中添加依赖:
[dependencies]
serde = "1.0"
rand = "0.8"运行 cargo build 自动下载并编译。
Workspace
一个仓库管理多个相关包:
[workspace]
members = ["adder", "add_one"]6 高级特性
6.1 智能指针
Rust 中一些常用智能指针:
| 类型 | 作用 |
|---|---|
Box<T> | 堆上分配数据 |
Rc<T> | 单线程引用计数共享所有权 |
RefCell<T> | 运行时借用检查的内部可变性 |
Arc<T> | 多线程安全的引用计数 |
Mutex<T> / RwLock<T> | 多线程互斥访问 |
let b = Box::new(5);
println!("b = {}", b);6.2 并发与异步
并发编程
Rust 通过所有权和类型系统保证线程安全。
use std::thread;
let handle = thread::spawn(|| {
println!("hello from spawned thread");
});
handle.join().unwrap();async/await
Rust 原生支持异步编程,通常配合 tokio 运行时。
async fn hello() {
println!("hello async");
}
#[tokio::main]
async fn main() {
hello().await;
}6.3 不安全 Rust 与 FFI
unsafe
unsafe 块可以绕过编译器的部分安全检查。
unsafe {
// 裸指针、调用 unsafe 函数等
}使用 unsafe 需要程序员自己保证安全。
FFI
Rust 可以调用 C 库,使用 extern 声明外部函数。
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("abs(-3) = {}", abs(-3));
}
}6.4 宏
- 声明宏:用
macro_rules!定义。 - 过程宏:在编译期处理 Token 流,分为派生宏、属性宏、函数式宏。
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
say_hello!();7 工程实践
7.1 测试
单元测试
测试函数用 #[test] 标注。
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}运行 cargo test。
集成测试
集成测试放在项目根目录的 tests/ 文件夹中,每个文件是一个独立 crate。
文档测试
在文档注释中写示例代码,cargo test 会自动运行。
/// # Examples
/// ```
/// let result = my_crate::add(2, 2);
/// assert_eq!(result, 4);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}7.2 代码质量
| 工具 | 命令 | 作用 |
|---|---|---|
| rustfmt | cargo fmt | 自动格式化 |
| clippy | cargo clippy | 静态检查与建议 |
| rustdoc | cargo doc | 生成文档 |
7.3 CI/CD
常见 GitHub Actions 配置:
name: Rust CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Test
run: cargo test --verbose
- name: Clippy
run: cargo clippy -- -D warnings
- name: Format check
run: cargo fmt -- --check7.4 实战项目建议
- CLI 工具:实现一个文件搜索或 TODO 管理工具。
- Web 服务:用
axum或actix-web写 REST API。 - 系统工具:实现简单的端口扫描器或日志分析器。
- 小游戏:用
bevy或macroquad写 2D 小游戏。
8 生态与进阶方向
8.1 常用 crate
| crate | 用途 |
|---|---|
serde | 序列化与反序列化 |
tokio | 异步运行时 |
reqwest | HTTP 客户端 |
axum / actix-web | Web 框架 |
sqlx / diesel | 数据库 |
clap | 命令行参数解析 |
anyhow / thiserror | 错误处理辅助 |
log / tracing | 日志与链路追踪 |
8.2 应用场景
嵌入式开发
Rust 可以开发无操作系统或 RTOS 上的固件,常用 no_std 环境。
WebAssembly
Rust 编译到 WASM 性能优秀,常用于浏览器前端高性能模块。
cargo new --lib wasm-demo
cargo install wasm-pack
wasm-pack build --target web系统编程
操作系统内核、驱动、命令行工具、数据库等底层系统软件是 Rust 的传统强项。
8.3 继续学习建议
- 阅读 The Rust Programming Language 官方书籍。
- 刷 Rustlings 小练习。
- 在 Exercism Rust 轨道 做题。
- 阅读优秀开源项目源码,如
ripgrep、alacritty、tokio。 - 参与社区,阅读 This Week in Rust。
总结
从所有权系统出发,掌握结构体、枚举、错误处理、泛型与 trait,再理解模块系统和 Cargo 包管理,最后学习智能指针、并发、异步、unsafe 和宏等高级特性,就能具备使用 Rust 进行工程实践的能力。Rust 的学习曲线较陡,但熟悉之后,编译器会成为你最可靠的助手。