环境搭建与工具链
本章是教程的起点:把「写 Rust」这件事从零跑通——装工具链、建工程、跑起来、看懂报错。 前置知识:会一门有包管理器的语言(Python / Java / Node / Go 任选其一); 建议先读 学习路线与环境准备,了解本站的整体安排。 术语译名以 附录 A · 术语中英对照 为准,本章只补它的空白。
本章目标
- 能说清 Rust 的编译模型(AOT 编译 + 无 GC + 零开销抽象)与 Python / Java / Go 的定位差异。
- 能独立完成 rustup 安装、版本验证、组件安装与卸载,并解释
stable/beta/nightly的关系。 - 能用
cargo new/run/build/check/clean完成一个项目的完整开发循环,并说明 debug 与 release 的差别。 - 能解释
Cargo.toml每个小节的作用,会写依赖版本约束、feature、profile 与[lints]。 - 能搭出一个最小的 workspace,并用
cargo run -p指定成员运行。 - 能读懂
cargo build的报错信息(错误码、-->定位、help:建议),并知道dbg!、{:#?}、RUST_BACKTRACE怎么用。
Rust 是什么:三种设计选择
Rust 的自我定位是「一门让每个人都能写出可靠且高效软件的通用语言」。对已有语言经验的读者,只要抓住 三个设计选择,就能预测后面所有语法的形状。
编译型:没有虚拟机,没有运行时
rustc 把源码预先编译(ahead-of-time,AOT)成目标平台的本地机器码,直接链接成 .exe 或者被别的程序 调用的静态/动态库。发布出去的程序不依赖「Rust 运行时」这种中间层(std 会被静态链接进二进制)。
- Python 交给 CPython 解释字节码,Go 把运行时(含调度器、GC)一起编进二进制,Java 编成字节码交给 JVM。
- Rust 的二进制里没有解释器、没有 GC 线程、没有类加载器;一个
hello world在 Windows 上约 300 KB。
无 GC:内存安全靠编译期检查,而不是运行期回收
这是最需要「换脑子」的一点。Rust 不用垃圾回收(garbage collection,GC),而是用所有权(ownership)、 借用(borrowing)和生命周期(lifetime)三条规则,在编译期静态地证明「这块内存什么时候可以释放、 谁能读写它」。检查不通过就编译失败,通过了就等价于手工 C 语言级别的内存管理,且没有运行期开销。
💡 对照:Python / Java / Go 程序员第一次写 Rust 会疯狂撞上
cannot borrow ... as mutable这类错误。 那不是「Rust 麻烦」,而是其他语言把内存问题的账单推迟到了运行期(Python 靠 GC,Java 靠 GC, C/C++ 靠人)。Rust 把账单提前到编译期,代价是你要学会描述数据的流向。详见〈变量与流程控制〉。
零开销抽象(zero-cost abstraction)
「你不用的东西,不付代价;你用的东西,不会比手写更差。」具体靠这些机制实现:
- 泛型单态化(monomorphization):
Vec<T>编译时按实际类型展开成具体代码,没有 Java 那样的装箱与虚表查找。 - trait 静态分发:默认在编译期决定调用哪个实现,等价于直接函数调用。
- 迭代器与闭包:
map/filter/fold链式写法编译后通常和手写for循环生成同样质量的机器码。 - 编译期计算(
const求值)、内联(inlining)、无别名信息(noalias)优化等。
🧠 原理:零开销抽象不是「编译器魔法」,而是类型系统把信息暴露给优化器的结果。因为借用检查器 能证明「这两个引用不可能指向同一块内存」,LLVM 才敢做 C 语言里做不到的优化。
与其他语言的定位对比
| 维度 | Rust | Python |
|---|---|---|
| 执行模型 | AOT 编译为本地机器码 | 解释器 + 字节码(CPython) |
| 内存管理 | 编译期所有权,无 GC | 引用计数 + 分代 GC |
| 空值处理 | 无 null,用 Option<T> | None,随处可为空 |
| 错误处理 | Result<T, E> 返回值 + ? 传播 | 异常 try/except |
| 泛型 | 单态化,零开销 | 动态类型,靠鸭子类型 |
| 并发安全 | 编译期 Send/Sync 检查 | GIL 限制 CPU 并行 |
| 编译速度 | 慢(借用检查 + 单态化) | 无编译 |
| 典型场景 | 系统 / CLI / 嵌入式 / WASM | 数据科学、脚本、AI 胶水 |
| 维度 | Rust | Java | Go |
|---|---|---|---|
| 执行模型 | AOT 编译为本地机器码 | 字节码 + JIT(HotSpot) | AOT 编译 + 内嵌运行时 |
| 内存管理 | 编译期所有权,无 GC | 分代 GC | 并发三色标记 GC |
| 空值处理 | 无 null,用 Option<T> | null + Optional | nil(指针/接口/map/slice) |
| 错误处理 | Result<T, E> 返回值 + ? 传播 | 受检 / 非受检异常 | error 返回值 + if err != nil |
| 泛型 | 单态化,零开销 | 类型擦除的泛型 | 1.18 起支持泛型(有 GC 开销) |
| 并发安全 | 编译期 Send/Sync 检查 | 运行时检查,无编译期保证 | 运行期 race detector |
| 编译速度 | 慢(借用检查 + 单态化) | 中等 | 极快 |
| 典型场景 | 系统 / CLI / 嵌入式 / WASM | 企业后端、Android | 云原生后端、CLI、网络服务 |
⚠️ 陷阱:不要用「Rust 更快」作为唯一理由来选它。Rust 真正的差异化价值是在保持 C/C++ 级性能的 同时,把内存安全和线程安全变成编译期错误。如果你的问题是「Python 太慢」,先考虑 numpy / 换算法, 再考虑 Rust。
rustc 与 cargo 的分工
这两个命令的关系,等价于 javac 与 mvn、gcc 与 make:
| 工具 | 职责 | 你会直接用它吗 |
|---|---|---|
rustc | 真正的编译器:解析 → 类型检查 → 借用检查 → MIR 优化 → LLVM 代码生成 → 链接 | 很少。单文件试验、看编译细节时会用 |
cargo | 包管理 + 构建系统 + 测试 + 文档 + 发布:读 Cargo.toml,决定用什么参数调用 rustc | 天天用,它是 Rust 的工作入口 |
cargo 在幕后把完整参数喂给 rustc。想知道它到底传了什么,用 cargo build -v(verbose)就能看到整条 命令行——这也是排查链接错误的关键手段。
安装与验证
安装 rustup(Windows 官方方式)
Rust 官方推荐的安装器是 rustup——它同时是「工具链管理器」和「代理命令」。从 官网安装页(官方,含 Windows 的 rustup-init.exe 下载)或 rustup 官方文档(官方,rustup 全部子命令参考)获取。
Windows 上 rustup 默认使用 MSVC 工具链(x86_64-pc-windows-msvc),它需要 C/C++ 链接器 (用于最终链接和部分系统库)。两种获得方式:
powershell
# 方式一(推荐):winget 安装 VS 2022 生成工具,只装 C++ 生成工具和 Windows SDK,不装完整 IDE
winget install Microsoft.VisualStudio.2022.BuildTools
# 方式二:下载 Visual Studio 2022 生成工具安装器,手动勾选
# 「使用 C++ 的桌面开发」工作负载(含 MSVC v143 与 Windows 10/11 SDK)安装完成后,在 PowerShell 里执行 rustup 安装器并接受默认选项:
powershell
# 交互式安装:默认选项即 stable + msvc + 修改 PATH
winget install Rustlang.Rustup
# 检查 PATH 是否生效(新开一个 PowerShell 窗口再执行)
rustup --version⚠️ 陷阱:装完必须新开终端,因为 PATH 是进程启动时读取的。若
rustup仍找不到,检查系统环境 变量里是否存在%USERPROFILE%\.cargo\bin(自定义过CARGO_HOME时则是%CARGO_HOME%\bin)。
验证安装
powershell
# 一看默认宿主平台与已装工具链
rustup show
# 二看编译器与构建工具版本(本章基准:rustc 1.98.1 / cargo 1.98.1)
rustc --version
cargo --version
# 三看全部组件是否齐(rustfmt / clippy 应显示 installed)
rustup component list --installedrustup show 的典型输出(Windows 默认安装):
text
Default host: x86_64-pc-windows-msvc
rustup home: C:\Users\<你>.rustup
installed toolchains
--------------------
stable-x86_64-pc-windows-msvc (active, default)
active toolchain
----------------
name: stable-x86_64-pc-windows-msvc
active because: it's the default toolchain
installed targets:
x86_64-pc-windows-msvc安装常用组件
component 是依附于某个工具链的附加件,换工具链要重装:
powershell
# rustfmt=格式化器,clippy=静态检查器,rust-analyzer=编辑器语言服务器
rustup component add rustfmt clippy rust-analyzer
# 查看某组件是否可用/已装
rustup component list | Select-String clippy验证:
powershell
cargo fmt --version
cargo clippy --version
rust-analyzer --versionstable / beta / nightly 与工具链管理
Rust 每 6 周发一个 stable 版本,发布节奏是三通道(release channel):
stable:正式版,本书主线只用它(rustc 1.98.1)。beta:下一个 stable 的候选,用于提前验证。nightly:每夜构建,所有 unstable 特性(需#![feature(...)])只在这里可用。
powershell
# 安装并切换默认工具链
rustup toolchain install nightly
rustup toolchain install 1.98.1
rustup default stable
# 临时用某个工具链跑一条命令(不改变默认值)
rustup run nightly rustc --version
# 列出/卸载工具链
rustup toolchain list
rustup toolchain uninstall beta
# 更新:rustup 自身 + 默认工具链的所有组件
rustup self update
rustup update交叉编译目标(target):target 指「产物跑在哪个平台」。
powershell
rustup target list --installed # 当前已安装的目标
rustup target add x86_64-pc-windows-gnu # 加一个 GNU ABI 目标(示例)
rustup target add wasm32-unknown-unknown # 加 WebAssembly 目标(示例)
cargo build --target wasm32-unknown-unknown # 按目标构建(部分目标还需额外链接器)让项目固定工具链:在项目根目录放 rust-toolchain.toml,团队和 CI 就会用同一版本:
toml
# rust-toolchain.toml:进入该目录时 rustup 自动切换到指定通道
[toolchain]
channel = "1.98.1"
components = ["rustfmt", "clippy"]
targets = ["x86_64-pc-windows-msvc"]卸载
powershell
# 卸载所有工具链、组件、cargo install 装的二进制,并清理 PATH 中由 rustup 添加的项
rustup self uninstall卸载后目录 %RUSTUP_HOME%(默认 %USERPROFILE%\.rustup)与 %CARGO_HOME% (默认 %USERPROFILE%\.cargo)可手工删除。想换安装盘符,就在安装前设置这两个环境变量, 例如把 RUSTUP_HOME 指到 D:\rustup、CARGO_HOME 指到 D:\cargo。
第一个程序:main.rs 与 Cargo 工程
最小单文件程序
新建 main.rs,内容如下。它演示的是:每个可执行 Rust 程序都必须有一个 fn main() 作为入口, println! 是一个宏(macro,注意末尾的 !),不是函数。
rust
// main.rs:单文件程序的最小骨架
fn main() {
// 与 Java 的 main(String[]) / Python 的 __main__ 对应,但返回值与参数由类型系统约束
println!("Hello, Rust 2024!");
}在 PowerShell 里直接编译运行(cargo 缺席时 rustc 也能独立工作):
powershell
rustc --edition 2024 main.rs # 生成 main.exe 与 main.pdb
.\main.exe输出:
Hello, Rust 2024!
🧠 原理:
fn main()的返回值不是随意的。返回()(默认)时进程退出码为 0;返回Result<(), E>时,Err会让进程以非 0 码退出并打印错误——这是 CLI 工具的常用写法(〈结构体与 trait〉一章讲)。
用 cargo new 创建工程
真实项目一律用 Cargo 创建,它一次生成目录结构、清单文件、.gitignore 和 Git 仓库:
powershell
# 二进制(binary,可执行程序)工程:默认就是 --bin
cargo new hello_cargo
cargo new cli_tool --bin
# 库(library,给别人依赖的 crate)工程
cargo new my_lib --lib
# 不初始化 Git 仓库;--name 可指定包名而非目录名
cargo new scratch --vcs none
cargo new playground --name my_playground --edition 2024cargo new hello_cargo 生成的结构(典型输出):
text
hello_cargo/
├── .git/ # 自动初始化的 Git 仓库(用 --vcs none 可跳过)
├── .gitignore # 内容为 /target,即忽略构建产物
├── Cargo.lock # 首次构建后生成,锁定依赖的精确版本
├── Cargo.toml # 工程清单(manifest),相当于 package.json / pom.xml
└── src/
└── main.rs # cargo new 生成的模板生成的 src/main.rs 内容就是:
rust
fn main() {
println!("Hello, world!");
}若目标目录已存在,用 cargo init(就地初始化,不新建目录):
powershell
cargo init . # 在当前目录创建工程
cargo init --lib . # 就地创建库工程四个日常命令
| 命令 | 作用 | 何时用 |
|---|---|---|
cargo run | 编译并运行二进制目标 | 开发循环主力 |
cargo build | 只编译,产物在 target/ | 需要二进制文件本身 |
cargo check | 只做类型检查与借用检查,不生成机器码 | 改完代码想快速看是否通过,比 build 快数倍 |
cargo clean | 删除整个 target/ 目录 | 排查诡异增量编译问题、回收磁盘 |
powershell
cargo run # 编译 + 运行
cargo run -- --name Rust # -- 之后的参数传给程序本身,不是传给 cargo
cargo build # debug 构建
cargo build --release # release 构建(优化)
cargo check # 最快的一遍类型检查
cargo clean # 清空 target/
cargo build -v # 打印真实 rustc 命令行,排查链接/参数问题用⚠️ 陷阱:
cargo run -- arg1 arg2里的--必须有。写成cargo run arg1时,cargo 会把arg1当成自己的参数解析并报错。这是 PowerShell/bash/CMD 通用的约定。
cargo check 的价值在于:Rust 编译时间主要花在代码生成与优化上,而你改代码时 90% 的错误 (类型、借用、拼写)在前端检查阶段就能发现。先 check 再 run 是社区默认习惯。
powershell
# 典型输出(注意没有 linking 阶段;括号里是你项目的实际路径)
# Checking hello_cargo v0.1.0 (C:\path\to\hello_cargo)
# Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
cargo checkdebug 与 release profile
cargo build 默认使用 dev profile(界面上显示为 dev),cargo build --release 使用 release profile。两者的默认差异:
| 设置 | dev(默认) | release |
|---|---|---|
opt-level | 0(不优化) | 3(激进优化) |
debug(调试符号) | true | false |
debug-assertions | true | false |
overflow-checks(整数溢出检查) | true | false |
incremental(增量编译) | true | false |
strip | false | false |
⚠️ 陷阱:release 下整数溢出不会 panic,而是回绕(wrapping),这可能掩盖 bug。若你的业务 不能容忍回绕(如金额、长度计算),在
[profile.release]里显式打开overflow-checks = true。
编译产物在哪
所有产物集中在 target/,按 profile 分子目录:
text
target/
├── debug/ # dev profile
│ ├── hello_cargo.exe # 可执行文件(Windows 带 .exe)
│ ├── hello_cargo.pdb # 程序数据库(调试符号,Windows MSVC 特有)
│ ├── hello_cargo.d # 依赖描述文件,增量编译用
│ ├── deps/ # 依赖库的 rlib/rmeta 等中间产物
│ ├── incremental/ # 增量编译缓存
│ ├── build/ # build.rs 构建脚本产物
│ └── .fingerprint/ # 缓存指纹,决定「要不要重新编译」
└── release/ # release profile,结构同上💡 对照:
target/≈node_modules/+dist/+ 编译缓存的合体,但它永远不该提交到 Git。cargo new生成的.gitignore已经写好/target。
目录结构详解
一个成熟项目的长相
text
my_app/
├── Cargo.toml # 清单:包元数据、依赖、feature、profile、lints
├── Cargo.lock # 锁定文件:记录依赖的精确版本与校验和(由 cargo 生成,不要手改)
├── rust-toolchain.toml # 可选:固定工具链版本
├── rustfmt.toml # 可选:格式化配置
├── build.rs # 可选:构建脚本,编译前执行(生成代码、编译 C 库等)
├── .gitignore # 至少含 /target
├── src/
│ ├── main.rs # 二进制入口(有它才是 bin crate)
│ ├── lib.rs # 库入口(有它才是 lib crate)
│ ├── bin/ # 额外的可执行目标,每个 .rs 一个 bin
│ │ └── admin.rs # cargo run --bin admin
│ ├── models.rs # 业务模块(由 mod models; 声明)
│ └── models/ # 同名的 models/ 目录 + models.rs 可共存(Rust 2018+ 风格)
│ └── user.rs
├── tests/ # 集成测试:每个文件是独立 crate,只能访问公开 API
│ └── api.rs
├── benches/ # 基准测试(criterion 等)
├── examples/ # 示例程序:cargo run --example demo
└── target/ # 构建产物,不提交Cargo.toml 五大常用小节
toml
[package]
name = "my_app" # 包名,也是 crates.io 上的名字;导入时用 my_app(连字符会变下划线)
version = "0.1.0" # 语义化版本,发布后不可改已发布版本号
edition = "2024" # 语言版本,见「`edition = "2024"`」
rust-version = "1.85" # 最低支持的 Rust 版本(MSRV),cargo 会据此拒绝过旧的编译器
description = "示例工程" # 以下四项是发布到 crates.io 时必填/建议填的元数据
license = "MIT OR Apache-2.0" # Rust 社区惯例是双许可
repository = "https://github.com/example/my_app"
authors = ["Your Name <you@example.com>"]
[dependencies] # 运行期依赖:库与程序都需要的第三方 crate
serde = { version = "1", features = ["derive"] }
[features] # 编译期开关,由下游或 --features 激活
default = ["fast"]
fast = ["dep:serde"] # dep: 语法表示「可选依赖当作 feature」
[profile.release] # 构建配置,见「`[profile.release]` 调优」
lto = "thin"
[lints.rust] # 声明式 lint 配置:把 lint 写进清单,团队成员共享
unsafe_code = "forbid"
[lints.clippy]
all = { level = "warn", priority = -1 }
dbg_macro = "warn"🧠 原理:
[lints]小节从 Cargo 1.74 起稳定。它把原本要写在每个.rs文件顶部的#![deny(...)]收拢到清单里,保证「我本地开了哪些 lint」和「CI 开了哪些」完全一致。
src/main.rs 与 src/lib.rs
src/main.rs:bin crate 的根。编译出可执行文件,名字默认等于[package] name。src/lib.rs:lib crate 的根。编译出.rlib,供别人use;库没有main。- 两者可以共存,这是推荐做法:把逻辑放进
lib.rs,main.rs只做参数解析与调用:
rust
// src/lib.rs:所有可测试的逻辑放这里
/// 把摄氏度换算为华氏度。库函数写成 pub 才能被外部 crate 和集成测试调用。
pub fn celsius_to_fahrenheit(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}rust
// src/main.rs:只负责 I/O,逻辑委托给库
fn main() {
// 用 crate 名(此处为 my_app)引用自己的库:集成测试也走这个入口
let f = my_app::celsius_to_fahrenheit(100.0);
println!("100°C = {f}°F");
}输出:
100°C = 212°F
这样切分的好处:单元测试与集成测试都能直接测库代码,而 bin 里的 main 很难被测试框架调用。
Cargo.lock:要不要提交?
| 工程类型 | 是否提交 Cargo.lock | 原因 |
|---|---|---|
| 二进制 / 应用 / CLI / 服务 | 提交 | 保证任何人、任何机器、CI 构建出的依赖版本完全一致,可复现 |
| 库(发布到 crates.io) | 不提交(或提交但不影响下游) | 下游自己会解析版本;提交的 lock 不会传递给依赖你的人 |
Cargo.lock 由 cargo 自动生成与更新,不要手改。常用命令:
powershell
cargo update # 在版本约束允许范围内升级所有依赖,并更新 lock
cargo update -p rand # 只升级 rand
cargo update -p rand --precise 0.8.5 # 精确指定版本
cargo tree # 打印依赖树,排查「谁引入了这个 crate」
cargo tree -d # 只看同一 crate 被解析成多个版本的地方
cargo build --locked # 要求 lock 不变,否则报错(CI 常用)
cargo build --frozen # = --locked + --offline⚠️ 陷阱:CI 里用
cargo build --locked能挡住「某人本地偷偷cargo update却没提交 lock」 的情况,避免「本地能过、CI 挂掉」的经典事故。
target/ 与 .gitignore
target/ 是纯缓存目录,可以随时删(cargo clean),删了只是下次编译慢。除了 /target, 项目 .gitignore 通常还会加:
txt
# 构建产物:体积大且可由源码重建
/target
# 全局忽略文件的替代写法(若不用全局配置)
**/*.rs.bk💡 对照:
.gitignore+Cargo.lock的分工,等价于 Node 里忽略node_modules/但提交package-lock.json。
Cargo.toml 与 edition 2024 要点
edition = "2024"
edition(版次)不是 Rust 的版本号,而是「语言的一次不兼容调整批次」。编译器同时支持 2015 / 2018 / 2021 / 2024 四种 edition,同一个项目内不同 crate 可以各用各的,因此生态可以渐进迁移。
toml
[package]
edition = "2024" # 本章基准;需要 rustc 1.85 及以上
rust-version = "1.85" # MSRV:声明后就别再用更新的特性,否则用户装不上edition 2024 中与工程配置、日常写法直接相关的点:
Cargo.toml默认解析器为resolver = "3"(由 edition 决定)。它改变 feature 的合并规则: 平台/条件不满足的依赖不再激活其 feature,dev-dependencies的 feature 也不再泄漏到普通构建。 结果是「依赖减少、编译更快、构建更一致」。- RPIT(返回位置 impl Trait)捕获规则变化:
fn f() -> impl Iterator<Item = u8>现在会自动捕获 签名中出现的全部生命周期,不再需要显式+ '_(此前需要impl Iterator<Item = u8> + '_)。 gen成为保留关键字:为将来的生成器(generator)语法让路,标识符gen需要写成r#gen或改名。unsafe_op_in_unsafe_fn成为错误:unsafe fn内部调用 unsafe 操作也必须显式写unsafe {}。- never 类型与
match人体工学改进:match的默认绑定模式(default binding modes)更一致。 static mut引用取用成为错误:必须改用&raw(原始指针)或addr_of!之类的安全替代。
🚀 进阶:Rust 2024 Edition Guide 逐条列出了 2024 版次的行为变更与迁移方法,完整差异以它为准(入口见附录 D)。
既有项目升级 edition:
powershell
# 自动改写代码以适配新 edition,并更新 Cargo.toml 的 edition 字段
cargo fix --edition
# 也支持跨多个版本一次迁移
cargo fix --edition --edition-idioms依赖版本语义:"1.2" 到底是什么意思
Cargo 的版本约束遵循语义化版本(SemVer) MAJOR.MINOR.PATCH,默认使用 caret 语义:
| 写法 | 名称 | 允许的范围(对 1.2.3) | 说明 |
|---|---|---|---|
"1.2.3" | caret(默认,等价 ^1.2.3) | >=1.2.3, <2.0.0 | 允许升 minor/patch,不允许升 major |
"1.2" | caret | >=1.2.0, <2.0.0 | 最常见写法,官方 crate 依赖里的默认 |
"1" | caret | >=1.0.0, <2.0.0 | 最宽松的「主版本内随便」 |
"0.8.5" | caret(0.x 特殊) | >=0.8.5, <0.9.0 | 主版本为 0 时,minor 变更视为破坏性 |
"~1.2.3" | tilde | >=1.2.3, <1.3.0 | 只允许 patch 升级 |
"=1.2.3" | 精确 | 只能是 1.2.3 | 锁定死版本,谨慎使用 |
"*" | 通配 | 任意版本 | crates.io 发布时会拒绝,日常也别用 |
">=1.2, <1.5" | 区间 | 指定范围 | 需要绕开某个坏版本时使用 |
⚠️ 陷阱:
0.x版本的 crate(生态里非常多)按 SemVer 规定 minor 升级就可能是破坏性变更, 所以rand = "0.8"不会被自动升到0.9。这也意味着0.xcrate 升级时要读 changelog。
用 cargo add 加依赖(别手抄版本号)
cargo add 自 Cargo 1.62 起内置于 cargo(此前需要 cargo-edit):
powershell
cargo add rand # 查最新稳定版并写入 [dependencies]
cargo add rand@0.8 # 指定版本约束
cargo add tokio --features full # 加依赖并开启 feature
cargo add tokio -F rt-multi-thread -F macros # 多个 feature(-F 可重复)
cargo add serde --no-default-features -F derive
cargo add --dev pretty_assertions # 写入 [dev-dependencies]
cargo add --build cc # 写入 [build-dependencies]
cargo add --optional tokio # 标为可选依赖,配套 [features]
cargo add rand --dry-run # 只看会写什么,不改文件
cargo remove rand # 删依赖(同样会同步清理 features)执行 cargo add rand 后 Cargo.toml 大致变成:
toml
[dependencies]
rand = "0.8.5"cargo add tokio --features full 则会写成内联表:
toml
[dependencies]
tokio = { version = "1", features = ["full"] }💡 对照:
cargo add≈npm install --save/poetry add/go get,但它会把解析到的版本 约束写进清单,且默认使用「主版本内兼容」的 caret 写法——这也是 Rust 生态升级相对平滑的原因之一。
三类依赖与 [features]
| 小节 | 参与什么构建 | 典型用途 |
|---|---|---|
[dependencies] | 库和二进制都参与 | 运行时真正需要的 crate |
[dev-dependencies] | 只参与 test / bench / example | 测试框架、mock、断言库(如 pretty_assertions) |
[build-dependencies] | 只参与编译 build.rs 脚本 | 代码生成器、cc(编译 C 源码) |
toml
[dependencies]
serde = { version = "1", features = ["derive"] }
[dev-dependencies]
# 只在测试/基准/示例中编译,不会进入最终产物
pretty_assertions = "1"
[build-dependencies]
# build.rs 自己用的依赖,不会出现在运行期依赖树里
cc = "1"toml
[features]
default = ["json"] # 不写 --no-default-features 时默认开启
json = ["dep:serde_json"] # dep: 前缀让可选依赖只作为 feature 出现,不污染公开命名空间
full = ["json", "yaml"] # feature 可以组合其他 feature
yaml = ["dep:serde_yaml"]对应代码里用条件编译开关:
rust
// 这个示例演示 feature 如何影响编译结果;用 --features json 打开 JSON 分支
fn main() {
#[cfg(feature = "json")]
println!("已启用 json feature");
// 未启用时不编译这一行,因此不会报错
#[cfg(not(feature = "json"))]
println!("未启用 json feature");
}⚠️ 陷阱:feature 必须是可加的(additive)。开启一个 feature 不应该让已有功能失效、也不应该 改变已有 API 的语义——因为 workspace 里任意一个成员开启它,就会对整棵依赖图生效。这是 Rust 生态 最容易踩的设计约束之一。
[profile.release] 调优
toml
[profile.release]
opt-level = 3 # 0-3,或 "s"/"z"(体积优先);默认 3
lto = "thin" # 链接时优化:"thin" 折中,"fat" 最激进但编译慢,false 关闭
codegen-units = 1 # 单编译单元,优化更充分但编译更慢;默认 release 为 16
panic = "abort" # panic 直接 abort 而非 unwind,二进制更小(注意:会禁用 catch_unwind)
strip = "symbols" # 去掉符号表,显著减小体积(1.59+ 支持该布尔/字符串写法)
debug = false # 是否保留调试信息
overflow-checks = true # 业务不能容忍整数回绕时打开
[profile.dev]
opt-level = 1 # 开发时略作优化,缓解「debug 模式慢到没法跑」的问题🧠 原理:
lto与codegen-units = 1之所以能提速,是因为它们允许跨 crate、跨编译单元内联与 常量传播——这正好呼应「零开销抽象」:优化器需要看得到全局信息才敢消除抽象开销。
[lints]:把静态检查写进清单
toml
[lints.rust]
unsafe_code = "forbid" # forbid 连 #[allow] 都无法绕过
missing_docs = "warn" # 公开项缺少文档时告警
[lints.clippy]
all = { level = "warn", priority = -1 } # 打开 clippy::all,priority 越小越先应用
unwrap_used = "warn" # 生产代码里提醒别裸用 unwrap
dbg_macro = "warn" # 别把 dbg! 提交上去
todo = "warn"可用级别:allow < warn < deny < forbid。在命令行上临时提升级别:
powershell
# 把 clippy 的全部警告当成错误,CI 里最常用的一行
cargo clippy --all-targets -- -D warnings延伸阅读
- 同一概念的第二种讲法(官方书中文版、Rust 圣经的逐章映射),见 附录 E · 对照阅读与组合学习法。
- 官方文档、中文资料、书单与工具的完整索引,见 附录 D · 学习资源与文档索引。