Skip to content

环境搭建与工具链

本章是教程的起点:把「写 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 语言里做不到的优化。

与其他语言的定位对比

维度RustPython
执行模型AOT 编译为本地机器码解释器 + 字节码(CPython)
内存管理编译期所有权,无 GC引用计数 + 分代 GC
空值处理null,用 Option<T>None,随处可为空
错误处理Result<T, E> 返回值 + ? 传播异常 try/except
泛型单态化,零开销动态类型,靠鸭子类型
并发安全编译期 Send/Sync 检查GIL 限制 CPU 并行
编译速度慢(借用检查 + 单态化)无编译
典型场景系统 / CLI / 嵌入式 / WASM数据科学、脚本、AI 胶水
维度RustJavaGo
执行模型AOT 编译为本地机器码字节码 + JIT(HotSpot)AOT 编译 + 内嵌运行时
内存管理编译期所有权,无 GC分代 GC并发三色标记 GC
空值处理null,用 Option<T>null + Optionalnil(指针/接口/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。

rustccargo 的分工

这两个命令的关系,等价于 javacmvngccmake

工具职责你会直接用它吗
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 --installed

rustup 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 --version

stable / 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:\rustupCARGO_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 2024

cargo 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% 的错误 (类型、借用、拼写)在前端检查阶段就能发现。先 checkrun 是社区默认习惯。

powershell
# 典型输出(注意没有 linking 阶段;括号里是你项目的实际路径)
#     Checking hello_cargo v0.1.0 (C:\path\to\hello_cargo)
#     Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
cargo check

debug 与 release profile

cargo build 默认使用 dev profile(界面上显示为 dev),cargo build --release 使用 release profile。两者的默认差异:

设置dev(默认)release
opt-level0(不优化)3(激进优化)
debug(调试符号)truefalse
debug-assertionstruefalse
overflow-checks(整数溢出检查)truefalse
incremental(增量编译)truefalse
stripfalsefalse

⚠️ 陷阱: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/ + 编译缓存的合体,但它永远不该提交到 Gitcargo 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.rssrc/lib.rs

  • src/main.rs:bin crate 的根。编译出可执行文件,名字默认等于 [package] name
  • src/lib.rs:lib crate 的根。编译出 .rlib,供别人 use;库没有 main
  • 两者可以共存,这是推荐做法:把逻辑放进 lib.rsmain.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.x crate 升级时要读 changelog。

cargo add 加依赖(别手抄版本号)

cargo addCargo 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 randCargo.toml 大致变成:

toml
[dependencies]
rand = "0.8.5"

cargo add tokio --features full 则会写成内联表:

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

💡 对照cargo addnpm 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 模式慢到没法跑」的问题

🧠 原理ltocodegen-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


延伸阅读


内容以 rustc 1.98.1 · Rust 2024 edition 为基准