Cargo 与 Bazel:两种构建系统在 Rust 大型项目中的取舍分析

发布时间:2026/7/17 15:46:36

Cargo 与 Bazel:两种构建系统在 Rust 大型项目中的取舍分析 Cargo 与 Bazel两种构建系统在 Rust 大型项目中的取舍分析一、Cargo够用就好Cargo 是 Rust 的官方构建系统对纯 Rust 项目来说几乎完美# Cargo.toml —— 简洁直观的配置 [package] name my-service version 0.1.0 edition 2021 [dependencies] tokio { version 1, features [full] } serde { version 1, features [derive] } serde_json 1 # 一个 cargo build 搞定一切Cargo 的优势很明显零配置入门cargo new就能开始写代码。crates.io 生态依赖管理自动、版本解析可靠。项目结构约定src/main.rs、src/lib.rs等约定让所有 Rust 项目看起来一样。增量编译cargo build自动只编译修改过的部分。但 Cargo 也有局限只懂 Rust无法管理 C、Python、Protobuf 等非 Rust 代码。单仓库支持弱monorepo 场景下跨 crate 的依赖处理不够灵活。缓存粒度粗没有分布式构建缓存虽然有 sccache但不是内建的。二、Bazel为大型多语言项目而生Bazel 是 Google 开源的构建系统支持几十种语言核心卖点是多语言、增量编译、分布式缓存。一个典型的 Bazel Rust 项目结构如下# WORKSPACE —— 定义外部依赖和工作空间 workspace(name my_project) load(bazel_tools//tools/build_defs/repo:http.bzl, http_archive) # 导入 Rust 规则 http_archive( name rules_rust, sha256 ..., urls [https://github.com/bazelbuild/rules_rust/releases/download/...], ) load(rules_rust//rust:repositories.bzl, rules_rust_dependencies) rules_rust_dependencies() # 导入 crates.io 依赖Bazel 也可以拉 Rust 的包 load(rules_rust//crate_universe:defs.bzl, crates_repository) crates_repository( name crates_index, cargo_lockfile //:Cargo.lock, # 复用 Cargo.lock manifests [//:Cargo.toml], )# BUILD.bazel —— 定义构建目标每个目录一个 BUILD 文件 load(rules_rust//rust:defs.bzl, rust_binary, rust_library, rust_test) # 定义 Rust 库 rust_library( name my_lib, srcs [src/lib.rs], deps [ crates_index//:tokio, # 引用外部 crate crates_index//:serde, ], visibility [//visibility:public], # 控制可见性 ) # 定义 Rust 二进制 rust_binary( name my_service, srcs [src/main.rs], deps [ :my_lib, # 引用同项目的库 //proto:greeter_rust_proto, # 引用 Protobuf 生成的 Rust 代码 crates_index//:tonic, ], )Bazel 的真正威力在多语言项目中体现# proto/BUILD.bazel —— 一个目录同时管理多种语言的生成代码 load(rules_proto//proto:defs.bzl, proto_library) load(rules_rust//proto:defs.bzl, rust_proto_library) # Protobuf 定义语言无关 proto_library( name greeter_proto, srcs [greeter.proto], visibility [//visibility:public], ) # 自动生成 Rust 代码 rust_proto_library( name greeter_rust_proto, deps [:greeter_proto], ) # 自动生成 Python 代码 load(rules_python//python:defs.bzl, py_proto_library) py_proto_library( name greeter_py_proto, deps [:greeter_proto], )三、核心取舍对比我把两种构建系统在大型项目中的关键差异总结如下维度CargoBazel大项目影响学习曲线低30 分钟入门高需要学 Starlark、BUILD 语法团队培训成本配置复杂度Cargo.toml 一个文件WORKSPACE 每个目录 BUILD.bazel维护负担增量编译自动crate 粒度精确文件级别更细粒度CI 时间分布式缓存需要 sccache内建 Remote Cache Remote Execution编译速度多语言支持仅 RustRust/C/Python/Java/Go/Proto…跨语言项目必需外部依赖crates.io (版本好管理)需要手动声明或通过 crate_universe依赖管理复杂度IDE 支持rust-analyzer 完美需要生成 Cargo.toml 代理开发体验CI 集成简单cargo test复杂bazel 配置 缓存服务CI 维护成本四、什么时候该切换到 Bazel我的判断标准很简单满足以下任意两条就应该考虑 Bazel项目超过 3 种编程语言如果你在写 Rust 服务 C FFI Python 脚本 Protobuf 定义Cargo 管不了这些。团队超过 10 个开发者Bazel 的可见性控制visibility、构建目标粒度和缓存共享对于大团队更有价值。CI 编译时间超过 10 分钟Bazel 的分布式缓存Remote Cache可以让第二次及以后的全量构建变成几乎即时的。单仓库monorepo中有多个独立服务Bazel 的精确依赖分析能确保只重新编译真正需要的东西。如果你只是一个 Rust 项目没有其他语言团队不超过 5 个人别折腾 Bazel——Cargo 完全够用换 Bazel 反而会增加维护负担。实际项目中如果你已经用 Cargo 开发了一段时间想迁移到 Bazel可以借助rules_rust的crate_universe直接读取现有的Cargo.toml和Cargo.lock减少迁移成本# 直接读取 Cargo.toml 中的依赖生成 Bazel 的 BUILD 文件 crates_repository( name crates_index, cargo_lockfile //:Cargo.lock, manifests [//:Cargo.toml, //sub_crate:Cargo.toml], )实际迁移时还踩过一个坑crate_universe生成的 BUILD 文件不包含build.rs的编译产物导致依赖 OpenSSL 的 crate 在 Bazel 下直接编译失败需要手动补cargo_build_script规则。五、总结Cargo——Rust 专属、简单直观、生态完善。纯 Rust 项目首选。Bazel——多语言、增量精确、分布式缓存。大团队多语言项目的最佳选择。迁移成本——如果未来可能上 Bazel建议一开始就把Cargo.toml和目录结构调整好减少后续迁移的工作量。混合使用——也可以在 Bazel 项目中让 Bazel 管理外部依赖内部 Rust 代码继续用cargo build开发通过 Bazel 的 genrule 桥接。作为程序员接触 Bazel 的过程确实比 Cargo 痛苦很多——Starlark 语法、WORKSPACE 配置、BUILD 文件规则……每一个概念都值得专门写一篇文章。但理解了它的设计理念后不得不承认在大型多语言项目中Bazel 的复杂度是有道理的因为它解决的问题本身就是复杂的。

相关新闻