关于 rust-workspace
rust-workspace 面向 Rust 开发者,帮助判断采用单个 crate 还是 Cargo workspace,并规划成员组织方式。内容涵盖目录拓扑、依赖方向、共享配置,以及混合根包的迁移。
主要功能
- 01单 crate 与 workspace 的取舍
- 02workspace 布局模式选择
- 03虚拟清单与根包的权衡
- 04workspace 共享配置
- 05依赖方向与 crate 边界
- 06混合根包诊断与迁移
适用场景
- 01判断子域是否需要独立 crate
- 02规划 Cargo workspace 成员布局
- 03在成员间共享元数据和依赖
- 04重构混合根包
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ full-stack-skills/ rust-skills --skill rust-workspace安装后在 Claude Code 中输入/rust-workspace确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlexamples/examples.mdexamples/golden-layout/Cargo.tomlexamples/golden-layout/src/domain.rslib.rsexamples/golden-workspace/Cargo.lockCargo.tomlexamples/golden-workspace/my-cli/Cargo.tomlexamples/golden-workspace/my-cli/src/main.rsexamples/golden-workspace/my-core/Cargo.tomlexamples/golden-workspace/my-core/src/lib.rsexamples/golden-workspace/my-net/Cargo.tomlexamples/golden-workspace/my-net/src/lib.rsreferences/dependency-direction.mdmixed-root-package-antipattern.mdproduction-workspace-boundaries.mdreferences.mdsa-token-rs-case-study.mdvirtual-vs-root-manifest.mdworkspace-dependencies.mdworkspace-layouts.md详细介绍
Rust 工作区与项目拓扑结构
权威资料:Cargo Book — Workspaces、The Book ch7 和 ch14-03、Rust Reference ch7、RFC 1525、matklad — Large Rust Workspaces。
本技能决定项目应该包含多少个 crate,以及它们之间的关系。配套的 rust-module-layout 决定单个 crate 的 src/ 中包含什么。
能力边界
✅ 擅长
- 决定使用单 crate 还是多 crate 工作区(包含五个具体触发条件的决策树)
- 由项目需求驱动的工作区布局:小型根目录扁平布局、混合/按领域分组布局、集中于
crates/的布局、嵌套布局和根包布局 - 虚拟清单与根包——权衡和迁移
- 工作区级共享配置:
[workspace.package]、[workspace.dependencies]、[workspace.lints] - 依赖方向 DAG——types → core → sdk → server → binary
- 诊断并重构“混合根包”反模式
- crate 命名、发布和成员间的版本协调
⚠️ 前置条件
- Rust 所有权和基础模块语法——参见
rust-stable - crate 内部的模块布局(lib.rs 门面、mod 声明、可见性)——参见
rust-module-layout
❌ 不在范围内
- Cargo.toml
[dependencies]语法和功能解析 → 使用rust-cargo-build - crate 内部的 src/ 目录布局 → 使用
rust-module-layout - Rust 语法基础 → 使用
rust-stable - 测试组织 → 使用
rust-testing
适用场景
- “我应该把它拆分成工作区,还是保留为一个 crate?”
- “如何配置 Cargo 工作区?”
- “重构我的 rbatis 风格混合根包”
- “为什么 cargo 只会在根目录构建一个 crate?”
- “如何在工作区成员之间共享依赖?”
- “我的依赖方向是否正确?”
数据隐私
此技能不会收集、存储或传输任何用户数据。
基础知识——包、crate、模块
crate 内部的模块布局细节(mod 文件、可见性、重新导出)由配套的
rust-module-layout技能介绍。本节仅介绍影响项目级决策的部分。
包、crate 与工作区
| 术语 | 含义 |
|---|---|
| 包 | 一个 Cargo.toml 及其指向的源代码;发布到 crates.io 的单位 |
| Crate | 编译单元——可以是库(src/lib.rs),也可以是二进制程序(src/main.rs、src/bin/*.rs)。一个包可以包含多个 crate(1 个库 + N 个二进制程序)。 |
| 工作区 | 共享一个 Cargo.lock 和 target/ 的包集合。 |
| 模块 | crate 内部可命名的作用域——使用 mod foo; 声明,并解析到 src/foo.rs 或 src/foo/mod.rs。 |
单包布局
# Library only # Binary only # Library + binary
my-lib/ my-app/ my-crate/
├── Cargo.toml ├── Cargo.toml ├── Cargo.toml
└── src/ └── src/ └── src/
└── lib.rs └── main.rs ├── lib.rs
└── main.rs
# Multi-binary (one package, multiple binaries)
my-app/
├── Cargo.toml
└── src/
├── lib.rs
├── main.rs # binary named after the package
└── bin/
├── tool_a.rs # binary `tool_a`
└── tool_b.rs # binary `tool_b`
模块声明(令 Java/Python 开发者意外的规则)
// src/lib.rs — declare modules explicitly; directories are NOT auto-discovered
pub mod front_of_house; // loads src/front_of_house.rs OR src/front_of_house/mod.rs
mod back_of_house; // private
pub(crate) mod utils; // crate-visible
所有内容默认均为私有;pub 用于公开。完整的可见性模型、父级边界可达性规则,以及现代的 foo.rs + foo/ 布局,请参见 rust-module-layout。
use 路径(快速参考)
use crate::front_of_house::hosting; // absolute (current crate)
use std::collections::HashMap; // absolute (external)
use self::back_of_house::Cook; // relative (current module)
use super::parent_module::helper; // relative (parent)
use std::{cmp::Ordering, io}; // nested
use std::fmt::Result as FmtResult; // alias
pub use crate::front_of_house::hosting; // re-export (facade pattern)
条件编译
#[cfg(target_os = "linux")]
fn only_linux() {}
#[cfg(feature = "serde")]
fn with_serde() {}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
struct Config;
if cfg!(target_os = "linux") { /* runtime check */ }
项目脚手架
cargo new my-app # Binary project
cargo new my-lib --lib # Library project
cargo init # Initialize current directory
对于多 crate 工作区,手动创建根虚拟清单(没有 cargo workspace new),根据下方的决策模型选择成员拓扑,然后运行 cargo new --lib <selected-member-path>。对于小型根目录扁平工作区,可以使用 cargo new --lib my-core;对于集中于 crates/ 的大型工作区,可以使用 cargo new --lib crates/my-core。如需使用模板,请使用 cargo-generate。
工作区模式(单 Crate 与多 Crate)
权威来源:Cargo Book — Workspaces、The Book ch14-03、matklad — Large Rust Workspaces、RFC 1525。
工作区是一个或多个共享同一个 Cargo.lock 和 target/ 目录的包的集合。工作区的存在有四个原因:加快构建(共享依赖项编译)、协调版本(一个锁文件、一套 cargo publish 流程)、明确边界(每个 Crate 都是独立的编译单元,拥有自己的公共 API),以及独立复用(用户可以依赖某个 Crate,而无需引入其他 Crate)。
决策树 — 应使用多少个 Crate?
Is this project one cohesive library or one binary?
├── Yes → Single-crate layout (Section 8.1)
└── No, it has multiple subdomains
├── Do the subdomains share types and call each other heavily?
│ └── Single-crate with module directories (use rust-module-layout skill)
└── At least one subdomain is independently useful / independently versioned / has different deps?
└── Multi-crate workspace (Section 8.2)
经验法则:只有在满足以下至少一项条件时,才拆分为多个 Crate(来源:references/production-workspace-boundaries.md):
- 子域需要独立发布或供第三方复用
- 子域需要独立的
feature/ target /no_std/ WASM 边界 - 需要在编译时禁止反向依赖(例如,
core不得依赖tokio) - 子域拥有独立版本管理的公共 API
- 测试 / 构建周期存在显著差异
如果以上条件都不满足,优先使用模块,而不是 Crate。模块成本更低(无需发布、协调版本或单独的 Cargo.toml)。有关 Crate 内部的组织方式,请参阅 rust-module-layout 技能。
单 Crate 布局
适用于中小型项目的默认布局。一个 Cargo.toml、一个 src/。有四种形式:仅库、仅二进制、库 + 二进制(同一包),或多二进制(src/bin/<name>.rs)。四种形式的骨架示例请参阅 examples/single-crate.md。
何时升级为工作区:当二进制或库需要独立的依赖项 / feature / target 边界、发布节奏或构建 / 测试周期时。仅仅多出一个二进制文件时,仍可将其放在同一包的 src/bin/ 下。
多 Crate 工作区 — 两种根目录形式
Cargo 支持两种类型的根 Cargo.toml:
| 形式 | 根目录中包含什么 | 适用场景 |
|---|---|---|
| 虚拟清单 | 仅 [workspace] — 没有 [package] | 新建工作区的默认选择。 根目录整洁,没有顶层 src/;成员可采用根目录平铺式、混合式或集中式布局。 |
| 根包 | [workspace] + [package] + 根目录下的 src/ | 适用于小型(2–3 个包)工作区,其中某个包明确是主包;同时需要接受下文所述的权衡。 |
Cargo 团队和社区(尤其是 matklad 的 Large Rust Workspaces)建议所有非简单工作区都采用虚拟清单:
- 根目录杂乱 — 根目录中的
[package]会迫使src/、tests/、benches/与其他 Crate 的目录一同放在顶层。 - 命令使用不便 — 使用根包时,在根目录运行
cargo build只会构建根包;要构建全部内容则需要使用--workspace。虚拟清单默认会构建所有成员。 - 发布不便 — 根包与成员并存,会导致
cargo publish的发布顺序令人困惑。
由项目驱动的布局模式
| 模式 | 布局 | 适用场景 | 使用者 |
|---|---|---|---|
| A. 根目录平铺式虚拟工作区 | <member>/ 与根目录下的 Cargo.toml 并列 | 适用于小型、紧密协作的工作区,通常有 2-8 个包/成员 | Tokio、Serde、Clap |
| B. 混合式/按领域分组 | 核心成员位于根目录,另设 support/*、examples/* 或其他实际分组目录 | 工作区不断增长,且有稳定的成员组,或适配器/示例较繁杂 | 框架和迁移工作区 |
| C. 集中式/分组式 | crates/<member>/ 或 crates/<category>/* | 大型工作区、多语言仓库,或需要用 Rust 容器组织内容的根目录 | Bevy 风格的大型仓库 |
| D. 嵌套子工作区 | vendor/<sub>/,并带有自己的 [workspace] | 隔离 Git 子模块或供应商提供的工作区(少见) | 供应商提供的上游工作区 |
| E. 根目录包 | 根目录下有 [package],并有并列或集中管理的成员 | 有 2-3 个包,其中一个 crate 确实是主包 | 库加配套 CLI |
工作区成员数量是参考信号,而非硬性规定。应先确定包/crate 的边界,再考虑仓库根目录的杂乱程度、独立发布需求、适配器/插件分组、目标平台、示例/测试、其他语言以及既有路径。作为评审参考:2-8 个紧密协作的包通常应保持根目录平铺;约 8-20 个包时,可比较根目录平铺和混合式布局;20 个以上通常适合分组或采用 crates/。除非有 Rust 边界方面的理由,否则绝不要为每个 Maven/Gradle 模块都创建一个 Cargo 包。
完整的骨架和决策矩阵:references/workspace-layouts.md。
模式 A 骨架——小型工作区的默认布局
my-project/
├── Cargo.toml # virtual: [workspace]
├── my-core/ # library
│ ├── Cargo.toml
│ └── src/lib.rs
├── my-net/ # library
│ └── ...
└── my-cli/ # binary
└── src/main.rs
# root Cargo.toml — virtual manifest
[workspace]
resolver = "3"
members = ["my-core", "my-net", "my-cli"]
[workspace.package]
edition = "2024"
version = "0.1.0"
license = "Apache-2.0"
[workspace.dependencies]
serde = { version = "1", features = ["derive"] } # pin once
my-core = { path = "my-core" } # internal deps here
# my-net/Cargo.toml
[package]
name = "my-net"
version.workspace = true # inherits from [workspace.package]
edition.workspace = true
[dependencies]
serde.workspace = true # inherits from [workspace.dependencies]
my-core.workspace = true # internal workspace dep
“混合根目录包”反模式
从 Java/Maven 或 Python 转来的开发者常犯的错误:在工作区根目录下放置一个包含大量代码的真实 [package],同时还有成员 crate。
# Cargo.toml — DON'T (anonymized from a real public crate)
[workspace]
members = ["codegen", "macro-driver", "example"]
[package] # ❌ root package + workspace together
name = "my_framework"
version = "4.9.6"
[dependencies]
my-codegen = { path = "codegen" }
my-macro-driver = { path = "macro-driver" }
my-framework/
├── Cargo.toml # 163 lines mixing workspace + package config
├── src/ # ❌ the root package's source pollutes the root
│ ├── lib.rs # 30 lines of `pub use *::*`
│ ├── crud.rs # 591 lines (monster file — see rust-module-layout)
│ ├── executor.rs # 642 lines
│ └── plugin/
├── codegen/ # member crate
├── macro-driver/ # member crate
└── tests/
五个问题:根目录变得杂乱、命令行为不对称(cargo build 只会构建根目录包)、版本绑定、没有清晰的门面(lib.rs 会变成通配符重新导出的中转站),以及发布后迁移会造成破坏性变更。
重构目标——采用虚拟清单,将所有 crate 放在 crates/ 下:
my-framework/
├── Cargo.toml # virtual manifest — workspace config only
└── crates/
├── my-framework/ # was: src/ at root
│ ├── Cargo.toml
│ └── src/
├── codegen/
├── macro-driver/
└── example/
迁移过程按固定步骤执行,且保留已发布的 crate 名称——完整的六步流程见 references/mixed-root-package-antipattern.md,示例见 examples/migration-mixed-to-virtual.md。
工作区级共享配置
三个工作区级表可避免成员之间出现配置偏差。完整语法见 references/workspace-dependencies.md。
# root Cargo.toml
[workspace.package] # shared package metadata
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
license = "Apache-2.0"
[workspace.dependencies] # shared external + internal deps
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
my-core = { path = "crates/core" }
[workspace.lints.rust] # shared lint config
unsafe_code = "forbid"
missing_docs = "warn"
成员通过 .workspace = true 加入配置:
# crates/net/Cargo.toml
[package]
name = "my-net"
version.workspace = true # inherits 0.1.0
edition.workspace = true
[dependencies]
serde.workspace = true # inherits pinned version + features
my-core.workspace = true
[lints]
workspace = true # inherits shared lints
依赖方向(DAG)
crate 依赖构成一个有向无环图。禁止循环依赖(cargo 会报错)。应有意识地规划依赖方向。
low-level types ──► domain logic ──► application ──► binary
(my-types) (my-core) (my-server) (my-cli)
反模式——依赖方向泄漏:
# crates/core/Cargo.toml — ❌ core depending on CLI concerns
[dependencies]
clap = "4" # CLI lib has no business in core
my-cli = { path = "../cli" } # core cannot depend on the binary
验证:
cargo tree # visualize the dep graph
cargo tree --invert --package my-core # what depends on my-core?
如果 my-types 显示依赖了任何非 std 的内容,说明依赖方向有误。分层规则见 references/dependency-direction.md。
参考示例——sa-token-rs(包含 11 个 crate 的虚拟工作区)
sa-token-rs 是 Java Sa-Token 权限框架的真实 Rust 移植版,是本技能所讲解各类模式的典范。它采用虚拟清单,包含 11 个 crate,依赖图分为 5 层且结构清晰,按 crates/<category>/ 分组,并完整继承 [workspace.*] 配置。
布局(集中式与按领域分组的混合布局)
sa-token-rs/
├── Cargo.toml # virtual manifest
└── crates/
├── sa-token/ # Layer 3: facade (public API)
├── sa-token-core/ # Layer 1: core types & traits (no internal deps)
├── sa-token-derive/ # Layer 2: proc-macro (core only)
├── sa-token-context-mock/ # Layer 2: mock context (core only)
├── sa-token-dao-memory/ # Layer 2: memory DAO (core only)
├── sa-token-dao-redis/ # Layer 4: redis DAO (facade + core)
├── sa-token-axum/ # Layer 4: axum adapter (facade + core)
├── sa-token-plugin/ # domain grouping inside container
│ ├── sa-token-jwt/ # Layer 4: JWT plugin
│ └── sa-token-sign/ # Layer 4: signature plugin
├── sa-token-demo/ # domain grouping inside container
│ └── sa-token-demo-axum/ # Layer 5: binary example
└── sa-token-test/ # Layer 5: integration tests
DAG——5 层,每条箭头都向上
L1 sa-token-core
▲
L2 sa-token-derive sa-token-context-mock sa-token-dao-memory
▲ ▲ ▲
L3 sa-token (facade — aggregates L2 leaves into one public API)
▲
L4 sa-token-dao-redis sa-token-axum sa-token-jwt sa-token-sign
▲
L5 sa-token-demo-axum sa-token-test (binaries + tests)
为什么它堪称典范
- 虚拟清单 — 没有根目录的
[package];cargo build默认会构建全部 11 个 crate。 - 封装式与按领域分组的混合布局 — 叶子 crate 放在
crates/sa-token-*下,插件和演示程序则分别按系列归类在crates/下。 - 全面继承
[workspace.*]— 包括version、edition = "2024"、rust-version = "1.85"、license、repository、[workspace.lints](unsafe_code = "forbid"、missing_docs = "warn"、clippy::pedantic = "warn"),以及用于内部和外部依赖的[workspace.dependencies]。 - 由功能门控的可选依赖 —
sa-token提供redis = ["dep:sa-token-dao-redis"]。 - 清晰的 DAG — core 没有内部依赖;门面 crate 负责聚合;适配器、插件和 DAO 位于其上层;演示程序和测试会使用所有这些 crate。
查看 references/sa-token-rs-case-study.md,了解完整的逐成员 Cargo.toml 分析、DAG 验证命令,以及这种布局如何支持独立发布。
工作区命令速查表
cargo build --workspace # build every member (default in virtual manifest)
cargo build -p my-core # build one member
cargo test --workspace # test every member
cargo check --workspace # fast type-check everything
cargo doc --workspace --no-deps # docs for every member
cargo publish -p my-core # publish one member
cargo run -p my-cli # run a specific binary
cargo workspaces version minor # bump all members in lockstep (needs cargo-workspaces)
cargo workspaces publish # publish all members in topological order
如需跨所有成员批量升级版本并发布,请安装 cargo-workspaces。
工作流
- 确定项目类型和 crate 边界 — 从单个 crate 开始;只有在需要独立复用或版本管理、隔离依赖或目标平台,或拥有不同生命周期时才拆分。否则使用模块。
- 梳理结构信号 — 包括最终的包数量、发布单元、适配器/插件系列、示例/测试、仓库根目录的杂乱程度、语言,以及对兼容性敏感的现有路径。
- 选择布局 — 小型且内聚的工作区:根目录平铺;包含实际系列的成长型工作区:混合/按领域分组;大型或多语言工作区:封装式/分组式。将数量视为审查触发条件,而非硬性门槛。
- 在工作区层级固定共享元数据和依赖 — 使用
[workspace.package]、[workspace.dependencies]、[workspace.lints]。成员通过.workspace = true选择继承。 - 验证依赖方向 —
cargo tree --invert --package my-core必须显示只有更高层级的 crate 依赖更低层级的 crate。不得有反向边,也不得有环。 - 选择命名约定 — crate 名称使用 snake_case,
Cargo.toml中的name使用 kebab-case。使用完整单词;避免使用 2 个字母的缩写(参见rust-module-layout的命名参考)。 - 验证 — 运行
cargo check --workspace、cargo tree、cargo doc --workspace --no-deps。检查生成的文档侧边栏 — 它应体现预期的公共 API 范围。
注意事项
- 区分
crate::(引用当前 crate 根)和::other_crate_name::(对外部 crate 的绝对路径引用)。 - 受限可见性(如
pub(crate))不受 Edition 边界限制;请确认项目的 Rust 版本,不要想当然地认为 Edition 2015 不允许使用它。 module.rs和module/mod.rs都受支持;新代码通常优先采用前者,但不要把使用mod.rs的弃用警告误认为错误。- 工作区解析器设置会全局生效;Edition 2021 默认使用 resolver 2,而 Edition 2024 默认使用 resolver 3。
pub(in path)中的路径参数必须指向当前项的祖先模块,不能用于跨任意兄弟模块公开可见性。- 使用
#[path]属性会绕过文件系统约定——应用后,模块路径不再遵循默认的文件树结构。 - 虚拟清单不能包含
[dependencies]或[package]。 如果看到failed to parse manifest at ... missing field package,说明你混用了虚拟清单和根包语法。要么移除[package](虚拟清单),要么添加它(根包)——不要两种都用一半。 - 在根包工作区中运行
cargo build只会构建根包。 使用--workspace才能构建所有内容。对于虚拟清单,cargo build已会构建所有成员——更省心。 - 工作区 glob 只匹配一层。
members = ["crates/*"]不会匹配crates/libs/core/;请使用crates/libs/*或显式路径。根目录平铺的成员可以使用显式名称或范围明确的 glob,但绝不能使用members = ["*"],因为它会匹配非包目录。 - 工作区内部的
path依赖发布时仍需要版本。my-core = { path = "../core", version = "0.1.0" }——如果没有version,cargo publish会拒绝发布。使用[workspace.dependencies]可将版本集中在一处。 - 升级工作区共享依赖时,只需编辑根目录的
Cargo.toml。 不要在成员 crate 中重新固定版本——这会违背初衷并导致版本漂移。 - 重命名已发布的 crate 属于破坏性变更。 在旧名称下添加一个已弃用的别名 crate(
pub use my_new_name::*;),保留一个发布周期后再移除。
按需资源
- 布局示例 — 基本模块布局
- 概念速查
- 工作区布局 — 面向不同项目规模的根目录平铺、混合、容纳式、嵌套和根包骨架
- 虚拟清单与根清单 — 深入讲解如何选择,并比较命令使用体验
- 混合根包反模式 — 完整诊断及 rbatis 风格布局的迁移路径
- 工作区依赖 — 深入讲解
[workspace.package]/[workspace.dependencies]/[workspace.lints] - 依赖方向 — DAG 规则、分层及方向不清的反模式
sa-token-rs案例研究 — 真实的 11-crate 虚拟工作区,包含 5 层 DAG、逐成员详细解析、DAG 验证及发布影响- 生产级工作区边界:拆分协议、领域、平台、传输层、SDK、适配器和二进制程序时,请阅读相关章节。
examples/golden-layout/:单 crate 的 CI 编译示例examples/golden-workspace/:包含三个 crate 的小型根目录平铺虚拟工作区示例
Official References
- The Book ch 7 — Managing Growing Projects
- The Book ch14-03 — Cargo Workspaces
- Rust Reference ch 7 (Items)
- Rust Reference ch 7.2 (Modules)
- Cargo Book — Workspaces
- Cargo Book —
workspace.package - Cargo Book — Workspace dependencies
- RFC 1525 — Cargo Workspaces
- matklad — Large Rust Workspaces (community best-practice reference)
- Rust API Guidelines — Organization (C-HIERARCHY, C-REEXPORT)