Skip to content

Rust 系统学习文档 ​

适合人群:已经掌握一门主流编程语言,希望系统学习 Rust 语言、工具链、工程实践、后端服务、异步编程、数据库访问、WASM、CLI 和高性能系统开发的学习者。

学习目标:从 Rust 基础语法开始,逐步掌握所有权、借用、生命周期、trait、泛型、错误处理、并发、异步、Cargo 工程化、测试、Web 后端、数据库、部署和真实项目落地。

核对时间:2026-09-01。Rust 当前稳定版已进入 1.98.x 线;Rust 2024 Edition 已在 Rust 1.85.0 稳定。具体安装版本、发行说明和 Edition 细节请以 Rust 官方网站和官方博客为准。


目录 ​

  1. Rust 是什么
  2. Rust 适合什么场景
  3. Rust 工具链:rustup、rustc、cargo
  4. 创建第一个 Rust 项目
  5. Rust 项目结构与 Cargo.toml
  6. 基础语法:变量、类型、函数、控制流
  7. 所有权、移动、复制、借用
  8. 引用、可变引用与借用检查器
  9. 生命周期
  10. 结构体、枚举、模式匹配
  11. Option、Result 与错误处理
  12. 泛型、trait、trait object
  13. 模块系统、crate、package、workspace
  14. 集合、字符串、迭代器、闭包
  15. 智能指针与内部可变性
  16. 并发编程:线程、消息传递、共享状态
  17. 异步编程:Future、async/await、Tokio
  18. Rust Web 后端技术栈:Axum、Actix Web、Tower
  19. 数据库访问:SQLx、Diesel、SeaORM
  20. 序列化、配置、日志与可观测性
  21. 测试、基准测试与代码质量
  22. CLI、WASM、嵌入式与 FFI
  23. 项目落地:后端 API 服务示例
  24. 学习路线:由基础到高阶
  25. 常见误区与排错
  26. 核心知识点深度补全
  27. 官方参考资料

1. Rust 是什么 ​

Rust 是一门注重性能、可靠性和内存安全的系统级编程语言。它不依赖垃圾回收器,而是通过所有权、借用和生命周期机制,在编译期防止大量内存错误。

Rust 的核心特点:

  • 内存安全:避免悬垂指针、重复释放、数据竞争等问题。
  • 高性能:接近 C/C++ 的运行效率。
  • 无 GC:没有传统垃圾回收停顿。
  • 强类型:类型系统表达能力强。
  • 并发安全:在编译期减少数据竞争。
  • 工具链完整:Cargo、rustfmt、Clippy、rust-analyzer 体验成熟。
  • 跨平台:可用于 Linux、Windows、macOS、WebAssembly、嵌入式等环境。

一句话理解:

Rust 试图让你写出接近 C/C++ 性能、但更难写出内存错误和并发错误的程序。

1.1 Rust 和 C/C++ 的关系 ​

Rust 经常被拿来和 C/C++ 对比。

Rust 不是 C++ 的语法糖,也不是 C 的替代方言。它是一门新语言,提供:

  • 所有权系统。
  • 模式匹配。
  • trait。
  • cargo 包管理。
  • 现代错误处理。
  • 安全抽象。

Rust 可以写底层系统,也可以写 Web 后端、CLI、WASM 和服务端基础设施。

1.2 Rust 和 Go 的关系 ​

Go 更强调简单、快速开发和云原生服务。

Rust 更强调控制力、性能、内存安全和零成本抽象。

粗略选择:

  • 快速写微服务、团队学习成本低:Go 很合适。
  • 性能敏感、内存安全、系统工具、底层组件:Rust 更合适。
  • 高性能后端服务:Rust 和 Go 都可选,取决于团队能力和性能要求。

2. Rust 适合什么场景 ​

2.1 非常适合 ​

  • 高性能后端服务。
  • 网络代理。
  • 数据处理工具。
  • CLI 工具。
  • 编译器、解释器、语言工具。
  • 数据库、存储引擎。
  • 区块链和密码学相关基础设施。
  • WebAssembly。
  • 嵌入式。
  • 操作系统、驱动、系统组件。
  • 需要替代部分 C/C++ 的安全底层模块。

2.2 可以使用,但要评估 ​

  • 普通 CRUD Web 服务。
  • 企业后台。
  • 小团队业务系统。
  • 快速原型。

这些场景 Rust 能做,但学习成本和开发速度要评估。如果团队主要目标是快速交付业务,TypeScript/NestJS、Java/Spring、Go 可能更现实。

2.3 不太适合初学者直接上手的场景 ​

  • 对业务变化极快、模型还不稳定的早期产品。
  • 团队没有 Rust 经验、交付周期很紧。
  • 大量依赖不成熟生态的业务。
  • 只是为了“技术先进”而选择 Rust。

Rust 很强,但不是所有项目都值得用 Rust。


3. Rust 工具链:rustup、rustc、cargo ​

3.1 rustup ​

rustup 是 Rust 官方推荐的工具链管理器。

它负责:

  • 安装 Rust。
  • 切换 stable / beta / nightly。
  • 安装组件。
  • 添加交叉编译 target。
  • 更新工具链。

常用命令:

bash
rustup update
rustup show
rustup toolchain list
rustup default stable
rustup component add rustfmt clippy
rustup target add wasm32-unknown-unknown

3.2 rustc ​

rustc 是 Rust 编译器。

你可以直接编译单文件:

bash
rustc main.rs

但真实项目通常不直接使用 rustc,而是使用 Cargo。

3.3 Cargo ​

Cargo 是 Rust 的包管理器和构建工具。

它负责:

  • 创建项目。
  • 管理依赖。
  • 编译。
  • 运行。
  • 测试。
  • 发布 crate。
  • 管理 workspace。
  • 生成文档。

常用命令:

bash
cargo new hello-rust
cargo run
cargo build
cargo build --release
cargo test
cargo check
cargo fmt
cargo clippy
cargo doc --open

3.4 rustfmt ​

rustfmt 是格式化工具。

bash
cargo fmt

团队项目应在 CI 中检查格式:

bash
cargo fmt --check

3.5 Clippy ​

Clippy 是 Rust 的 linter。

bash
cargo clippy

CI 中推荐:

bash
cargo clippy --all-targets --all-features -- -D warnings

含义:把 Clippy warning 当作错误,防止质量问题进入主分支。

3.6 rust-analyzer ​

rust-analyzer 是 Rust 的语言服务器,提供:

  • 自动补全。
  • 跳转定义。
  • 类型提示。
  • 重构。
  • inline hints。
  • 诊断。

VS Code、Neovim、IntelliJ Rust 等开发环境通常都会用到它。


4. 创建第一个 Rust 项目 ​

4.1 创建项目 ​

bash
cargo new hello-rust
cd hello-rust
cargo run

项目结构:

text
hello-rust/
  Cargo.toml
  src/
    main.rs

src/main.rs:

rust
fn main() {
    println!("Hello, world!");
}

4.2 编译和运行 ​

开发模式:

bash
cargo run

只检查不生成最终二进制:

bash
cargo check

生产优化构建:

bash
cargo build --release

产物位置:

text
target/debug/
target/release/

4.3 cargo check 为什么重要 ​

Rust 编译包含类型检查、借用检查、代码生成等步骤。

cargo check 不生成最终机器码,速度通常比完整 build 快,适合开发时频繁检查。


5. Rust 项目结构与 Cargo.toml ​

5.1 二进制项目 ​

text
src/main.rs

表示这是可执行程序。

5.2 库项目 ​

bash
cargo new my-lib --lib

结构:

text
src/lib.rs

库可以被其他 crate 引用。

5.3 同时包含 lib 和 bin ​

text
src/
  lib.rs
  main.rs

常见做法:

  • lib.rs 放核心逻辑。
  • main.rs 放启动入口和参数解析。

这样更容易测试。

5.4 Cargo.toml ​

toml
[package]
name = "hello-rust"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

关键字段:

  • name:包名。
  • version:版本。
  • edition:Rust Edition,例如 2021、2024。
  • dependencies:运行依赖。
  • dev-dependencies:测试或开发依赖。
  • features:可选功能开关。

5.5 Edition 是什么 ​

Rust Edition 是语言演进机制,用于在不破坏旧代码的情况下引入新语法和行为调整。

常见 Edition:

  • 2015。
  • 2018。
  • 2021。
  • 2024。

新项目建议使用当前稳定工具链支持的最新 Edition。


6. 基础语法:变量、类型、函数、控制流 ​

6.1 变量默认不可变 ​

rust
let count = 1;
// count = 2; // 编译错误

可变变量:

rust
let mut count = 1;
count = 2;

Rust 默认不可变,有助于减少状态变化带来的错误。

6.2 类型推断 ​

rust
let name = "Ada";
let age = 28;

编译器能推断类型。

显式类型:

rust
let age: u32 = 28;

6.3 常见标量类型 ​

类型示例
i32有符号 32 位整数
u64无符号 64 位整数
f6464 位浮点数
booltrue / false
charUnicode 字符

6.4 函数 ​

rust
fn add(a: i32, b: i32) -> i32 {
    a + b
}

最后一行没有分号,表示表达式返回值。

如果写成:

rust
fn add(a: i32, b: i32) -> i32 {
    return a + b;
}

也可以,但 Rust 更常用表达式风格。

6.5 if 表达式 ​

rust
let score = 80;

let level = if score >= 60 {
    "pass"
} else {
    "fail"
};

if 可以返回值,但每个分支返回类型必须一致。

6.6 loop、while、for ​

rust
for item in [1, 2, 3] {
    println!("{item}");
}

Rust 中最常用的是 for,配合迭代器非常自然。


7. 所有权、移动、复制、借用 ​

所有权是 Rust 最核心、最需要认真理解的概念。

7.1 所有权规则 ​

Rust 有三条基本规则:

  1. 每个值都有一个所有者。
  2. 同一时间只能有一个所有者。
  3. 当所有者离开作用域,值会被释放。

7.2 移动 ​

rust
let s1 = String::from("hello");
let s2 = s1;

// println!("{s1}"); // 编译错误
println!("{s2}");

String 在堆上分配内存。s1 移动给 s2 后,s1 不再可用。

这避免了两个变量同时认为自己拥有同一块堆内存,防止重复释放。

7.3 Copy ​

rust
let x = 1;
let y = x;

println!("{x}, {y}");

整数类型实现了 Copy,赋值会复制值,而不是移动所有权。

常见 Copy 类型:

  • 整数。
  • 浮点数。
  • bool。
  • char。
  • 只包含 Copy 类型的 tuple。

7.4 clone ​

如果确实要复制堆数据:

rust
let s1 = String::from("hello");
let s2 = s1.clone();

println!("{s1}, {s2}");

clone() 可能有成本。看到 clone() 时要思考是否真的需要复制。

7.5 借用 ​

不想转移所有权时,用引用:

rust
fn print_length(s: &String) {
    println!("{}", s.len());
}

fn main() {
    let name = String::from("Ada");
    print_length(&name);
    println!("{name}");
}

&name 是不可变引用,函数只是借用它。


8. 引用、可变引用与借用检查器 ​

8.1 不可变引用 ​

rust
let s = String::from("hello");
let r1 = &s;
let r2 = &s;

println!("{r1}, {r2}");

多个不可变引用可以同时存在。

8.2 可变引用 ​

rust
let mut s = String::from("hello");
let r = &mut s;
r.push_str(" world");

8.3 可变引用规则 ​

同一时间只能有一个可变引用:

rust
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // 编译错误
println!("{r1}");

原因:防止数据竞争和不一致状态。

8.4 不可变引用和可变引用不能同时存在 ​

rust
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
// let r3 = &mut s; // 编译错误
println!("{r1}, {r2}");

如果不可变引用后续不再使用,编译器可以允许新的可变引用。

8.5 借用检查器的价值 ​

借用检查器会让新手痛苦,但它在编译期防止:

  • 悬垂引用。
  • 迭代时修改集合。
  • 多线程数据竞争。
  • 可变共享导致的隐性 bug。

9. 生命周期 ​

生命周期描述引用有效范围。

多数时候 Rust 可以自动推断生命周期,不需要手写。

9.1 为什么需要生命周期 ​

错误示例:

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

编译器不知道返回引用来自 x 还是 y,也不知道它应该活多久。

写明生命周期:

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

含义:返回值的生命周期不超过 x 和 y 中较短的那个。

9.2 生命周期不是延长引用寿命 ​

生命周期标注不会让值活得更久,只是告诉编译器引用之间的关系。

9.3 结构体中的引用 ​

rust
struct UserView<'a> {
    name: &'a str,
}

表示 UserView 不能比 name 引用的数据活得更久。

9.4 什么时候需要手写生命周期 ​

常见场景:

  • 函数返回引用。
  • 结构体持有引用。
  • trait 方法涉及引用关系。
  • 编译器无法推断多个引用之间关系。

业务代码中,很多时候可以通过拥有数据来减少生命周期复杂度:

rust
struct User {
    name: String,
}

10. 结构体、枚举、模式匹配 ​

10.1 struct ​

rust
struct User {
    id: u64,
    name: String,
    active: bool,
}

let user = User {
    id: 1,
    name: String::from("Ada"),
    active: true,
};

10.2 impl ​

rust
impl User {
    fn display_name(&self) -> &str {
        &self.name
    }

    fn deactivate(&mut self) {
        self.active = false;
    }
}

&self 是不可变借用。

&mut self 是可变借用。

10.3 enum ​

rust
enum UserStatus {
    Active,
    Disabled,
    Pending { reason: String },
}

Rust 的 enum 可以携带数据,比很多语言的枚举更强。

10.4 match ​

rust
fn status_text(status: UserStatus) -> String {
    match status {
        UserStatus::Active => "active".to_string(),
        UserStatus::Disabled => "disabled".to_string(),
        UserStatus::Pending { reason } => format!("pending: {reason}"),
    }
}

match 必须覆盖所有情况,这让状态处理更安全。

10.5 if let ​

只关心一种情况时:

rust
if let Some(name) = maybe_name {
    println!("{name}");
}

11. Option、Result 与错误处理 ​

11.1 Option ​

Rust 没有 null。可能为空用 Option<T>。

rust
let name: Option<String> = Some("Ada".to_string());
let empty: Option<String> = None;

处理:

rust
match name {
    Some(value) => println!("{value}"),
    None => println!("empty"),
}

11.2 Result ​

可能失败用 Result<T, E>。

rust
fn parse_age(input: &str) -> Result<u32, std::num::ParseIntError> {
    input.parse::<u32>()
}

11.3 ? 操作符 ​

rust
fn read_config(path: &str) -> Result<String, std::io::Error> {
    let content = std::fs::read_to_string(path)?;
    Ok(content)
}

? 会在错误时提前返回。

11.4 panic ​

panic! 表示不可恢复错误。

rust
panic!("something went wrong");

业务代码中不要滥用 panic。可恢复错误应使用 Result。

11.5 anyhow 和 thiserror ​

常用错误库:

  • thiserror:定义库或领域错误类型。
  • anyhow:应用层快速聚合错误上下文。

示例:

rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum UserError {
    #[error("user not found: {0}")]
    NotFound(u64),
}

应用层:

rust
use anyhow::{Context, Result};

fn load_config() -> Result<String> {
    std::fs::read_to_string("config.toml")
        .context("failed to read config.toml")
}

12. 泛型、trait、trait object ​

12.1 泛型 ​

rust
fn first<T>(items: &[T]) -> Option<&T> {
    items.first()
}

泛型让函数适用于多种类型。

12.2 trait ​

trait 定义共享行为。

rust
trait Summary {
    fn summarize(&self) -> String;
}

struct Article {
    title: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        self.title.clone()
    }
}

12.3 trait bound ​

rust
fn print_summary<T: Summary>(item: T) {
    println!("{}", item.summarize());
}

也可以:

rust
fn print_summary(item: impl Summary) {
    println!("{}", item.summarize());
}

12.4 dyn Trait ​

rust
fn print_dyn(item: &dyn Summary) {
    println!("{}", item.summarize());
}

impl Trait 通常是静态分发,编译器为具体类型生成代码。

dyn Trait 是动态分发,通过 vtable 在运行时调用。

12.5 怎么选择 ​

优先:

  • 泛型 / impl Trait:性能好,类型明确。

使用 dyn Trait:

  • 需要把不同具体类型放进同一个集合。
  • 需要运行时多态。
  • 想降低泛型膨胀。

13. 模块系统、crate、package、workspace ​

13.1 package ​

一个 Cargo 项目是一个 package。

Cargo.toml 描述 package。

13.2 crate ​

crate 是编译单元。

一个 package 可以包含:

  • 一个 library crate。
  • 多个 binary crate。

13.3 module ​

模块用于组织代码。

rust
mod users;

文件:

text
src/
  main.rs
  users.rs

13.4 pub ​

默认私有,公开需要 pub。

rust
pub fn list_users() {}

13.5 workspace ​

多包项目使用 workspace。

toml
[workspace]
members = [
    "crates/api",
    "crates/core",
    "crates/cli"
]
resolver = "3"

适合:

  • API 服务。
  • 核心库。
  • CLI 工具。
  • 共享类型。

14. 集合、字符串、迭代器、闭包 ​

14.1 Vec ​

rust
let mut numbers = vec![1, 2, 3];
numbers.push(4);

14.2 HashMap ​

rust
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert("Alice", 90);

14.3 String 和 &str ​

String 是可增长、拥有所有权的字符串。

&str 是字符串切片,通常是借用视图。

rust
fn greet(name: &str) {
    println!("Hello, {name}");
}

let name = String::from("Ada");
greet(&name);
greet("Linus");

函数参数优先用 &str,更灵活。

14.4 迭代器 ​

rust
let numbers = vec![1, 2, 3, 4];

let even: Vec<i32> = numbers
    .into_iter()
    .filter(|n| n % 2 == 0)
    .collect();

14.5 闭包 ​

rust
let add_one = |x: i32| x + 1;
println!("{}", add_one(1));

闭包常用于迭代器、异步回调、函数式组合。


15. 智能指针与内部可变性 ​

15.1 Box ​

Box<T> 把值放到堆上。

rust
let value = Box::new(42);

适合:

  • 大对象。
  • 递归类型。
  • trait object。

15.2 Rc ​

Rc<T> 是单线程引用计数。

rust
use std::rc::Rc;

let value = Rc::new(String::from("shared"));
let a = Rc::clone(&value);
let b = Rc::clone(&value);

不适合多线程。

15.3 Arc ​

Arc<T> 是线程安全引用计数。

rust
use std::sync::Arc;

let shared = Arc::new(42);

多线程共享数据常用 Arc。

15.4 Mutex ​

rust
use std::sync::{Arc, Mutex};

let counter = Arc::new(Mutex::new(0));

Mutex 提供互斥访问。

15.5 RefCell ​

RefCell<T> 把借用检查从编译期推迟到运行期。

适合某些单线程内部可变性场景。

但滥用会让代码更难理解。


16. 并发编程:线程、消息传递、共享状态 ​

16.1 创建线程 ​

rust
use std::thread;

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

handle.join().unwrap();

16.2 move 闭包 ​

rust
let name = String::from("Ada");

let handle = std::thread::spawn(move || {
    println!("{name}");
});

handle.join().unwrap();

move 把变量所有权移动进线程,避免引用悬垂。

16.3 channel ​

rust
use std::sync::mpsc;

let (tx, rx) = mpsc::channel();

std::thread::spawn(move || {
    tx.send("hello").unwrap();
});

println!("{}", rx.recv().unwrap());

消息传递适合降低共享状态复杂度。

16.4 Arc + Mutex ​

rust
use std::sync::{Arc, Mutex};
use std::thread;

let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut value = counter.lock().unwrap();
        *value += 1;
    });
    handles.push(handle);
}

for handle in handles {
    handle.join().unwrap();
}

Rust 会在编译期防止很多数据竞争,但死锁仍然可能发生,需要设计上避免。


17. 异步编程:Future、async/await、Tokio ​

17.1 Rust async 的特点 ​

Rust 的 async 返回 Future。

Future 本身是惰性的,必须被 runtime 驱动。

常用 runtime:

  • Tokio。
  • async-std。

服务端生态中 Tokio 最常见。

17.2 Tokio 示例 ​

rust
#[tokio::main]
async fn main() {
    let body = reqwest::get("https://www.rust-lang.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap();

    println!("{}", body.len());
}

17.3 async 函数 ​

rust
async fn fetch_user(id: u64) -> Result<String, reqwest::Error> {
    let url = format!("https://api.example.com/users/{id}");
    let text = reqwest::get(url).await?.text().await?;
    Ok(text)
}

17.4 并发执行 ​

rust
let (user, orders) = tokio::try_join!(
    fetch_user(1),
    fetch_orders(1),
)?;

17.5 async 常见误区 ​

误区一:在 async 中使用阻塞 IO。

错误:

rust
std::fs::read_to_string("large.txt")?;

更好:

rust
tokio::fs::read_to_string("large.txt").await?;

误区二:持有 Mutex 锁跨越 .await。

这可能导致性能问题甚至死锁。应尽量缩短锁作用域。


18. Rust Web 后端技术栈:Axum、Actix Web、Tower ​

18.1 Axum ​

Axum 是 Tokio 生态中的 Web 框架,基于 Tower / Hyper。

适合:

  • API 服务。
  • 微服务。
  • 高性能后端。
  • 与 Tokio 生态深度集成。

示例:

rust
use axum::{routing::get, Router};
use std::net::SocketAddr;

async fn health() -> &'static str {
    "ok"
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/health", get(health));
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    axum::serve(tokio::net::TcpListener::bind(addr).await.unwrap(), app)
        .await
        .unwrap();
}

18.2 Actix Web ​

Actix Web 是成熟高性能 Web 框架。

示例:

rust
use actix_web::{get, App, HttpServer, Responder};

#[get("/health")]
async fn health() -> impl Responder {
    "ok"
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(health))
        .bind(("127.0.0.1", 3000))?
        .run()
        .await
}

18.3 Tower ​

Tower 是服务抽象和 middleware 生态。

Axum 中常用 middleware:

  • CORS。
  • trace。
  • timeout。
  • compression。
  • auth。

18.4 Web 框架怎么选 ​

Axum:

  • Tokio 生态自然。
  • Tower middleware 丰富。
  • 类型组合清晰。

Actix Web:

  • 成熟。
  • 性能强。
  • 文档和案例多。

Poem、Rocket、Warp 等也可选,但生产选型应考虑团队熟悉度、生态、维护活跃度。


19. 数据库访问:SQLx、Diesel、SeaORM ​

19.1 SQLx ​

SQLx 是异步 SQL 工具,支持 PostgreSQL、MySQL、SQLite 等。

特点:

  • async。
  • 可做编译期 SQL 检查。
  • 接近手写 SQL。

示例:

rust
use sqlx::PgPool;

#[derive(Debug)]
struct User {
    id: i64,
    name: String,
}

async fn find_user(pool: &PgPool, id: i64) -> Result<User, sqlx::Error> {
    sqlx::query_as!(
        User,
        "select id, name from users where id = $1",
        id
    )
    .fetch_one(pool)
    .await
}

19.2 Diesel ​

Diesel 是成熟 ORM,类型安全强。

特点:

  • 编译期查询类型检查。
  • ORM 风格。
  • 迁移工具成熟。

适合偏传统 ORM 的项目。

19.3 SeaORM ​

SeaORM 是异步 ORM。

适合:

  • async 项目。
  • 需要实体模型。
  • 希望更接近 ORM 体验。

19.4 怎么选择 ​

推荐判断:

  • 喜欢写 SQL、重视可控性:SQLx。
  • 传统 ORM、强类型查询:Diesel。
  • async ORM 风格:SeaORM。

20. 序列化、配置、日志与可观测性 ​

20.1 Serde ​

Serde 是 Rust 最常用的序列化/反序列化框架。

rust
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u64,
    name: String,
}

JSON:

rust
let json = serde_json::to_string(&user)?;
let user: User = serde_json::from_str(&json)?;

20.2 配置 ​

常见配置来源:

  • 环境变量。
  • .env。
  • TOML / YAML / JSON。
  • 命令行参数。

常用库:

  • config。
  • dotenvy。
  • clap。

20.3 日志和 tracing ​

Rust 后端推荐使用 tracing。

rust
use tracing::info;

fn main() {
    tracing_subscriber::fmt::init();
    info!("service started");
}

Web 服务中可以记录:

  • request id。
  • trace id。
  • route。
  • status。
  • latency。
  • error。

20.4 OpenTelemetry ​

生产系统可以接入 OpenTelemetry,把 Rust 服务的 traces、metrics、logs 送到观测平台。


21. 测试、基准测试与代码质量 ​

21.1 单元测试 ​

rust
fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn adds_two_numbers() {
        assert_eq!(add(1, 2), 3);
    }
}

运行:

bash
cargo test

21.2 集成测试 ​

目录:

text
tests/
  api_test.rs

集成测试会像外部用户一样使用你的 crate。

21.3 文档测试 ​

Rust 支持在文档注释中写示例并测试。

rust
/// Adds two numbers.
///
/// ```
/// assert_eq!(my_crate::add(1, 2), 3);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

21.4 基准测试 ​

常用 Criterion:

rust
// benches/my_benchmark.rs
use criterion::{criterion_group, criterion_main, Criterion};

fn bench_add(c: &mut Criterion) {
    c.bench_function("add", |b| b.iter(|| 1 + 2));
}

criterion_group!(benches, bench_add);
criterion_main!(benches);

21.5 质量检查 ​

推荐 CI:

bash
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo build --release

22. CLI、WASM、嵌入式与 FFI ​

22.1 CLI ​

CLI 项目常用:

  • clap:命令行参数解析。
  • anyhow:应用错误处理。
  • tracing:日志。

示例:

rust
use clap::Parser;

#[derive(Parser)]
struct Args {
    #[arg(short, long)]
    name: String,
}

fn main() {
    let args = Args::parse();
    println!("Hello, {}", args.name);
}

22.2 WebAssembly ​

Rust 可以编译到 WASM。

适合:

  • 前端高性能计算模块。
  • 图像处理。
  • 加密。
  • 游戏逻辑。
  • 跨语言复用算法。

常见工具:

  • wasm-bindgen。
  • wasm-pack。

22.3 嵌入式 ​

Rust 可用于嵌入式开发。

常见概念:

  • no_std。
  • HAL。
  • embedded-hal。
  • probe-rs。

22.4 FFI ​

FFI:Foreign Function Interface,用于和其他语言互操作。

Rust 可以:

  • 调 C。
  • 暴露 C ABI。
  • 被 Node/Python/Java 等调用。

FFI 涉及 unsafe,需要格外谨慎。


23. 项目落地:后端 API 服务示例 ​

23.1 推荐结构 ​

text
rust-api/
  Cargo.toml
  src/
    main.rs
    app.rs
    config.rs
    error.rs
    routes/
      mod.rs
      users.rs
    services/
      users.rs
    repositories/
      users.rs
    models/
      user.rs

23.2 依赖 ​

toml
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "macros"] }
thiserror = "2"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
tower-http = { version = "0.6", features = ["trace", "cors"] }

版本会随生态演进变化,真实项目请以 crates.io 和官方文档为准。

23.3 Handler ​

rust
use axum::{extract::Path, Json};
use serde::Serialize;

#[derive(Serialize)]
pub struct UserResponse {
    pub id: u64,
    pub name: String,
}

pub async fn get_user(Path(id): Path<u64>) -> Json<UserResponse> {
    Json(UserResponse {
        id,
        name: "Ada".to_string(),
    })
}

23.4 Router ​

rust
use axum::{routing::get, Router};

pub fn app() -> Router {
    Router::new().route("/users/{id}", get(crate::routes::users::get_user))
}

23.5 main ​

rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt::init();

    let app = rust_api::app::app();
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;

    axum::serve(listener, app).await?;
    Ok(())
}

23.6 Dockerfile ​

dockerfile
FROM rust:1 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
WORKDIR /app
COPY --from=builder /app/target/release/rust-api /usr/local/bin/rust-api
EXPOSE 3000
CMD ["rust-api"]

生产可进一步使用更小基础镜像和更严格安全策略。


24. 学习路线:由基础到高阶 ​

24.1 基础阶段 ​

目标:能写小程序和理解所有权。

学习:

  1. 变量、类型、函数、控制流。
  2. 所有权、移动、借用。
  3. struct、enum、match。
  4. Option、Result。
  5. Cargo 基础。

练习:

  • 写一个 todo CLI。
  • 写一个文件统计工具。
  • 写几个 Result 错误处理函数。

24.2 进阶阶段 ​

目标:能写可维护模块。

学习:

  1. trait。
  2. 泛型。
  3. 生命周期。
  4. 模块系统。
  5. 迭代器和闭包。
  6. 单元测试。

练习:

  • 写一个配置解析库。
  • 写一个 trait 抽象存储接口。
  • 写集成测试。

24.3 高级阶段 ​

目标:能写后端服务。

学习:

  1. Tokio。
  2. Axum / Actix Web。
  3. SQLx / Diesel。
  4. Serde。
  5. tracing。
  6. Docker 部署。

练习:

  • 写用户 CRUD API。
  • 接 PostgreSQL。
  • 加参数校验、错误处理、日志和测试。

24.4 架构阶段 ​

目标:能把 Rust 放进真实技术体系。

学习:

  1. workspace。
  2. observability。
  3. 性能分析。
  4. unsafe 边界。
  5. FFI。
  6. WASM。
  7. 生产部署。

练习:

  • 设计 Rust API 服务项目结构。
  • 设计 Rust + Node/NestJS 混合架构。
  • 写一个 Rust WASM 图像处理模块给前端调用。

25. 常见误区与排错 ​

25.1 到处 clone ​

clone() 能快速让代码编译,但可能隐藏性能问题。

先思考:

  • 是否可以借用?
  • 是否可以改变数据结构所有权?
  • 是否真的需要复制?

25.2 过早使用生命周期 ​

很多新手遇到生命周期就手写复杂标注。

更好的做法:

  • 先让类型拥有数据。
  • 用 String 而不是强行 &str。
  • 等确实需要借用时再优化。

25.3 在 async 中使用阻塞操作 ​

阻塞文件 IO、阻塞数据库驱动会影响异步 runtime。

尽量使用 async 版本库。

25.4 滥用 unwrap ​

rust
let value = result.unwrap();

原型可以,生产服务中要谨慎。

更好:

rust
let value = result?;

或添加上下文:

rust
let value = result.context("failed to load user")?;

25.5 unsafe 不是性能开关 ​

unsafe 表示编译器无法保证某些安全属性,需要开发者自己保证。

不要为了性能随意使用 unsafe。

25.6 编译慢 ​

常见优化:

  • 使用 cargo check。
  • 减少不必要 feature。
  • workspace 分层。
  • 使用 sccache。
  • CI 缓存 Cargo registry 和 target。

26. 核心知识点深度补全 ​

本章补全 Rust 学习中最容易被「知道语法但不懂机制」的部分:所有权背后的内存模型、Drop/RAII、生命周期省略规则、trait 进阶、Send/Sync、迭代器与单态化、字符串切片、错误链。目标是不仅让代码通过编译,还能写出正确、可维护、高性能的 Rust。

26.1 栈与堆:所有权背后的内存模型 ​

Rust 的所有权规则(第 7 章)背后是「栈」与「堆」的区别:

text
栈(stack)
- 后进先出,编译器知道每个变量的大小和生命周期。
- 分配/释放极快,随函数调用自动进出。
- 存放:整数、浮点、bool、char、大小固定的 struct、指针等。

堆(heap)
- 大小和生命周期运行时才知道,需要手动或靠所有权管理。
- 分配/释放更慢,可能碎片化。
- 存放:String、Vec、Box 等内部可变长数据。

String 为什么特殊:String 结构本身在栈上(包含指向堆的指针、长度、容量),真正的字符数据在堆上。所以:

rust
let s1 = String::from("hello");  // 栈上有 String 结构,堆上有 "hello"
let s2 = s1;                     // 拷贝栈上的结构,但不拷贝堆数据

如果 s1、s2 都认为拥有堆数据,离开作用域时会双重释放(double free)。Rust 用「移动」解决:s1 移动给 s2 后,s1 失效,只剩 s2 一个所有者。

而 i32 这类实现了 Copy 的类型只存栈上,赋值是完整拷贝,所以 x、y 都可用。

理解栈/堆,是理解「为什么 String 会移动、i32 会复制、clone 有成本」的关键。

26.2 Drop 与 RAII ​

Rust 用 RAII(Resource Acquisition Is Initialization)管理资源:值在离开作用域时自动调用 Drop trait 的 drop 方法释放资源。

rust
struct Connection;

impl Drop for Connection {
    fn drop(&mut self) {
        println!("关闭连接");
    }
}

{
    let _conn = Connection;
    // 离开作用域时自动打印「关闭连接」
}

Rust 没有 finally,也不需要手动释放内存/文件/锁 —— 所有权离开作用域就是释放时机。这从机制上避免了「忘记 close / 忘记 free」。

要点:

  • 释放顺序与声明顺序相反(后声明的先释放)。
  • String、Vec、File、MutexGuard 等都实现了 Drop。
  • 不能显式调用 Drop::drop,需要提前释放时用 std::mem::drop(value)(它只是「把值移走,让它在原地结束生命周期」)。

⚠️ 正是 Drop 语义要求「同一时间只能有一个所有者」:否则同一份堆数据会被释放两次。

26.3 部分移动 ​

移动可以细化到结构体的单个字段,这叫部分移动(partial move):

rust
struct User {
    name: String,
    age: u32,
}

let user = User { name: String::from("Ada"), age: 30 };
let name = user.name;   // 只移动 name 字段

println!("{}", user.age);   // OK,age 还在
// println!("{}", user.name); // 错误,name 已被移走

部分移动后,user 整体不再可用(不能把 user 传给需要 User 的函数),但未被移动的字段仍可单独访问。这是 Rust 精细所有权的一个体现,也是新手偶尔会遇到的编译错误来源。

26.4 生命周期省略规则 ​

第 9 章说「多数时候不需要手写生命周期」,因为编译器有省略规则(lifetime elision)。函数签名中每个引用参数默认有一个独立的生命周期,然后按三条规则补全返回值:

  1. 每个引用参数都有自己的生命周期参数。
  2. 如果只有一个输入生命周期,它被赋给所有输出生命周期。
  3. 如果有多个输入生命周期,但其中一个参数是 &self / &mut self,则 self 的生命周期赋给所有输出。

例子:

rust
fn first_word(s: &str) -> &str { /* ... */ }
// 规则 1:给 s 一个生命周期 'a
// 规则 2:唯一输入 'a → 输出也是 'a
// 等价于 fn first_word<'a>(s: &'a str) -> &'a str

fn longest(x: &str, y: &str) -> &str { /* ... */ }
// 规则 1:x 和 y 各有一个生命周期
// 规则 2 不适用(两个输入)
// 规则 3 不适用(没有 self)
// → 编译器无法确定,必须手写

只有当省略规则无法推断时才需要手写生命周期(如第 9 章的 longest)。

26.5 'static 生命周期 ​

'static 表示「整个程序运行期间都有效」。常见含义:

rust
let s: &'static str = "字符串字面量";  // 字面量直接存在二进制里,永远有效

'static 不是「让变量活得更久」,而是「这个引用可以活到程序结束」。常见误区:把一个普通局部变量标注成 'static 并不能延长它的生命 —— 编译器只会拒绝:

rust
fn bad() -> &'static str {
    let s = String::from("hello");
    &s   // 错误:s 是局部变量,不能返回 'static 引用
}

'static 常见于:字符串字面量、全局常量、Box::leak 主动泄漏后得到的引用、以及「要求返回类型是 'static」的 trait object 场景(如 Box<dyn Error> 常需要 'static 才能跨线程传递)。

26.6 trait 进阶:关联类型、默认方法、孤儿规则、Sized ​

(a)关联类型(associated type):trait 里用一个占位类型,实现时指定:

rust
trait Iterator {
    type Item;                    // 关联类型
    fn next(&mut self) -> Option<Self::Item>;
}

相比泛型参数,关联类型表示「每个实现只有一个确定的相关类型」,调用方不需要写 Iterator<Item = X> 一长串。

(b)默认方法:trait 方法可以带默认实现,实现者只需覆盖必要的部分:

rust
trait Summary {
    fn summarize(&self) -> String;
    fn describe(&self) -> String {   // 默认实现
        format!("默认描述:{}", self.summarize())
    }
}

(c)孤儿规则(orphan rule):只有「trait 或类型至少有一个定义在当前 crate」时,才能为该类型实现该 trait。它防止两个 crate 各自为同一个外部类型实现同一个外部 trait 产生冲突。这也是为什么有时需要「newtype 包装」:包一层自己的类型再实现外部 trait。

(d)Sized 与 ?Sized:默认所有泛型类型参数都要求 Sized(编译期大小已知,能放栈上)。T: ?Sized 放宽这个约束,允许 DST(动态大小类型,如 str、[T]、dyn Trait):

rust
fn print<T: ?Sized>(value: &T) { /* ... */ }  // 可以接受 &str、&[i32] 等

平时几乎不用关心 Sized,但它解释了为什么 dyn Trait、str 通常要放在引用或 Box 后面(因为大小不定)。

26.7 Send 与 Sync ​

Send 和 Sync 是 Rust 的两个「自动 trait」(auto trait),编译器根据类型内容自动推导,决定类型能否跨线程传递:

text
Send   :值可以安全地「转移所有权」到另一个线程。
Sync   :值的引用可以安全地「共享」给多个线程(&T 是 Send)。
  • String、i32、Vec<T>(T: Send)等都是 Send。
  • Rc<T> 不是 Send 也不是 Sync:它的引用计数不是原子的,跨线程会数据竞争,所以编译器直接禁止。
  • Arc<T> 是 Send + Sync(当 T 满足条件时):引用计数用原子操作实现。
  • Mutex<T> 是 Sync(当 T: Send 时):互斥锁保证同一时刻只有一个线程访问内部数据。

为什么这很重要:thread::spawn 要求闭包是 Send,Arc + Mutex 能跨线程共享,Rc + RefCell 只能单线程。这些约束在编译期就检查,不需要运行时靠运气 —— 这是 Rust 并发安全的来源之一。

⚠️ Send/Sync 是「安全性的自动推导」,不是「无死锁保证」:即使类型满足 Send + Sync,两个锁交叉等待仍会死锁(见第 16 章)。

26.8 迭代器进阶:三种 iter 与零成本抽象 ​

(a)三种迭代方式:

rust
let v = vec![1, 2, 3];

for x in &v {}          // iter():借用,产出 &T,之后 v 仍可用
for x in v.iter() {}    // 等价于 &v
for x in v {}           // into_iter():消费,产出 T,v 被移动
for x in &mut v {}      // iter_mut():可变借用,产出 &mut T
  • iter():只读遍历,不改变所有权。
  • into_iter():拿走所有权,适合遍历后不再需要原集合。
  • iter_mut():需要原地修改元素时用。

(b)惰性(lazy):迭代器是惰性的,链式调用 map/filter 不会立即执行,直到被 collect、for、count、sum 等「消费」:

rust
let v = vec![1, 2, 3, 4];
let even = v.iter().filter(|x| **x % 2 == 0);  // 什么都不发生
let count = even.count();                      // 这里才真正遍历

(c)零成本抽象(zero-cost abstraction):迭代器链会被编译器内联优化,iter().filter().map().collect() 通常能编译成接近手写 for 循环的机器码,没有额外的抽象开销。所以「怕迭代器慢而手写循环」在现代 Rust 里基本是多余的担心;先用清晰、可组合的迭代器写法,真的出现性能瓶颈再剖析。

26.9 String、&str 与 UTF-8 切片 ​

第 14 章已介绍 String(拥有数据)和 &str(借用切片),这里补 UTF-8 语义。

Rust 的 String/str 内部是 UTF-8 编码的字节序列。这意味着:

  • len() 返回的是「字节数」,不是「字符数」:
rust
let s = "中文";
println!("{}", s.len());          // 6,每个汉字 3 个字节
println!("{}", s.chars().count()); // 2,字符数
  • 不能按「字符索引」随机访问,&s[0..3] 必须落在字符边界上,否则 panic:
rust
let s = "中文";
let first = &s[0..3];   // OK,切出第一个完整汉字
// let bad = &s[0..2];  // panic:切在 UTF-8 字符中间

工程要点:

  • 遍历字符用 chars(),遍历字节用 bytes()。
  • 需要按「字素簇」(用户看到的字符,含 emoji、组合字符)分割时,标准库能力有限,通常引入 unicode-segmentation crate。
  • 处理用户输入、文件内容时,不要假设「一个字符 = 一个字节」,否则 emoji 和中文都会出 bug。

26.10 错误链与错误类型设计 ​

第 11 章介绍了 ?、thiserror、anyhow,这里补「错误链」和设计取舍。

? 的隐式转换:? 会把错误用 From 自动转换成函数返回的错误类型。所以给自定义错误实现 From<底层错误>,就能让 ? 无缝衔接:

rust
#[derive(Debug, thiserror::Error)]
pub enum UserError {
    #[error("database error: {0}")]
    Db(#[from] sqlx::Error),       // #[from] 自动生成 From<sqlx::Error>
    #[error("user {0} not found")]
    NotFound(u64),
}

async fn find_user(pool: &PgPool, id: u64) -> Result<User, UserError> {
    sqlx::query_as!(User, "select * from users where id = $1", id)
        .fetch_optional(pool).await?          // sqlx::Error 自动转 UserError::Db
        .ok_or(UserError::NotFound(id))       // Option → Result
}

错误链(error chain):一个错误往往由另一个错误引起,source 保存「引起本错误的底层错误」:

rust
fn load() -> Result<(), anyhow::Error> {
    let _f = std::fs::read_to_string("config.toml")
        .context("读取 config.toml 失败")?;   // 为底层 io::Error 附加上下文
    Ok(())
}

打印 {:?} 会输出整条错误链(最外层消息 + 内层原因),排查时能看到完整因果。

设计取舍:

  • 库(library)代码:定义结构化错误类型(thiserror),让调用方能匹配具体错误。
  • 应用(application)代码:用 anyhow 快速聚合,附加上下文,不必为每种错误建类型。
  • 不要用 unwrap()/expect() 代替错误处理;也不要 panic! 处理可预期错误(如「文件不存在」)。

26.11 单态化与泛型成本 ​

Rust 的泛型是「编译期单态化」(monomorphization):编译器为每个实际使用的具体类型生成一份专门代码。

rust
fn identity<T>(x: T) -> T { x }

fn main() {
    let a = identity(1i32);    // 生成 identity<i32>
    let b = identity("hi");    // 生成 identity<&str>
}

编译器生成两个版本的 identity,各自针对 i32 和 &str。结果是:

  • 运行时零开销:没有虚表查找,没有装箱,甚至能被内联。
  • 代价是「代码膨胀」:每种类型组合都生成一份代码,泛型用得越多二进制越大、编译越慢。

对比 dyn Trait(动态分发):一份代码,运行时通过 vtable 调用,有轻微间接调用开销,但代码体积小、类型可异构。这正是第 12 章「泛型 vs dyn Trait」取舍的底层原因:

text
impl Trait / 泛型  → 静态分发,编译期生成代码,零运行时开销
dyn Trait          → 动态分发,运行时 vtable,一份代码

工程上:性能敏感、类型固定的路径用泛型;需要「多种类型混在同一个集合」或「降低编译体积」时用 dyn Trait。绝大多数业务代码遵循「默认泛型/impl Trait,需要异构集合时才 dyn」即可。


27. 官方参考资料 ​

基于 VitePress 构建的个人学习文档知识库