关于 rust-dev
面向 Rust 新手及新建 CLI、服务或库项目的实用技能,讲解所有权、错误处理、初期设计选择与常见惯用法。
主要功能
- 01Rust 所有权与借用基础
- 02错误、Trait 与类型选择指南
- 03项目初始配置与 Crate 选择
- 04常见惯用法与编译器驱动重构
适用场景
- 01启动 Rust CLI、服务或库项目
- 02从其他语言转学 Rust
- 03选择类型与错误处理策略
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tenequm/ skills --skill rust-dev安装后在 Claude Code 中输入/rust-dev确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CHANGELOG.mdLICENSE.txtreferences/async-basics.mdcrate-shortlist.mddev-environment.mderror-handling.mdownership-and-types.mdperformance.mdproject-shape.mdreleasing.mdtesting.mdtraits-and-generics.md详细介绍
Rust 开发 - 第 1 天
从第一次提交开始,扎实掌握编写优质 Rust 应用的基础。这不是一本教科书。重点介绍 Rust 与其他语言的差异、影响后续一切的第 1 天决策,以及能满足大多数实际应用需求的一小组 crate。
适用场景
- 启动新的 Rust 项目(CLI、服务、库)
- 从 Python、JavaScript、Go、Java/C# 或 C++ 转向 Rust
- 在自有/借用类型、智能指针、trait 对象与泛型之间进行选择
- 选择错误处理策略(
anyhow还是thiserror) - 决定使用哪些 crate
- 配置精简但有明确主张的
Cargo.toml、clippy 和 rustfmt
第 1 天设置
# 1. Install the toolchain (rustup is the toolchain manager)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 2. Confirm components (rustfmt and clippy ship with stable, rust-src enables IDE features)
rustup component add rustfmt clippy rust-src
# 3. Create a project
cargo new my-app # binary (src/main.rs)
cargo new --lib my-lib # library (src/lib.rs)
# 4. The dev loop (memorize these four)
cargo check # fast type-check, no codegen
cargo run # build and run (binary)
cargo test # build and run tests (incl. doctests)
cargo clippy --all-targets # lint, incl. tests/ and benches/ (run before pushing)
cargo fmt # format
# 5. Manage dependencies without editing Cargo.toml by hand
cargo add tokio --features full
cargo remove tokio
cargo update # recompute Cargo.lock within existing semver ranges
cargo update 只会在 Cargo.toml 中已有的版本范围内更新。跨越主版本(从 1.x 到 2.0)需要编辑 Cargo.toml,或运行 cargo add <crate>@2。
rust-analyzer 是必备工具。 它是所有编辑器使用的语言服务器(VS Code、Zed、Neovim、Helix;RustRover 使用自己的引擎,但功能类似)。在 VS Code 中,安装 rust-analyzer 扩展,并将 rust-analyzer.check.command 设为 "clippy",这样保存时就能获得 lint 反馈。
之后想用文件监视器? 运行 cargo install --locked bacon,然后在项目中运行 bacon。第 1 天不需要它。
Rust 心智模型的 5 个要点
Rust 用编译时的内存、数据竞争和错误处理保证,换取了大多数语言中习以为常的两样东西(垃圾回收器和异常)。这项取舍决定了语言的整体设计。
1. 所有权:每个值都只有一个所有者
把值想象成实物:一本书、一个文件、一条网络连接。在任意时刻,只有一个变量拥有它。你可以:
- 移动它:
let b = a;将所有权交给b。a不复存在。 - 不可变借用它:
&a允许其他人查看它。可以有多个读取者。 - 可变借用它:
&mut a允许一个人修改它。访问是独占的。 - 克隆它:
a.clone()会创建一个深拷贝。双方各自保留一份。
当所有者离开作用域时,该值会被丢弃(释放内存、关闭文件、释放锁)。没有 GC,也不需要手动 free。这就是由编译器强制执行的 RAII。
2. 别名 XOR 可变性
在任意时刻,一份数据要么有:
- 一个可变引用(
&mut T),要么 - 任意数量的不可变引用(
&T),
绝不会两者兼有。这条规则消除了数据竞争和大多数释放后使用错误。借用检查器会强制执行这条规则。它发出警告时,说明你的数据所有权关系不清晰,而不是语言在故意刁难你。
3. 错误是值,而不是异常
Rust 没有 try/catch。可能失败的函数会返回 Result<T, E>。无法返回有用结果的函数会返回 Option<T>。编译器会强制你处理这两种情况。? 运算符只需一个字符,就能将错误沿调用栈向上传播:
fn read_config() -> Result<Config, anyhow::Error> {
let text = std::fs::read_to_string("config.toml")?; // ? = early-return on Err
let config = toml::from_str(&text)?;
Ok(config)
}
Rust 没有 null。Option<T> 的值为 None 或 Some(value)。编译器不会让你忽略 None 的情况。
4. Trait 不是 Java 接口
trait 定义行为。类型通过 impl 实现 trait。目前听起来都很熟悉。区别在于:
- 静态分发是默认方式。 编写
fn f<T: Display>(x: T)时,编译器会为每个调用时使用的具体T生成一份独立的f(单态化,类似 C++ 模板)。运行时开销为零。 - 动态分发需要显式选择,通过
dyn Trait实现(通常是Box<dyn Trait>或&dyn Trait)。每次调用都需要查询一次 vtable。 - 没有继承。 Trait 可以组合。如果你发现自己想用
Deref来“扩展”类型,请停下来,改用组合或枚举。 - 孤儿规则:你可以写
impl YourTrait for SomeoneElsesType,也可以写impl SomeoneElsesTrait for YourType,但不能让双方都来自外部。这能让依赖解析保持合理。
5. 借用检查器是设计参谋
新手最常见的错误,是把编译器错误当作需要消除的障碍。其实不是。几乎每个借用检查错误都揭示了一个关于谁拥有何物的真实问题。遇到难题时,问题很少是“怎样才能让它编译通过”,几乎总是“我真正想要的所有权关系是什么?”仔细阅读错误信息。编译器给出的信息格外丰富。
每个函数签名都要问的 3 个问题
编写函数前,先问:它需要拥有、读取还是修改输入?
fn consume(s: String) // owns: function takes responsibility, caller loses it
fn read(s: &str) // reads: function looks at it, caller keeps it
fn modify(s: &mut String) // mutates: function changes it in place
适用于 90% 情况的默认选择:
- 函数参数:优先使用
&str而不是String,使用&[T]而不是Vec<T>(这些都是切片,既接受拥有所有权的调用方,也接受借用的调用方)。 - 函数返回值:返回拥有所有权的类型(
String、Vec<T>)。返回引用意味着要处理生命周期;除非确有需要,否则先避免。 - 结构体字段:优先使用拥有所有权的类型(
String、Vec<T>)。在结构体中存储&str是新手最常见的陷阱,还会导致每个持有该结构体的类型都需要添加生命周期标注。
第一天决策表
对最先遇到的各种选择,给出一句话答案。
| 决策 | 默认选择 | 选择另一项的时机 |
|---|---|---|
String 与 &str(结构体字段) | String | 几乎绝不选 &str,除非你有充分理由并且理解生命周期 |
String 与 &str(函数参数) | &str | 只有必须拥有并在内部存储它时才使用 String |
Vec<T> 与 &[T](参数) | &[T] | 只有必须拥有所有权时才使用 Vec<T> |
Box<T>、Rc<T> 与 Arc<T> | Box<T>(单一所有者,堆分配) | 在线程间共享所有权时使用 Arc<T>。不要默认使用 Rc<T>;使用 Arc<T>,这样改用异步时就无需重构 |
RefCell<T> 与 Mutex<T> | Mutex<T>(或 RwLock<T>) | 原因相同:它适用于异步和线程,而 RefCell 不适用 |
Option<T> 与 Result<T, E> | “没有值”时用 Option<T>,“因某种原因失败”时用 Result<T, E> | 如果缺失情况包含调用方应处理的含义,则用 Result |
dyn Trait 与 impl Trait / <T: Trait> | 泛型(<T: Trait> 或 impl Trait)——静态分发 | 需要异构集合(Vec<Box<dyn Animal>>)时使用 Box<dyn Trait> |
| 应用代码中的错误 | 全部使用 anyhow::Result<T> | - |
| 库代码中的错误 | 使用 thiserror 派生的枚举 | 绝不要在公共库 API 中使用 Box<dyn Error>——这会迫使调用方进行向下转型 |
&self、&mut self 与 self | 获取器用 &self,设置器用 &mut self,构建器/消耗操作用 self | - |
| 模块布局 | 先使用内联模块,文件变长后再拆分 | 一个模块对应一个文件是 Java/C# 的习惯,不是 Rust 的习惯 |
尽早掌握的惯用写法
几乎每个 Rust 程序中都会出现这些写法。第一周就要学会。
用 ? 传播错误。 用一个字符替代九行 match。
let body = reqwest::get(url).await?.text().await?;
优先使用迭代器链,而非手动循环。 编译后的机器码与手写循环相同(LLVM 会内联闭包)。Rust 的惯用写法偏函数式。
let active_emails: Vec<String> = users
.iter()
.filter(|u| u.active)
.map(|u| u.email.clone())
.collect();
match 穷尽性检查。 在枚举中添加一个新变体后,所有未处理该变体的 match 都会导致编译错误。善用这一点。它是任何语言中最强大的重构工具之一。
让编译器推动重构。 同样的技巧也适用于结构体:添加一个没有 Default 的必填字段,所有现有构造函数都会导致编译错误——这会免费列出每个需要更新的位置,不会有遗漏。
使用 if let 和 let else 处理常见的单分支匹配。
if let Some(name) = user.name { println!("hi {name}"); }
let Some(name) = user.name else { return Err(anyhow!("no name")); };
// `name` is in scope from here on, no nesting
使用 From / Into 进行类型转换。 实现 From,就能免费获得 Into。? 会使用 From 自动转换错误类型。
使用 Option / Result 上的组合子。 使用 match 之前,先考虑 .map、.and_then、.unwrap_or、.unwrap_or_else、.ok_or。
派生宏。 使用 #[derive(Debug, Clone, PartialEq)],就能免费省去 80% 的样板代码。处理 JSON 时,添加 #[derive(Serialize, Deserialize)]。
近期语法糖。 值得了解的稳定功能,按新旧顺序排列:bool::ok_or 和 ok_or_else(1.98)可直接将条件转换为 Result(ready.ok_or(Error::NotReady)?)。cfg_select! 是对 cfg 谓词进行编译时 match 的宏,可替代 cfg-if crate 的大多数用法;if let 守卫也可用于 match 分支(match x { Some(v) if let Ok(n) = v.parse::<i32>() => ... })——这两项功能均在 1.95 稳定。assert_matches! 和 debug_assert_matches!(1.96)会根据模式而非相等性进行断言,这才是枚举的自然断言方式。还有两个较早的功能,你会经常看到,不要误以为它们很新奇:let 链(if let Some(x) = a && x > 3 { ... }),自 1.88 起在 2024 版中稳定;以及异步闭包(async || { ... },以及 AsyncFn 系列约束),自 1.85 起稳定。
如果你来自 X,以下几点可能会让你措手不及
如果你来自 Python 或 JavaScript:
- 对堆分配的值(如
String、Vec)执行let b = a;会移动它。之后不能再使用a。使用&a借用,或使用a.clone()复制。 - 没有 null。你必须使用
Option<T>。 - 没有异常。使用
Result<T, E>和?。编译器不允许你忽略错误。 - 没有继承。使用组合、trait 和枚举。
- 变量默认不可变。要修改变量,添加
mut。引用也一样:&与&mut。 - 允许且惯用的做法是重新声明同名变量:
let input = input.trim();会用新的绑定遮蔽旧绑定,新绑定的类型也可能不同。这不是修改,而是 Rust 在不另造一个input2的情况下就地转换值的方式。 - 整数类型必须显式指定,索引需要使用
usize。整数溢出的处理还会因构建方式而异:debug 构建“包含整数溢出检查,运行时会导致程序 panic”,而--release会移除这些检查,并执行“二进制补码回绕”。即使测试套件通过,生产环境中仍可能发生回绕。因此,对于算术运算确实可能触及边界的情况,应使用checked_*/saturating_*/wrapping_*,而不是依赖 debug panic 来发现问题。strict_add及同类方法(自 1.91 起稳定)在所有构建中都会在溢出时 panic;在[profile.release]下设置overflow-checks = true,则可在生产环境中保留 debug 行为。此外,整数类型之间的as转换会静默截断(300_u32 as u8的结果是44)——只要值可能超出范围,就使用u8::try_from(x)?。 String不支持索引。s[0]无法编译——要了解该使用什么,请参阅references/ownership-and-types.md。
如果你来自 Go:
- 像 Go 一样将错误作为值处理,但要用
?代替if err != nil。 - 没有
nil。使用Option<T>。 - 没有 GC 和 goroutine:使用所有权与借用,以及基于
tokio的 async/await。异步模型是协作式的(await是显式让出执行权的时机),而非抢占式。 interface{}对应 trait。默认使用泛型进行静态分派;只有在确有需要时才使用Box<dyn Trait>。- 默认静态链接。二进制文件更大,但自包含。
- 在应用代码中,
panic!应仅用于不可恢复的错误;不要像 Go 那样用它来“记录日志并继续”。
如果你来自 Java 或 C#:
- Trait 默认并非带虚分派的接口。
<T: Trait>会进行单态化。dyn Trait是可选的动态版本。 - 没有 null 引用。使用
Option<T>。 - 没有异常。使用
Result<T, E>和?。 - 没有类继承。使用枚举表示和类型,使用 trait 表示共享行为。
- 没有 GC:由所有权与借用决定生命周期。
Arc<T>最接近 Java 引用。 - 泛型会进行单态化,而非类型擦除。
如果你来自 C++:
- 和 RAII 类似,但借用检查器会在编译时强制执行。
- 没有复制/移动构造函数。
Clone表示显式克隆,Copy是表示可廉价按位复制的标记 trait。 - 安全代码中不会出现未定义行为(理论上如此)。
&是经过编译时检查的借用,并非裸指针。裸指针也存在(*const T、*mut T),但解引用需要使用unsafe。- 智能指针包括
Box<T>(unique_ptr)、Rc<T>(shared_ptr,单线程)和Arc<T>(shared_ptr,线程安全)。 - 宏具有卫生性。过程宏(derive、属性、类函数宏)是
serde、tokio::main等的实现方式。
Crate 候选清单
这些 crate 涵盖了大多数实际应用。按需添加;不必全部使用。
| Crate | 提供的功能 |
|---|---|
serde + serde_json | 序列化。使用 #[derive(Serialize, Deserialize)] 即可完成 |
tokio | 异步运行时。#[tokio::main]、tokio::spawn、异步 I/O |
anyhow | 应用错误类型。anyhow::Result<T>、bail!、context() |
thiserror | 库的错误枚举。#[derive(thiserror::Error)] |
clap | CLI 参数解析。使用 #[derive(Parser)] 即可获得 CLI |
reqwest | HTTP 客户端。默认异步,也提供阻塞功能 |
tracing + tracing-subscriber | 结构化日志。任何异步代码的默认选择(取代 log) |
axum | Web 框架。基于 tokio + hyper + tower 构建。2026 年的默认选择 |
sqlx | 数据库访问。支持异步、编译时检查查询。支持 PostgreSQL、MySQL、SQLite |
chrono | 日期和时间。维护者于 2026 年 1 月宣布将逐步弃用,并建议新代码使用 jiff。jiff(BurntSushi)是其继任者,但截至 2026 年 9 月仍处于 1.0 之前(0.2.x,1.0 跟踪问题仍在处理中,尚无日期)。如今,若需要与 serde/sqlx 集成,请选择 chrono;若能接受 1.0 之前的变动,则可选择 jiff |
每个 crate 的最简示例请参见 references/crate-shortlist.md。
应避免的首要反模式
每个新手的代码审查中都会出现这些错误。请避免。
- 在结构体中存储
&str(或任何引用)。 这会导致生命周期注解扩散到每个调用方。在有性能分析器支持的理由之前,请使用String。 - 动辄使用
Rc<RefCell<T>>(或Arc<Mutex<T>>)来模拟 Python/JS 对象图。 首先要问自己是否真的需要共享可变状态——通常,使用普通所有权或通过通道传递数据会更简洁。如果确实需要,优先使用Arc<Mutex<T>>,而不是Rc<RefCell<T>>,这样以后添加线程时就无需重构。 - 在库的公共 API 中使用
Box<dyn Error>。 这会迫使调用方进行向下转型。请用thiserror定义具类型的错误枚举。在二进制程序内部可以接受Box<dyn Error>,但绝不要在已发布的库中使用。 - 在原型和测试之外使用
.unwrap()和.expect()。 使用?并传播错误——对于Option,.ok_or_else(|| anyhow!(...))?可以完成转换。如果某个值确实不可能缺失,优先使用.expect("why it cannot fail"),而不是.unwrap():其中的消息就是用于说明该不变量的注释。 - 一味地
.clone(),直到代码能编译。 有时克隆是正确的做法,但如果你到处添加.clone()来绕过借用检查器,说明设计有问题。退一步,问问自己关于所有权的 3 个问题。 - 试图通过
Deref实现继承。Deref适用于类似智能指针的包装类型,不适用于面向对象风格的“扩展”。请使用组合。 - 动辄使用
unsafe。 应用开发者几乎永远不需要它。unsafe不会关闭借用检查器;它允许你执行 5 种特定操作(解引用原始指针、调用不安全函数、访问可变静态变量、实现不安全 trait、访问联合体字段),前提是你已手动验证这些不变量。 - 使用
.lines()或read_line读取不可信输入。 这些方法会进行不受限制的分配。BufRead::read_line自己的文档警告说,“攻击者可能持续发送字节,而始终不发送换行符或 EOF”——.lines()也会继承这一行为,因为它在底层对每一项都调用read_line。无论哪种方式,恶意或格式错误的对端都可能让你的进程耗尽内存。使用Read::take(n)限制读取量,并使用BufRead::skip_until(自 1.83 起稳定)丢弃过长的行。for line in reader.lines()是每篇教程都会教的第一件事,但几乎没有教程提到这一点。 - 将
std::fs::write用于不可丢失的数据。 它会先截断文件,再原地写入,因此写入期间发生崩溃或断电,会留下长度为零或只写入一部分的文件——而且成功返回并不意味着字节已写入磁盘。对于状态文件和清单:写入同一目录中的临时文件,调用File::sync_all(),再用rename覆盖目标文件;在 Unix 上,还要对父目录调用sync_all(),确保重启后重命名操作仍然有效。(tempfile::NamedTempFile::persist会执行重命名,但不会执行同步。)
可暂缓学习的内容
你在第 1 天不需要掌握这些内容。有些你可能永远用不上。
- 结构体字段中的生命周期。 使用拥有所有权的类型即可避免。等到你确实需要时,自然会明白。
Pin、Future内部实现、手动实现poll。 只需编写async fn并使用.await。unsafe和 FFI。 应用代码几乎从不需要。- 过程宏。 属于库作者的领域。
- 高阶 trait 约束(
for<'a>)、协变性、PhantomData。 属于专家领域。 Cell、OnceCell、LazyLock、MaybeUninit。 有明确理由时再使用。
最简 Cargo.toml
单 crate、edition 2024,并采用有主见的 lint 设置。可直接放入新项目。
[package]
name = "my-app"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
[dependencies]
[dev-dependencies]
[profile.release]
lto = "thin"
codegen-units = 1
# =============================================================================
# Lints. Loose-but-helpful: deny obvious bugs, warn on common smells, leave
# room to learn. Upgrade to clippy::pedantic later if you want the full ride.
# =============================================================================
[lints.rust]
# "deny", not "forbid": forbid cannot be lifted by a local #[allow] with a
# SAFETY comment, and that bites on ordinary mmap, not just FFI.
unsafe_code = "deny"
unreachable_pub = "warn"
[lints.clippy]
all = { level = "deny", priority = -1 }
# Idiomatic helpers
# Note: uninlined_format_args moved to clippy::pedantic (allow-by-default)
# during the 1.89/1.90 cycle, so an explicit warn keeps the nudge active.
uninlined_format_args = "warn"
semicolon_if_nothing_returned = "warn"
implicit_clone = "warn"
# Smells in non-prototype code
dbg_macro = "warn"
todo = "warn"
print_stdout = "warn" # use `tracing::info!` instead in real apps
确实需要静默某个 lint 时,优先使用 #[expect(clippy::lint_name, reason = "why")](自 1.81 起稳定),而不是 #[allow]:当 lint 不再触发时,expect 会发出警告,因此过时的抑制设置会显现出来,而不是越积越多。Clippy 的 allow_attributes 和 allow_attributes_without_reason(都属于选择启用的 restriction 组)会强制执行这一做法。有一点需要注意:如果某个 lint 只在一种操作系统上触发,就需要使用 #[cfg_attr(unix, expect(...))];否则在其他所有系统上,expect 都不会兑现,并会发出警告。
rustfmt.toml
style_edition = "2024"
edition = "2024"
这样就够了。rustfmt 的默认设置很好。有些团队会添加 use_small_heuristics = "Max",让更多代码保持在单行内。imports_granularity 和 group_imports 等高级选项截至 2026 年 10 月仍仅支持 nightly 版(rustfmt 跟踪议题 #4991 和 #5083)。
开始编辑前运行 cargo fmt(或者单独提交任何已有的格式偏差),避免格式噪声混入差异中,并将 cargo fmt --check 作为单独的 CI 步骤。
rust-toolchain.toml(可选但推荐)
为每个项目固定工具链版本,让团队所有成员都使用相同的 Rust 版本——但前提是指定精确版本。channel = "stable" 表示“这台机器上最近安装的任意 stable 版”,因此两台机器可能悄然使用不同版本;而以精确的 rustc 版本为键的编译器缓存也无法在它们之间共享产物。请有计划地更新固定版本(参见 references/dev-environment.md)。
[toolchain]
channel = "1.99.0"
components = ["rustfmt", "clippy", "rust-src"]
profile = "minimal"
该文件属于 rustup 的功能。来自 Nix、发行版软件包或 Homebrew 的 rustc 会忽略它,因此在这些机器上会使用已安装的任意编译器。即使使用 rustup,它也不是优先级最高的设置:目录的 rustup override set(以及 RUSTUP_TOOLCHAIN 和 cargo +toolchain)都会覆盖它,因此遗忘的 override 会悄然让这个文件失去作用。rustup show 会告诉你当前启用的是哪个工具链,以及原因;rustup override unset 可以清除它。
.gitignore
/target
提交 Cargo.lock。cargo new 会将它纳入版本控制,Cargo 指南也建议:“如有疑问,就将 Cargo.lock 纳入版本控制系统。”FAQ 刻意没有将此作为普遍规则——“是否提交取决于你的包的需求”——但对于应用程序,答案毫无疑问是肯定的;如今,即使是库,提交它也已成为通常做法。
项目结构
my-app/
src/
main.rs # binary entry point: fn main()
lib.rs # OR a library crate root
config.rs # module: declared as `mod config;` in main.rs/lib.rs
api/ # nested module
mod.rs # OR `api.rs` next to api/ folder (2018+ style preferred)
users.rs
bin/ # extra binaries: src/bin/admin.rs -> `cargo run --bin admin`
examples/ # runnable examples: `cargo run --example demo`
tests/ # integration tests (each file is its own crate)
smoke.rs
Cargo.toml
Cargo.lock
rust-toolchain.toml
rustfmt.toml
.gitignore
在文件变长之前,使用 mod { ... } 内联模块,然后再拆分。不要预先拆分。
默认情况下,所有内容都是私有的,包括对父模块也是如此。mod config; 会使模块存在,但不会让其中的任何内容变得可访问。这是早期最常见的“为什么我无法调用这个”错误的根源;解决方法是在条目上添加可见性关键字(如果模块嵌套,还要在模块上添加):pub 会向外部公开,pub(crate) 则只在你自己的 crate 内公开。对于不属于库已发布 API 的内容,pub(crate) 是合适的默认选择——它能让下表中的 unreachable_pub lint 提供有用的信息。
上面的布局只有一个 crate。如果你需要第二个 crate——例如共享库加 CLI,或服务加其客户端——就需要使用 Cargo 工作区,以及从单个 crate 扩展时所需的 Cargo.toml 配置:功能标志、构建脚本,以及 rust-version 实际控制的内容。详见 references/project-shape.md。
学习路径
- The Rust Book (https://doc.rust-lang.org/book/) - 权威、免费且内容最新。Brown University 的交互式版本 (https://rust-book.cs.brown.edu/) 增加了测验和可视化内容。
- Rustlings (https://github.com/rust-lang/rustlings) - 配合 The Book 进行练习。
- 100 Exercises to Learn Rust (https://rust-exercises.com/) - 可替代或补充 Rustlings,内容稍新。
- Rust for Rustaceans(Jon Gjengset)- 面向初学者进阶的书。熟练掌握基础后再读。
- Zero to Production in Rust(Luca Palmieri)- 如果你要构建后端服务,可以阅读。注意:这本书使用
actix-web,而axum是 2026 年的默认选择;其中的模式很容易迁移。
查询语法时,可参考 Rust by Example (https://doc.rust-lang.org/rust-by-example/)。
查看精选 crate 推荐时,可参考 blessed.rs (https://blessed.rs/crates)。
参考文档
详细内容位于 references/。遇到相关主题时阅读对应文档。
- ownership-and-types.md - 所有权、借用、生命周期、
String/&str/Cow、字符串为何不能通过索引访问、切片、HashMap和entryAPI、智能指针、自引用结构体陷阱 - error-handling.md -
Result、?、anyhow与thiserror的用法模式、在边界处而非之前包装错误、自定义错误枚举、何时适合使用panic! - traits-and-generics.md - 将 trait 用作约束、
dyn与impl Trait和泛型的区别、常见 derive、From/Into/Display/Debug、闭包和Fn/FnMut/FnOnce系列、全面实现、孤儿规则 - async-basics.md - 先了解线程和
mpsc,再学 async、tokio、#[tokio::main]、.await、Send/Sync、有界并发、SIGTERM 下的取消与优雅关闭、常见陷阱(在 async 中阻塞、跨越.await持有MutexGuard) - crate-shortlist.md - 上述 8 个 crate 各自的最简用法示例
- project-shape.md - 超出单个 crate 的项目组织:工作区和继承的依赖、
[workspace.lints]、功能标志、build.rs、rust-version的作用、#[non_exhaustive] - testing.md - 测试什么、跳过什么、务实的测试组织方式、保持测试套件快速运行、高价值的最小工具集
- dev-environment.md - 快速构建循环、构建缓存(kache 的设置及其问题、sccache、rust-cache)、控制
target/的大小、针对平台的链接器指南、CI、文件监视器 - releasing.md - 发布二进制文件:
dist与手动搭建的release-plz+cargo-zigbuild流水线、[profile.dist]、从一个运行器交叉编译所有目标、分发到 binstall/Homebrew/Nix,以及确保发布的 crate 包含内容符合预期 - performance.md - 先分析性能再优化、使用 criterion/divan 进行基准测试、真正能提升运行时性能的方法