外观
Rust 系统学习文档
适合人群:已经掌握一门主流编程语言,希望系统学习 Rust 语言、工具链、工程实践、后端服务、异步编程、数据库访问、WASM、CLI 和高性能系统开发的学习者。
学习目标:从 Rust 基础语法开始,逐步掌握所有权、借用、生命周期、trait、泛型、错误处理、并发、异步、Cargo 工程化、测试、Web 后端、数据库、部署和真实项目落地。
核对时间:2026-09-01。Rust 当前稳定版已进入 1.98.x 线;Rust 2024 Edition 已在 Rust 1.85.0 稳定。具体安装版本、发行说明和 Edition 细节请以 Rust 官方网站和官方博客为准。
目录
- Rust 是什么
- Rust 适合什么场景
- Rust 工具链:rustup、rustc、cargo
- 创建第一个 Rust 项目
- Rust 项目结构与 Cargo.toml
- 基础语法:变量、类型、函数、控制流
- 所有权、移动、复制、借用
- 引用、可变引用与借用检查器
- 生命周期
- 结构体、枚举、模式匹配
- Option、Result 与错误处理
- 泛型、trait、trait object
- 模块系统、crate、package、workspace
- 集合、字符串、迭代器、闭包
- 智能指针与内部可变性
- 并发编程:线程、消息传递、共享状态
- 异步编程:Future、async/await、Tokio
- Rust Web 后端技术栈:Axum、Actix Web、Tower
- 数据库访问:SQLx、Diesel、SeaORM
- 序列化、配置、日志与可观测性
- 测试、基准测试与代码质量
- CLI、WASM、嵌入式与 FFI
- 项目落地:后端 API 服务示例
- 学习路线:由基础到高阶
- 常见误区与排错
- 核心知识点深度补全
- 官方参考资料
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-unknown3.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 --open3.4 rustfmt
rustfmt 是格式化工具。
bash
cargo fmt团队项目应在 CI 中检查格式:
bash
cargo fmt --check3.5 Clippy
Clippy 是 Rust 的 linter。
bash
cargo clippyCI 中推荐:
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.rssrc/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 位整数 |
f64 | 64 位浮点数 |
bool | true / false |
char | Unicode 字符 |
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 有三条基本规则:
- 每个值都有一个所有者。
- 同一时间只能有一个所有者。
- 当所有者离开作用域,值会被释放。
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.rs13.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 test21.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 --release22. 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.rs23.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 基础阶段
目标:能写小程序和理解所有权。
学习:
- 变量、类型、函数、控制流。
- 所有权、移动、借用。
- struct、enum、match。
- Option、Result。
- Cargo 基础。
练习:
- 写一个 todo CLI。
- 写一个文件统计工具。
- 写几个
Result错误处理函数。
24.2 进阶阶段
目标:能写可维护模块。
学习:
- trait。
- 泛型。
- 生命周期。
- 模块系统。
- 迭代器和闭包。
- 单元测试。
练习:
- 写一个配置解析库。
- 写一个 trait 抽象存储接口。
- 写集成测试。
24.3 高级阶段
目标:能写后端服务。
学习:
- Tokio。
- Axum / Actix Web。
- SQLx / Diesel。
- Serde。
- tracing。
- Docker 部署。
练习:
- 写用户 CRUD API。
- 接 PostgreSQL。
- 加参数校验、错误处理、日志和测试。
24.4 架构阶段
目标:能把 Rust 放进真实技术体系。
学习:
- workspace。
- observability。
- 性能分析。
- unsafe 边界。
- FFI。
- WASM。
- 生产部署。
练习:
- 设计 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)。函数签名中每个引用参数默认有一个独立的生命周期,然后按三条规则补全返回值:
- 每个引用参数都有自己的生命周期参数。
- 如果只有一个输入生命周期,它被赋给所有输出生命周期。
- 如果有多个输入生命周期,但其中一个参数是
&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 Titer():只读遍历,不改变所有权。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-segmentationcrate。 - 处理用户输入、文件内容时,不要假设「一个字符 = 一个字节」,否则 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. 官方参考资料
- Rust 官网:https://www.rust-lang.org/
- Rust 官方博客:https://blog.rust-lang.org/
- Rust 2024 Edition Guide:https://doc.rust-lang.org/edition-guide/rust-2024/
- The Rust Programming Language:https://doc.rust-lang.org/book/
- Rust by Example:https://doc.rust-lang.org/rust-by-example/
- Rust Standard Library:https://doc.rust-lang.org/std/
- Cargo Book:https://doc.rust-lang.org/cargo/
- rustup book:https://rust-lang.github.io/rustup/
- rustfmt:https://github.com/rust-lang/rustfmt
- Clippy:https://doc.rust-lang.org/clippy/
- rust-analyzer:https://rust-analyzer.github.io/
- Tokio:https://tokio.rs/
- Axum:https://github.com/tokio-rs/axum
- Actix Web:https://actix.rs/
- Serde:https://serde.rs/
- SQLx:https://github.com/launchbadge/sqlx
- Diesel:https://diesel.rs/
- wasm-bindgen:https://rustwasm.github.io/docs/wasm-bindgen/
- The Embedded Rust Book:https://docs.rust-embedded.org/book/