Workspaces: monorepos com Cargo
1. Introdução aos Workspaces no Cargo
Quando um projeto Rust cresce além de um único crate, surge a necessidade de organizar múltiplos crates que se relacionam entre si. É aqui que os workspaces do Cargo entram em cena. Um workspace é um conjunto de um ou mais crates que compartilham o mesmo diretório raiz, o mesmo arquivo Cargo.lock e um processo de build unificado.
A principal diferença entre manter múltiplos crates independentes e usar um workspace está na coordenação. Com crates independentes, cada um possui seu próprio Cargo.lock, suas dependências podem divergir e você precisa gerenciar versões manualmente. Já em um workspace, todos os membros compartilham a resolução de dependências, garantindo consistência.
Os benefícios são claros:
- Dependências compartilhadas: uma única versão de cada dependência é resolvida para todo o workspace
- Build unificado: cargo build compila tudo que precisa ser recompilado de uma só vez
- Versionamento sincronizado: mudanças em múltiplos crates podem ser feitas em um único commit
2. Estrutura e Configuração de um Workspace
Para criar um workspace, você precisa de um Cargo.toml raiz que define a seção [workspace] e lista os membros. A estrutura básica é:
meu-projeto/
├── Cargo.toml # Workspace raiz
├── Cargo.lock # Compartilhado
├── crates/
│ ├── lib-core/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── lib.rs
│ └── bin-app/
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
O Cargo.toml raiz fica assim:
[workspace]
members = [
"crates/lib-core",
"crates/bin-app",
]
resolver = "2"
Você pode usar glob patterns para incluir múltiplos diretórios:
[workspace]
members = ["crates/*"]
exclude = ["crates/legacy-crate"]
A chave exclude é útil para evitar que diretórios específicos sejam tratados como membros do workspace.
3. Gerenciamento de Dependências em Workspaces
Em workspaces, as dependências podem ser declaradas de duas formas: no Cargo.toml raiz (compartilhadas) ou individualmente em cada crate.
Dependências compartilhadas (Rust 2024+):
# Cargo.toml raiz
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# crates/lib-core/Cargo.toml
[dependencies]
serde.workspace = true
tokio.workspace = true
Dependências locais entre crates:
# crates/bin-app/Cargo.toml
[dependencies]
lib-core = { path = "../lib-core" }
O Cargo.lock compartilhado garante que todos os membros usem exatamente as mesmas versões das dependências, evitando o temido "inferno das dependências".
4. Compilação e Build em Workspaces
Os comandos do Cargo funcionam de forma inteligente em workspaces:
# Compila todos os membros
cargo build --workspace
# Compila apenas um crate específico
cargo build -p lib-core
# Verifica se o código compila (mais rápido que build)
cargo check --workspace
# Executa testes de todos os membros
cargo test --workspace
A compilação incremental é compartilhada: se você modificar apenas lib-core, apenas ele e seus dependentes diretos serão recompilados. O cache de compilação fica em target/ na raiz do workspace.
Flags úteis:
- --workspace: opera em todos os membros
- --package <nome> ou -p <nome>: opera em um membro específico
- --all: sinônimo de --workspace (depreciado em versões recentes)
5. Organização e Versionamento de Crates
Workspaces oferecem flexibilidade no versionamento. Você pode optar por:
Versionamento independente: cada crate tem sua própria versão no Cargo.toml.
Versionamento sincronizado com workspace.package:
# Cargo.toml raiz
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Seu Nome"]
license = "MIT"
# crates/lib-core/Cargo.toml
[package]
name = "lib-core"
version.workspace = true
edition.workspace = true
Para publicar no crates.io seletivamente:
cargo publish -p lib-core
cargo publish -p bin-app
Boas práticas incluem manter CHANGELOG.md por crate e usar tags Git como lib-core-v0.2.0 para rastrear versões.
6. Testes e Integração Contínua em Monorepos
Executar testes em todo o workspace é simples:
cargo test --workspace
Para CI com GitHub Actions, um exemplo básico:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo test --workspace
Para isolar falhas e testar apenas crates modificados, ferramentas como cargo-watch e scripts personalizados com git diff podem ser usados:
# Exemplo conceitual (requer script customizado)
cargo test -p $(git diff --name-only HEAD~1 | grep '^crates/' | cut -d'/' -f2 | sort -u)
7. Ferramentas e Dicas Avançadas
Workspaces aninhados: você pode ter workspaces dentro de workspaces, embora isso seja raro e geralmente desnecessário.
[workspace.dependencies]: centraliza todas as dependências, facilitando atualizações em massa.
Integração com ferramentas:
- cargo-make: task runner para automatizar builds complexos
- cargo-release: gerencia versionamento e publicação de múltiplos crates
- cargo-profiler: para análise de desempenho em monorepos
Exemplo de uso com cargo-make:
# Makefile.toml
[tasks.build-all]
command = "cargo"
args = ["build", "--workspace"]
[tasks.test-all]
command = "cargo"
args = ["test", "--workspace"]
8. Considerações Finais e Casos de Uso
Workspaces não são para todos os projetos. Evite usá-los quando:
- O projeto é muito pequeno (um ou dois crates simples)
- As equipes são desconexas e preferem releases independentes
- Há necessidade de versionamento drasticamente diferente entre crates
Exemplos reais de uso bem-sucedido:
- Servo: o motor de renderização da Mozilla usa workspaces extensivamente
- Tokio: a runtime assíncrona tem múltiplos crates em um workspace
- Rust Analyzer: o LSP para Rust é um monorepo complexo com dezenas de crates
Para se aprofundar, explore temas como features condicionais em workspaces, documentação unificada com cargo doc --workspace e estratégias de publicação contínua.
Referências
- The Cargo Book: Workspaces — Documentação oficial do Cargo sobre workspaces, com configuração detalhada e exemplos
- Rust by Example: Workspaces — Tutorial prático com exemplos de código para iniciantes em workspaces
- The Rust Programming Language: Workspaces — Capítulo do livro oficial sobre organização de projetos com workspaces
- Cargo Workspaces: A Complete Guide — Artigo técnico do LogRocket cobrindo casos de uso avançados e armadilhas comuns
- Monorepos in Rust with Cargo Workspaces — Guia prático sobre como estruturar monorepos Rust com CI/CD e boas práticas
- cargo-release Documentation — Ferramenta para automatizar releases em workspaces com múltiplos crates
- cargo-watch — Utilitário para reexecutar comandos quando arquivos mudam, útil em desenvolvimento de workspaces