概述
- 参考文档:The Cargo Book与The rustup Book
rustup是Rust官方的工具链安装器与版本管理器,cargo是官方的构建系统与包管理器,二者组合就是Rust生态的标准工具链- 职责划分:
rustup:安装多套工具链(stable/beta/nightly)、按目录切换版本、管理组件与交叉编译目标cargo:创建项目、解析依赖、编译、测试、跑文档、格式化检查、发布crates
- 工具链与工具的关系:
flowchart LR R["rustup"] -->|"安装与切换"| T["工具链(stable / nightly)"] T --> C["cargo"] T --> K["rustc"] T --> F["rustfmt / clippy"] C -->|"解析依赖"| CR["crates.io"] C -->|"构建产物"| P["target/"]
快速开始
安装:
rustup是唯一入口,装好它就装好了cargo与rustcbashcurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup update # 升级工具链到最新新建并运行项目:
bashcargo new hello # 生成二进制项目,加 --lib 生成库项目 cd hello cargo run # 编译并运行生成的项目结构:
Cargo.toml:项目清单,声明名称、版本与依赖Cargo.lock:锁文件,固定依赖的精确版本,应当提交进版本库src/main.rs:二进制入口,库项目则是src/lib.rstarget/:全部构建产物,加入.gitignore
日常开发循环用
cargo check代替cargo build:只做类型检查不生成产物,反馈最快
依赖管理
在
Cargo.toml的[dependencies]中声明依赖,版本号遵循语义化版本(semver)toml[dependencies] serde = "1.0" # 等价于 ^1.0:兼容 1.x 的最新版 rand = "0.8.5" # ^0.8.5:兼容 0.8.x tokio = { version = "1", features = ["full"] } # 按需启用 feature"1.0"是^1.0的简写,只约束主版本与兼容范围,精确版本由Cargo.lock固定,保证团队成员与 CI 构建结果一致命令行操作依赖:
bashcargo add serde # 添加依赖并写入 Cargo.toml cargo remove serde # 移除依赖 cargo update # 更新 lock 内的依赖到兼容上限 cargo update -p [email protected] # 只更新一个包 cargo tree # 树状打印依赖图非
crates.io来源:git 仓库与本地路径,本地路径常用于同时开发的关联项目toml[dependencies] mylib = { git = "https://github.com/user/mylib" } # 来自 git 仓库 # mylib = { path = "../mylib" } # 来自本地路径CI 与部署构建加
--locked:按Cargo.lock原样安装,不一致直接报错而不是悄悄更新
常用命令
构建与检查:
bashcargo build # 调试构建,产物在 target/debug/ cargo build --release # 优化构建,产物在 target/release/ cargo test # 跑单元测试、集成测试与文档测试 cargo bench # 跑基准测试 cargo doc --open # 生成并打开 API 文档代码质量由官方组件承担,不需要再挑第三方的工具:
bashcargo fmt # rustfmt:官方格式化,消除风格争论 cargo clippy # 官方 linter,给出惯用法建议run与test支持临时指定要运行的例子或测试:cargo test <名称子串>只跑名字匹配的测试在
Cargo.toml里配置[alias]可以给长命令起短名,保存在项目里随仓库共享toml[alias] rr = "run --release"
工作空间
一个仓库多个
crates时用workspace统一管理:根Cargo.toml列出成员,成员共享一个Cargo.lock与一个target/共享锁文件意味着所有成员用同一份依赖版本,不会出现两个子项目各锁一版
serde的情况toml[workspace] members = ["crates/app", "crates/core", "crates/utils"] resolver = "2"
成员摆放的惯例是
crates/目录:每个子目录是一个完整的 crate,有自己的Cargo.toml与src/crates/不是cargo的内建目录,换个名字照样能用,cargo认的是members列表而不是目录名典型布局:
textrepo/ ├── Cargo.toml # 根清单,只列成员,又叫虚拟清单 ├── Cargo.lock # 整个工作空间共用一份 ├── crates/ │ ├── core/ # 一个成员 crate │ │ ├── Cargo.toml │ │ └── src/lib.rs │ └── app/ │ ├── Cargo.toml │ └── src/main.rs └── target/ # 构建产物也只有一份
crates/与src/的区别:src/装的是一个 crate的源码,入口是src/main.rs或src/lib.rscrates/装的是一组 crate,里面每个子目录才是又一个"清单加src/“的完整项目- 只有仓库根上有
crates/,单 crate 项目里不会出现这个目录
cargo真正内建的目录:src/之外还有tests/(集成测试)、benches/(基准测试)、examples/(示例),多个二进制入口放src/bin/下,每个文件编译成一个可执行文件依赖去重:
workspace级别的[workspace.dependencies]统一声明版本,成员用serde.workspace = true继承大仓库的收益:
cargo build在整个工作空间按需增量编译,改一个成员不必重建无关部分
Rustup工具链管理
发布通道有
stable、beta、nightly三种:stable:每 6 周发一版,日常开发的默认选择beta:下一版的候选nightly:每天构建,包含未稳定的实验性特性
常用操作:
bashrustup toolchain install stable nightly # 安装工具链 rustup toolchain list # 查看已装 rustup default stable # 设置全局默认 rustup run nightly cargo build # 临时用指定工具链执行按目录固定工具链:
rustup override set nightly作用于当前目录,或者放一个rust-toolchain.toml进仓库随代码走,仓库优先用文件方式,协作者无需手动配置toml[toolchain] channel = "1.75.0" # 也可以精确钉一个版本组件管理:
rustc、cargo、rustfmt、clippy都是工具链的组件,可以单独增删bashrustup component add rustfmt clippy rustup component list --installed交叉编译:
target指目标平台三元组,安装后cargo build --target <目标>即可产出别的平台的产物bashrustup target add wasm32-unknown-unknown cargo build --target wasm32-unknown-unknownrustup接管命令分发:rustc、cargo等命令实际是rustup的代理(proxy),调用时再转发给当前生效的工具链版本,rustup which cargo可查看实际执行的二进制
发布
库项目发布到
crates.io:bashcargo login <token> # 首次设置 api token cargo publish --dry-run # dry run cargo publish发布前
cargo package --list确认包内文件,Cargo.toml里的description、license是发布的必填字段版本号遵守
semver,破坏性改动必须升主版本号,生态据此判断兼容性
与C/C++生态对比
C/C++每个项目都得自己拼一套工具链:- 构建:
make、cmake或者 IDE 自己的工程格式,换个项目就换一套 - 依赖:
conan、vcpkg、系统包管理器里挑一个,团队之间经常不一样 - 格式化和 lint 还要另外选工具
- 构建:
Rust这边没有挑选的问题:构建、依赖、测试、文档、发布都是cargo的子命令,包统一在crates.io,格式化只有rustfmt,lint 只有clippy- 依赖冲突的处理方式不同:
Rust允许同一个包的多个版本共存,链接器的事cargo包办;C/C++里同名符号冲突、链接顺序排错都是自己排查 - 工具链版本:
rustup管rustc的版本,类似nvm之于node,区别是rust-toolchain.toml能提交进仓库,进目录就会自动切到文件指定的版本 - 实际体感:
cargo new完就能编译运行,不用先写构建脚本,也不用自己编译依赖