mcpField
R
rust-workspace

rust-workspace

github.com/full-stack-skills开发12121 次安装5 周前更新
代码仓库

关于 rust-workspace

rust-workspace 面向 Rust 开发者,帮助判断采用单个 crate 还是 Cargo workspace,并规划成员组织方式。内容涵盖目录拓扑、依赖方向、共享配置,以及混合根包的迁移。

主要功能

  1. 01单 crate 与 workspace 的取舍
  2. 02workspace 布局模式选择
  3. 03虚拟清单与根包的权衡
  4. 04workspace 共享配置
  5. 05依赖方向与 crate 边界
  6. 06混合根包诊断与迁移

适用场景

  1. 01判断子域是否需要独立 crate
  2. 02规划 Cargo workspace 成员布局
  3. 03在成员间共享元数据和依赖
  4. 04重构混合根包

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/full-stack-skills/rust-skills --skill rust-workspace

安装后在 Claude Code 中输入/rust-workspace确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
examples/
examples.md
examples/golden-layout/
Cargo.toml
examples/golden-layout/src/
domain.rs
lib.rs
examples/golden-workspace/
Cargo.lock
Cargo.toml
examples/golden-workspace/my-cli/
Cargo.toml
examples/golden-workspace/my-cli/src/
main.rs
examples/golden-workspace/my-core/
Cargo.toml
examples/golden-workspace/my-core/src/
lib.rs
examples/golden-workspace/my-net/
Cargo.toml
examples/golden-workspace/my-net/src/
lib.rs
references/
dependency-direction.md
mixed-root-package-antipattern.md
production-workspace-boundaries.md
references.md
sa-token-rs-case-study.md
virtual-vs-root-manifest.md
workspace-dependencies.md
workspace-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/ 中包含什么。

能力边界

✅ 擅长
  1. 决定使用单 crate 还是多 crate 工作区(包含五个具体触发条件的决策树)
  2. 由项目需求驱动的工作区布局:小型根目录扁平布局、混合/按领域分组布局、集中于 crates/ 的布局、嵌套布局和根包布局
  3. 虚拟清单与根包——权衡和迁移
  4. 工作区级共享配置:[workspace.package]、[workspace.dependencies]、[workspace.lints]
  5. 依赖方向 DAG——types → core → sdk → server → binary
  6. 诊断并重构“混合根包”反模式
  7. crate 命名、发布和成员间的版本协调
⚠️ 前置条件
  1. Rust 所有权和基础模块语法——参见 rust-stable
  2. crate 内部的模块布局(lib.rs 门面、mod 声明、可见性)——参见 rust-module-layout
❌ 不在范围内
  1. Cargo.toml [dependencies] 语法和功能解析 → 使用 rust-cargo-build
  2. crate 内部的 src/ 目录布局 → 使用 rust-module-layout
  3. Rust 语法基础 → 使用 rust-stable
  4. 测试组织 → 使用 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):

  1. 子域需要独立发布或供第三方复用
  2. 子域需要独立的 feature / target / no_std / WASM 边界
  3. 需要在编译时禁止反向依赖(例如,core 不得依赖 tokio)
  4. 子域拥有独立版本管理的公共 API
  5. 测试 / 构建周期存在显著差异

如果以上条件都不满足,优先使用模块,而不是 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)建议所有非简单工作区都采用虚拟清单:

  1. 根目录杂乱 — 根目录中的 [package] 会迫使 src/、tests/、benches/ 与其他 Crate 的目录一同放在顶层。
  2. 命令使用不便 — 使用根包时,在根目录运行 cargo build 只会构建根包;要构建全部内容则需要使用 --workspace。虚拟清单默认会构建所有成员。
  3. 发布不便 — 根包与成员并存,会导致 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)
为什么它堪称典范
  1. 虚拟清单 — 没有根目录的 [package];cargo build 默认会构建全部 11 个 crate。
  2. 封装式与按领域分组的混合布局 — 叶子 crate 放在 crates/sa-token-* 下,插件和演示程序则分别按系列归类在 crates/ 下。
  3. 全面继承 [workspace.*] — 包括 version、edition = "2024"、rust-version = "1.85"、license、repository、[workspace.lints](unsafe_code = "forbid"、missing_docs = "warn"、clippy::pedantic = "warn"),以及用于内部和外部依赖的 [workspace.dependencies]。
  4. 由功能门控的可选依赖 — sa-token 提供 redis = ["dep:sa-token-dao-redis"]。
  5. 清晰的 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。


工作流

  1. 确定项目类型和 crate 边界 — 从单个 crate 开始;只有在需要独立复用或版本管理、隔离依赖或目标平台,或拥有不同生命周期时才拆分。否则使用模块。
  2. 梳理结构信号 — 包括最终的包数量、发布单元、适配器/插件系列、示例/测试、仓库根目录的杂乱程度、语言,以及对兼容性敏感的现有路径。
  3. 选择布局 — 小型且内聚的工作区:根目录平铺;包含实际系列的成长型工作区:混合/按领域分组;大型或多语言工作区:封装式/分组式。将数量视为审查触发条件,而非硬性门槛。
  4. 在工作区层级固定共享元数据和依赖 — 使用 [workspace.package]、[workspace.dependencies]、[workspace.lints]。成员通过 .workspace = true 选择继承。
  5. 验证依赖方向 — cargo tree --invert --package my-core 必须显示只有更高层级的 crate 依赖更低层级的 crate。不得有反向边,也不得有环。
  6. 选择命名约定 — crate 名称使用 snake_case,Cargo.toml 中的 name 使用 kebab-case。使用完整单词;避免使用 2 个字母的缩写(参见 rust-module-layout 的命名参考)。
  7. 验证 — 运行 cargo check --workspace、cargo tree、cargo doc --workspace --no-deps。检查生成的文档侧边栏 — 它应体现预期的公共 API 范围。

注意事项

  1. 区分 crate::(引用当前 crate 根)和 ::other_crate_name::(对外部 crate 的绝对路径引用)。
  2. 受限可见性(如 pub(crate))不受 Edition 边界限制;请确认项目的 Rust 版本,不要想当然地认为 Edition 2015 不允许使用它。
  3. module.rs 和 module/mod.rs 都受支持;新代码通常优先采用前者,但不要把使用 mod.rs 的弃用警告误认为错误。
  4. 工作区解析器设置会全局生效;Edition 2021 默认使用 resolver 2,而 Edition 2024 默认使用 resolver 3。
  5. pub(in path) 中的路径参数必须指向当前项的祖先模块,不能用于跨任意兄弟模块公开可见性。
  6. 使用 #[path] 属性会绕过文件系统约定——应用后,模块路径不再遵循默认的文件树结构。
  7. 虚拟清单不能包含 [dependencies] 或 [package]。 如果看到 failed to parse manifest at ... missing field package,说明你混用了虚拟清单和根包语法。要么移除 [package](虚拟清单),要么添加它(根包)——不要两种都用一半。
  8. 在根包工作区中运行 cargo build 只会构建根包。 使用 --workspace 才能构建所有内容。对于虚拟清单,cargo build 已会构建所有成员——更省心。
  9. 工作区 glob 只匹配一层。 members = ["crates/*"] 不会匹配 crates/libs/core/;请使用 crates/libs/* 或显式路径。根目录平铺的成员可以使用显式名称或范围明确的 glob,但绝不能使用 members = ["*"],因为它会匹配非包目录。
  10. 工作区内部的 path 依赖发布时仍需要版本。 my-core = { path = "../core", version = "0.1.0" }——如果没有 version,cargo publish 会拒绝发布。使用 [workspace.dependencies] 可将版本集中在一处。
  11. 升级工作区共享依赖时,只需编辑根目录的 Cargo.toml。 不要在成员 crate 中重新固定版本——这会违背初衷并导致版本漂移。
  12. 重命名已发布的 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

评论

支持 Markdown