Skip to content

Rust 学习教程 · 核心到进阶

1 所有权系统

1.1 所有权基础

为什么需要所有权

C/C++ 中内存管理靠程序员手动 malloc/free,容易泄漏或悬垂。Java/Go 靠垃圾回收器(GC),但会带来运行时开销和停顿。Rust 选择了一条不同的路:在编译期通过所有权规则检查内存安全,不依赖 GC,也不依赖手动释放。

三条规则

  1. Rust 中每个值都有一个所有者(owner)。
  2. 同一时刻,一个值只能有一个所有者。
  3. 当所有者离开作用域,值会被自动丢弃。
rust
fn main() {
    let s = String::from("hello");  // s 是所有者
    println!("{}", s);
} // s 离开作用域,内存被释放

移动与拷贝

基础标量类型(如 i32、bool、char)默认实现 Copy trait,赋值时按位复制,原变量仍然可用。

rust
let x = 5;
let y = x;      // Copy
println!("{}", x);  // 合法

复杂类型(如 String、Vec)赋值时发生移动(move),原变量失效。

rust
let s1 = String::from("hello");
let s2 = s1;    // s1 被移动到 s2
println!("{}", s1);  // 错误!value borrowed here after move

1.2 借用与生命周期

不可变借用

不想转移所有权,可以使用引用。

rust
fn calculate_length(s: &String) -> usize {
    s.len()
}

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);
    println!("{} 的长度是 {}", s, len);
}

可变借用

默认引用不可变。需要修改原数据时,使用 mut 引用。

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

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s);
}

借用规则

  • 同一作用域内,要么只能有一个可变引用,要么只能有多个不可变引用。
  • 引用必须始终有效,不能指向已经释放的内存。
rust
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;  // 错误! cannot borrow as mutable more than once

生命周期

生命周期用于确保引用在使用时仍然有效,标注形式为 'a

rust
fn longest<'a>(x: &'a str, y: &'a str) -&'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

很多情况下编译器可以自动推断生命周期,不需要手动标注。

1.3 切片

字符串切片

  • String:拥有所有权的可变字符串,存储在堆上。
  • &str:字符串切片,不拥有数据,只是引用。
rust
let s = String::from("hello");
let slice: &str = &s[0..2];  // "he"

数组切片

rust
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3];  // [2, 3]

2 结构体与枚举

2.1 结构体

定义与实例化

rust
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 的函数称为关联函数,常用于构造函数。

rust
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

2.2 枚举

枚举定义

枚举可以包含不同类型的数据。

rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

Option

Rust 没有空指针,用 Option<T> 表示可能不存在的值。

rust
enum Option<T> {
    Some(T),
    None,
}

let some_number = Some(5);
let absent_number: Option<i32> = None;

Result

Result<T, E> 表示可能失败的操作结果。

rust
enum Result<T, E> {
    Ok(T),
    Err(E),
}

let result: Result<i32, ParseIntError> = "42".parse();

if let 与 while let

简化对 Option/Result 的处理。

rust
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! 用于不可恢复的错误,程序会终止。

rust
panic!("crash and burn");

3.2 可恢复错误

Result 与 match

rust
let f = File::open("hello.txt");

let f = match f {
    Ok(file) => file,
    Err(error) => panic!("Problem opening the file: {:?}", error),
};

? 运算符

? 可以把 ResultErr 直接返回给调用者。

rust
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 信息。

rust
let f = File::open("hello.txt").unwrap();
let f = File::open("hello.txt").expect("Failed to open hello.txt");

3.3 自定义错误

rust
#[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 泛型

泛型函数

rust
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

泛型结构体

rust
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 定义了一组可以被共享的行为。

rust
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。

rust
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):用于引用模块中的项。

定义与引用模块

rust
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 公开。

rust
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,  // 私有
    }
}

use 与 pub use

use 把路径引入当前作用域。pub use 可以再导出,让外部代码也能通过这个路径访问。

rust
pub use crate::front_of_house::hosting;

5.2 Cargo 与依赖

添加依赖

Cargo.toml 中添加依赖:

toml
[dependencies]
serde = "1.0"
rand = "0.8"

运行 cargo build 自动下载并编译。

Workspace

一个仓库管理多个相关包:

toml
[workspace]
members = ["adder", "add_one"]

6 高级特性

6.1 智能指针

Rust 中一些常用智能指针:

类型作用
Box<T>堆上分配数据
Rc<T>单线程引用计数共享所有权
RefCell<T>运行时借用检查的内部可变性
Arc<T>多线程安全的引用计数
Mutex<T> / RwLock<T>多线程互斥访问
rust
let b = Box::new(5);
println!("b = {}", b);

6.2 并发与异步

并发编程

Rust 通过所有权和类型系统保证线程安全。

rust
use std::thread;

let handle = thread::spawn(|| {
    println!("hello from spawned thread");
});

handle.join().unwrap();

async/await

Rust 原生支持异步编程,通常配合 tokio 运行时。

rust
async fn hello() {
    println!("hello async");
}

#[tokio::main]
async fn main() {
    hello().await;
}

6.3 不安全 Rust 与 FFI

unsafe

unsafe 块可以绕过编译器的部分安全检查。

rust
unsafe {
    // 裸指针、调用 unsafe 函数等
}

使用 unsafe 需要程序员自己保证安全。

FFI

Rust 可以调用 C 库,使用 extern 声明外部函数。

rust
extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("abs(-3) = {}", abs(-3));
    }
}

6.4 宏

  • 声明宏:用 macro_rules! 定义。
  • 过程宏:在编译期处理 Token 流,分为派生宏、属性宏、函数式宏。
rust
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

say_hello!();

7 工程实践

7.1 测试

单元测试

测试函数用 #[test] 标注。

rust
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

运行 cargo test

集成测试

集成测试放在项目根目录的 tests/ 文件夹中,每个文件是一个独立 crate。

文档测试

在文档注释中写示例代码,cargo test 会自动运行。

rust
/// # Examples
/// ```
/// let result = my_crate::add(2, 2);
/// assert_eq!(result, 4);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

7.2 代码质量

工具命令作用
rustfmtcargo fmt自动格式化
clippycargo clippy静态检查与建议
rustdoccargo doc生成文档

7.3 CI/CD

常见 GitHub Actions 配置:

yaml
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 -- --check

7.4 实战项目建议

  • CLI 工具:实现一个文件搜索或 TODO 管理工具。
  • Web 服务:用 axumactix-web 写 REST API。
  • 系统工具:实现简单的端口扫描器或日志分析器。
  • 小游戏:用 bevymacroquad 写 2D 小游戏。

8 生态与进阶方向

8.1 常用 crate

crate用途
serde序列化与反序列化
tokio异步运行时
reqwestHTTP 客户端
axum / actix-webWeb 框架
sqlx / diesel数据库
clap命令行参数解析
anyhow / thiserror错误处理辅助
log / tracing日志与链路追踪

8.2 应用场景

嵌入式开发

Rust 可以开发无操作系统或 RTOS 上的固件,常用 no_std 环境。

WebAssembly

Rust 编译到 WASM 性能优秀,常用于浏览器前端高性能模块。

bash
cargo new --lib wasm-demo
cargo install wasm-pack
wasm-pack build --target web

系统编程

操作系统内核、驱动、命令行工具、数据库等底层系统软件是 Rust 的传统强项。

8.3 继续学习建议


总结

从所有权系统出发,掌握结构体、枚举、错误处理、泛型与 trait,再理解模块系统和 Cargo 包管理,最后学习智能指针、并发、异步、unsafe 和宏等高级特性,就能具备使用 Rust 进行工程实践的能力。Rust 的学习曲线较陡,但熟悉之后,编译器会成为你最可靠的助手。