Rust 是目前最著名的 C++ 替代语言,它的版本管理和项目管理由两个组件提供:

  • rustup:Rust 工具链版本管理
  • cargo:项目构建和包管理

对比 C++ 生态:rustup 管理编译器版本(C++ 没有标准的编译器版本管理器),cargo 融合了 CMake(构建)、vcpkg/Conan(包管理)和 CTest(测试)的功能。Rust 的标准工具链解决了 C++ 生态中长期以来需要拼凑不同工具才能覆盖的问题。

下面主要关注 Linux 环境下的使用,macOS 高度类似;Windows 中路径不同但概念一致。

Rust 工具链目录

rustup 管理的安装目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~/.rustup/
├── settings.toml
├── update-hashes/
└── toolchains/ -- 各个版本的 Rust 工具链
├── stable-x86_64-unknown-linux-gnu/
│ ├── bin/
│ │ ├── rustc -- 编译器(可看作 g++/clang++)
│ │ ├── cargo -- 构建和包管理器
│ │ ├── rustfmt -- 代码格式化(clang-format)
│ │ ├── clippy-driver -- 静态检查(clang-tidy)
│ │ └── ...
│ ├── lib/ -- 编译器及各目标平台的标准库
│ └── share/
└── nightly-x86_64-unknown-linux-gnu/
└── ...

cargo 自身的数据存放在 ~/.cargo/

1
2
3
4
5
~/.cargo/
├── bin/ -- rustup 代理程序及 cargo install 安装的二进制 crate
├── registry/ -- crates.io 注册表缓存(类似 vcpkg 的 ports 目录)
├── git/ -- git 依赖缓存
└── config.toml -- cargo 配置(全局 build 选项)

C++ 对比:Rust 工具链相关文件集中在 ~/.rustup~/.cargo 中,标准卸载方式为 rustup self uninstall。C++ 环境中 GCC/Clang、CMake、vcpkg、clang-tidy 等工具分散在各处,没有统一的安装根目录。注意 Rust GNU 工具链仍可能依赖系统 linker、libc 等外部组件。

rustup 版本管理

C++ 生态中没有标准的编译器版本管理器——通常依赖系统包管理器(apt install g++-14)或 conda/spack 来安装特定编译器版本,切换版本比较繁琐。

rustup 填补了这个缺口,统一管理:

  • 工具链安装和更新(stablebetanightly 通道)
  • 多版本切换(默认工具链、目录级覆盖)
  • 附加组件管理(rustfmtclippyrust-analyzer 等)
  • 交叉编译 target 安装

安装与卸载

1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装程序会自动把 ~/.cargo/bin 添加到 PATH 中。

验证:

1
2
3
rustc --version
cargo --version
rustup --version

卸载:

1
rustup self uninstall

通道与工具链管理

三个发布通道:

  • stable:生产使用
  • beta:下一版 stable 的预览
  • nightly:每日构建,包含实验性特性
1
2
3
4
5
6
7
8
9
rustup toolchain install stable
rustup toolchain install nightly
rustup toolchain install 1.85.0

rustup toolchain list # 已安装的工具链
rustup show # 当前生效的工具链详情
rustup default stable # 设置默认工具链
rustup update # 更新所有已安装的工具链
rustup toolchain uninstall 1.85.0

临时使用指定工具链执行命令:

1
2
cargo +nightly build
cargo +1.85.0 test

这在测试 nightly 特性或验证不同编译器版本的兼容性时很实用。C++ 中需要手动切换 CC/CXX 环境变量或修改 CMake 的 CMAKE_C_COMPILER

组件管理

1
2
3
rustup component list
rustup component add rustfmt clippy
rustup component remove rustfmt

C++ 对比:

组件 作用 C++ 近似等价
rustfmt 代码格式化 clang-format
clippy 静态检查/lint clang-tidy
rust-analyzer LSP 语言服务器 clangd / ccls
rust-docs 本地标准库文档 cppreference 离线版
rust-src 标准库源码 libstdc++/libc++ 源码

Rust 这些组件随工具链一起由 rustup 管理,统一安装和更新。C++ 中这些工具通常需要单独安装、各自维护版本兼容。

项目级版本固定

rust-toolchain.toml 用于为项目选择或固定开发工具链;它不同于 Cargo.toml 中声明最低支持 Rust 版本的 rust-version(类似 CMakeLists.txt 里的 cmake_minimum_required):

1
2
[toolchain]
channel = "1.85.0"

放在项目根目录即可,rustup 会自动使用对应版本,未安装时会自动下载。

交叉编译 target

1
2
3
4
rustup target list
rustup target add aarch64-unknown-linux-gnu
rustup target add wasm32-unknown-unknown
rustup target remove wasm32-unknown-unknown

C++ 交叉编译需要手动安装对应平台的 toolchain(如 aarch64-linux-gnu-g++),配置 CMake toolchain file。Rust 通过 rustup target add 安装目标平台的 Rust 标准库;部分目标(如 aarch64-unknown-linux-gnu)仍需单独安装交叉链接器(如 gcc-aarch64-linux-gnu)并配置 linker。wasm32-unknown-unknown 这类不依赖系统链接器的 target 相对简单,但该便利性不能推广到所有 target。

cargo 项目管理和包依赖

cargo 相当于 CMake + vcpkg/Conan + CTest 的融合体,覆盖了构建、依赖管理和测试全流程。

创建项目

1
2
3
cargo new --lib mylib   # library package,默认包含 src/lib.rs
cargo new myapp # binary package,默认包含 src/main.rs
cargo init # 当前目录初始化

标准项目骨架:

1
2
3
4
5
myapp/
├── Cargo.toml -- 相当于 CMakeLists.txt + vcpkg.json
├── Cargo.lock -- 首次构建后生成,依赖精确锁定
└── src/
└── main.rs -- 入口(或 lib.rs 对于库)

Cargo.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[package]
name = "myapp"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"

[dependencies]
serde = { version = "1", features = ["derive"] }
rand = "0.8"
tokio = { version = "1", optional = true }

[dev-dependencies]
tempfile = "3"

[build-dependencies]
cc = "1"

[profile.release]
opt-level = 3
lto = true

Cargo.lock 锁定依赖的精确版本。应用程序通常应提交以保证构建可复现;库项目是否提交取决于是否希望固定开发和 CI 环境——当前 Cargo 官方建议在不确定时提交。C++ 中 Conan 的 lockfile、vcpkg 的 baseline 与 overrides 概念类似,但使用远不如 cargo 统一。

构建和运行

1
2
3
4
5
6
cargo build             # debug 模式(类似 cmake -DCMAKE_BUILD_TYPE=Debug && make)
cargo build --release # release 优化模式(类似 cmake -DCMAKE_BUILD_TYPE=Release)
cargo run # 构建并运行
cargo run --release
cargo check # 检查代码而不生成最终程序或库(比完整编译快得多)
cargo clean # 清理构建产物(make clean / 删除 build/)

构建产物位于项目的 target/ 目录(类似 CMake 的 build/):

1
2
3
target/
├── debug/ -- cargo build 产物
└── release/ -- cargo build --release 产物

cargo check 是 Rust 的独特优势:跳过大多数 crate 的最终代码生成和链接,只做类型检查、trait 解析等,比完整编译快得多。注意 build script 仍需编译执行,proc macro 和部分 host dependency 仍需生成可执行代码。C++ 编译器也有仅做语法/语义检查的选项,但生态中缺少一个在项目、依赖和构建脚本层面完全对应的统一命令。日常开发中用 cargo check 替代 cargo build 可以显著缩短反馈周期。

跳过 cargo:直接使用 rustc

C++ 中常直接用 g++ -o hello hello.cpp && ./hello 快速试一小段代码。Rust 也能这样做——rustc 可以脱离 cargo 独自编译单个 .rs 文件:

1
2
3
echo 'fn main() { println!("Hello, world!"); }' > hello.rs
rustc hello.rs # 类似 gcc hello.c -o hello
./hello # 输出 Hello, world!
1
2
3
4
5
6
7
8
# 启用优化
rustc -C opt-level=3 hello.rs

# 生成库而非可执行文件
rustc --crate-type lib mylib.rs

# 链接外部原生库
rustc main.rs -l m

与 C++ 对比:

能力 g++/clang++ rustc
单文件编译 g++ foo.cpp -o foo rustc foo.rs
指定输出名 -o <name> -o <name>
优化等级 -O2 / -O3 -C opt-level=3
编译为库 -shared / -c --crate-type lib/cdylib
链接系统库 -l<lib> -l <lib>
查看标准库路径 无直接等价 rustc --print target-libdir
仅类型检查 -fsyntax-only cargo check(rustc 无直接等价)

不过对于大多数工作,仍然推荐使用 cargo——原因和 C++ 中不推荐长期用裸 g++ 命令而不用 CMake 类似:一旦涉及到第三方依赖、feature 配置、条件编译、测试和多 crate 组织,裸 rustc 命令会迅速变得不可维护。rustc 更适合快速实验、学习语言特性或 benchmark 微测试,正式项目则统一走 cargo。

依赖管理

crates.io 注册表:

1
2
3
4
5
6
7
8
9
cargo add serde                 # 类似 vcpkg add / conan install
cargo add serde@=1.0.0
cargo add serde --features derive

cargo add --dev tempfile # [dev-dependencies]
cargo remove serde

cargo update # 更新依赖到兼容范围内的最新版本
cargo update -p serde

Git 仓库依赖:

1
2
3
cargo add --git https://github.com/user/repo.git
cargo add --git https://github.com/user/repo.git --branch main
cargo add --git https://github.com/user/repo.git --rev abc1234

C++ 对比:CMake 的 FetchContent(下载远程仓库并引用)或 Conan 的 Git 源。

本地路径依赖(开发中的库):

1
cargo add --path ../mylib

等价于 Cargo.toml 中:

1
2
[dependencies]
mylib = { path = "../mylib" }

C++ 对比:CMake 的 add_subdirectory("../mylib")find_package 配合 CMAKE_PREFIX_PATH。Rust 的做法更简单——一行声明即完成,不像 CMake 还需要在调用方加 target_link_libraries

测试

1
2
3
4
5
cargo test                     # 构建并运行所有测试(CTest)
cargo test test_name # 运行名称匹配 test_name 的测试(子串过滤)
cargo test -- --nocapture # 显示 stdout
cargo test --doc # 执行文档中的代码示例
cargo +nightly bench # 基准测试

C++ 中测试框架选择和集成方式各不相同(GoogleTest + CTest、Catch2、Boost.Test 等),还需手动配置 CTest 或 CMake 的 add_test。Rust 中 cargo test 开箱即用,单元测试直接写在源文件中(#[cfg(test)] mod tests)。

其他常用命令

命令 作用 C++ 近似等价
cargo check 快速类型检查 无直接等价
cargo fix 自动应用编译器建议 clang-tidy --fix
cargo fmt 代码格式化 clang-format -i
cargo clippy 静态检查/lint clang-tidy
cargo tree 查看依赖树 vcpkg depend-info / conan graph info / cmake --graphviz
cargo doc 生成文档 doxygen
cargo search 搜索 crates.io vcpkg search / conan search
cargo publish 发布到 crates.io
cargo install 安装二进制 crate pip install / npm install -g

Rust 包开发

package 与 crate

这两个概念在 Rust 中有严格区分,和 C++ 的对应关系并不直观。

crate 是 Rust 的编译单元。一次 rustc 调用处理一个 crate,根据 crate type 生成可执行程序、Rust 库(.rlib)、系统库(.so/.dll)或过程宏等产物。与 C++ 不同:C++ 的编译单元是单个 .cpp 文件(translation unit),通过链接合并为最终产物;crate 更接近最终产物而非中间的 .o

crate 分为两种:

  • binary crate:生成可执行文件,必须有 main 函数。根模块默认为 src/main.rs
  • library crate:生成库文件。默认输出 .rlib(Rust 自有的静态库格式,包含 Rust 元数据),根模块默认为 src/lib.rs。可通过 [lib] 中的 crate-type 改变输出类型——例如 cdylib 生成 C ABI 兼容的 .so/.dylib/.dll

package 是 Cargo 的项目概念——即一个 Cargo.toml 所管理的目录。一个 package 可以包含多个 crate:

1
2
3
4
5
myapp/
├── Cargo.toml
└── src/
├── main.rs -- binary crate 根模块,生成可执行文件
└── lib.rs -- library crate 根模块,生成库(可选)

如果 src/ 下同时有 main.rslib.rs,这个 package 就包含一个 library crate 和一个 binary crate。此外,src/bin/ 下的每个 .rs 文件也都是独立的 binary crate(如 src/bin/tool.rs 编译为可执行文件 tool)。

C++ 对比:C++ 中构建产物类型(可执行文件 vs 静态/动态库)和编译单元(.cpp)是分离的概念,由 CMake 的 add_executable / add_library 分别管理。Rust 将”编译范围 + 产物类型”统一到 crate 概念中,由入口文件位置(main.rs / lib.rs)和 Cargo.toml 共同决定。

模块系统

Rust 以 crate 为根,通过 mod 声明构建模块树。这与 C++ 的 #include 有本质区别。

C++ 的 #include 是文本级复制——预处理期将头文件内容逐字插入源文件,声明和定义分离在 .h.cpp 中。Rust 没有头文件mod 声明不复制文本,而是将对应文件/目录作为模块挂载到 crate 的模块树中:

1
2
3
4
5
6
7
8
9
src/
├── main.rs -- crate 根模块(binary)
├── lib.rs -- crate 根模块(library)
├── foo.rs -- 模块 foo,其中可声明 `pub mod bar;`
├── foo/
│ └── bar.rs -- foo::bar 子模块
└── utils/
├── mod.rs
└── helper.rs -- utils::helper 模块
1
2
3
4
5
6
// 在 main.rs 或 lib.rs 中:
mod foo; // 声明模块 foo(编译器会找 foo.rs 或 foo/mod.rs,二者不能同时存在)
mod utils; // 不是文本复制——编译器会找到 utils/mod.rs 并解析它

use foo::bar; // 路径导入(类似 C++ 的 using foo::bar,将名称 bar 引入当前作用域)
use crate::utils::helper;

几个 C++ 程序员需要适应的关键差异:

  • 没有头文件,没有声明/定义分离。Rust 没有 C++ 式的头文件与源文件分离。普通函数的签名和函数体构成同一个定义,公共接口通过 pub 和模块可见性控制。trait 方法声明与对应的 impl 可以位于不同模块或文件中。
  • 模块路径不是文件路径mod foo 中的 foo 是模块名而非文件名;文件只是模块的载体。模块路径是 crate 内部的逻辑路径,不等同于文件路径;但使用文件承载外部模块时,默认模块解析仍遵循规定的目录布局(foo.rsfoo/mod.rsfoo/bar.rs 等)。
  • 不需要 #include 防护或 #pragma once。模块天然不会重复引入。
  • use 相当于引入路径别名,不影响编译产物的包含关系(mod 决定哪些代码参与编译,use 只是缩短路径)。C++ 中 #includeusing 是独立的;Rust 中 moduse 也是独立的,但 mod 没有 C++ 的直接等价物。

Rust 2018+ 中,foo.rsfoo/mod.rs 二者只能选其一,不能同时存在。foo.rs 内部可以直接声明 pub mod bar; 来挂载 foo/bar.rs,无需降级到 foo/mod.rs。社区一般偏好前者(少一个 mod.rs 嵌套)。

workspace(多包仓库)

对于大型项目,workspace 在一个仓库中管理多个 package(类似 CMake 的 add_subdirectory 或在顶层 CMakeLists.txt 中批量管理子项目):

1
2
3
4
5
6
7
8
9
10
my-workspace/
├── Cargo.toml -- [workspace] 定义
├── Cargo.lock -- 全 workspace 共享同一份锁文件
├── target/ -- 全 workspace 共享构建目录
├── mylib/
│ ├── Cargo.toml
│ └── src/lib.rs
└── myapp/
├── Cargo.toml
└── src/main.rs

workspace 根 Cargo.toml

1
2
3
4
5
6
[workspace]
members = ["mylib", "myapp"]
resolver = "3"

[workspace.dependencies]
serde = "1" # 集中声明,子包通过 workspace = true 引用

C++ 对比:CMake 父子项目各自独立管理依赖版本,容易产生不一致。Rust workspace 共享 Cargo.locktarget/,各成员使用同一个依赖解析结果,但 Cargo 仍可能在同一依赖树中同时存在多个不兼容版本(使用 cargo tree -d 可查看重复版本)。

features(条件编译)

Rust 的 features 对标 C++ 预处理器(#ifdef / #if defined(...) 配合 CMake 的 option()),但集成度更高:

1
2
3
4
5
6
7
8
[features]
default = ["std"]
std = []
async = ["dep:tokio"]
full = ["std", "async"]

[dependencies]
tokio = { version = "1", optional = true }
1
2
3
cargo build --features "async"
cargo build --no-default-features
cargo build --all-features

代码中通过 #[cfg(feature = "async")] 控制编译路径。与 #ifdef 的区别:features 在 Cargo.toml 中显式声明,可组合其他 features 或激活可选依赖——例如 async = ["dep:tokio"] 显式规定启用 async 时同时启用可选依赖 tokio。相比之下,C++ 预处理宏本身只是自由的文本条件。

发布到 crates.io

1
2
3
4
cargo login <token>
cargo publish
cargo yank --vers 0.1.0 # 阻止新的依赖解析选择该版本,但不删除已发布文件
cargo yank --vers 0.1.0 --undo

crates.io 注册表

crates.io 是 Rust 的中心化包注册表,类似 vcpkg 的 ports 目录或 Conan Center,但有统一的 CLI 入口和版本策略。

cargo add serde 幕后的流程:

  1. ~/.cargo/registry/ 中查找本地缓存的索引
  2. 根据 Cargo.toml 中声明的版本约束解析依赖——Rust 默认使用语义化版本(^0.8 表示 >=0.8.0, <0.9.0
  3. 确定完整依赖树,更新 Cargo.lock
  4. 从 crates.io 下载 crate 源码到 ~/.cargo/registry/src/
  5. 构建依赖,产物放在项目的 target/ 目录

镜像源配置: ~/.cargo/config.toml 中指定:

1
2
3
4
5
[source.crates-io]
replace-with = 'ustc'

[source.ustc]
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"

cargo install — 全局二进制工具

1
2
3
cargo install ripgrep       # 类似 brew install / apt install
cargo install ripgrep@14.0.0
cargo install --git https://github.com/user/repo.git

安装到 ~/.cargo/bin/ 中,确保其位于 PATH 下即可使用。C++ 生态中没有统一的直接对应命令,通常使用系统包管理器、vcpkg、Conan,或者手动编译安装。

Rust FFI:与 C/C++ 互操作

对 C++ 背景的用户来说,Rust FFI(Foreign Function Interface)是关键需求。Rust 通过 C ABI 作为中间层与外部语言互操作。

调用 C 函数

1
2
3
4
5
6
7
8
unsafe extern "C" {
fn abs(input: std::ffi::c_int) -> std::ffi::c_int;
}

fn main() {
let value = unsafe { abs(-3) };
println!("abs(-3) = {value}");
}

如需链接额外的原生库,可在 build.rs 中输出 Cargo instruction,例如 println!("cargo::rustc-link-lib=m");;若项目包含需要一同编译的 C/C++ 源码,则可使用 cc crate。在 Rust 2024 中,extern "C" block 必须标记为 unsafe,其中未显式标记 safe 的外部函数默认是 unsafe 调用。

将 Rust 编译为 C 可链接的动态库

1
2
3
4
5
// lib.rs
#[unsafe(no_mangle)]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
1
2
3
# Cargo.toml
[lib]
crate-type = ["cdylib"] # 输出 .so / .dylib / .dll

生成的动态库在 Linux、macOS 和 Windows 上分别为 .so.dylib.dll,可分别通过 dlopenLoadLibrary 动态加载,也可由 C/C++ 程序直接链接(配合 cbindgen 生成头文件)。C++ 程序调用 Rust 库的路径与调用 C 库一致。

常用 FFI 生态工具

工具 作用 C++ 相关场景
bindgen 从 C 头文件生成 Rust FFI 绑定 复用现有 C/C++ 库
cbindgen 从 Rust 代码生成 C 头文件 将 Rust 库暴露给 C++ 项目
cc crate build.rs 中编译 C/C++ 代码 混编 C/C++ 和 Rust
cxx crate 安全的 C++ ↔ Rust 互操作 直接对接 C++ 类型(std::string 等)
pyo3 + maturin Rust → Python 扩展模块 用 Rust 替代 C++ 写 Python 扩展
wasm-bindgen Rust → WebAssembly/JavaScript 将计算密集逻辑从 C++(Emscripten)迁移到 Rust

缓存与磁盘管理

1
2
3
du -sh target/                  # 项目构建产物(删除后不影响其他项目)
du -sh ~/.cargo/registry/ # crates.io 源码缓存(跨项目共享)
du -sh ~/.cargo/git/ # Git 依赖缓存(跨项目共享)
  • cargo clean:清理当前项目 target/,类似删除 CMake build/ 目录
  • 删除 ~/.cargo/registry/~/.cargo/git/:下一次 cargo build 会重新下载
  • C++ 对比:vcpkg 的二进制缓存通常位于 ~/.cache/vcpkg/archives/,Conan 2 的本地缓存默认位于 ~/.conan2/,概念类似

推荐工作流

新项目:

1
2
3
4
5
6
7
8
cargo new myapp && cd myapp
cargo add serde --features derive
cargo add rand
cargo add --dev tempfile

cargo check # 快速反馈
cargo run
cargo test

已有项目:

1
2
3
git clone <url> && cd project
cargo build # 自动下载依赖,构建
cargo run

固定工具链版本:

1
2
3
# rust-toolchain.toml
[toolchain]
channel = "1.85.0"

Rust 与 C++ 对比

关注点 Rust 方案 C++ 生态中的近似等价
编译器版本管理 rustup 无标准方案(系统包管理器 / conda / spack)
构建系统 cargo build CMake + Make/Ninja
包管理 cargo add / crates.io vcpkg / Conan
项目配置 Cargo.toml CMakeLists.txt + vcpkg.json
锁文件 Cargo.lock Conan lockfile / vcpkg baseline 与 overrides
测试 cargo test CTest + GoogleTest / Catch2
格式化 cargo fmt clang-format
静态检查 cargo clippy clang-tidy
文档 cargo doc Doxygen
条件编译 features + #[cfg] #ifdef + CMake option()
构建产物目录 target/ build/

Rust 的工具链设计将 C++ 生态中分散的工具整合为一套统一方案,从编译器安装、构建、依赖管理到测试和发布都有标准的 CLI 入口。这种一致性是 Rust 工程体验的重要特征。C++20 modules 主要改进代码组织和编译模型,并未统一编译器管理、构建、依赖管理和测试工具。