概述

  • 参考文档: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与rustc

    bash
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    rustup update # 升级工具链到最新
  • 新建并运行项目:

    bash
    cargo new hello      # 生成二进制项目,加 --lib 生成库项目
    cd hello
    cargo run            # 编译并运行
  • 生成的项目结构:

    • Cargo.toml:项目清单,声明名称、版本与依赖
    • Cargo.lock:锁文件,固定依赖的精确版本,应当提交进版本库
    • src/main.rs:二进制入口,库项目则是src/lib.rs
    • target/:全部构建产物,加入.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 构建结果一致

  • 命令行操作依赖:

    bash
    cargo 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原样安装,不一致直接报错而不是悄悄更新

常用命令

  • 构建与检查:

    bash
    cargo build            # 调试构建,产物在 target/debug/
    cargo build --release  # 优化构建,产物在 target/release/
    cargo test             # 跑单元测试、集成测试与文档测试
    cargo bench            # 跑基准测试
    cargo doc --open       # 生成并打开 API 文档
  • 代码质量由官方组件承担,不需要再挑第三方的工具:

    bash
    cargo 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列表而不是目录名

    • 典型布局:

      text
      repo/
      ├── 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.rs
    • crates/装的是一组 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:每天构建,包含未稳定的实验性特性
  • 常用操作:

    bash
    rustup 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都是工具链的组件,可以单独增删

    bash
    rustup component add rustfmt clippy
    rustup component list --installed
  • 交叉编译:target指目标平台三元组,安装后cargo build --target <目标>即可产出别的平台的产物

    bash
    rustup target add wasm32-unknown-unknown
    cargo build --target wasm32-unknown-unknown
  • rustup接管命令分发:rustc、cargo等命令实际是rustup的代理(proxy),调用时再转发给当前生效的工具链版本,rustup which cargo可查看实际执行的二进制

发布

  • 库项目发布到crates.io:

    bash
    cargo 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完就能编译运行,不用先写构建脚本,也不用自己编译依赖