O que são Workspaces no Cargo?
Um workspace no Cargo é um mecanismo para organizar múltiplos pacotes Rust dentro de um único repositório, compartilhando dependências e configurações comuns. Diferente de um monorepo simples, workspaces oferecem otimizações na compilação, permitindo que crates (pacotes) evitem compilações redundantes de dependências já processadas. Quando você trabalha com projetos grandes, é comum ter uma biblioteca core, aplicações cliente, ferramentas CLI e módulos especializados — e é exatamente nesse cenário que workspaces brilham.
A estrutura de um workspace é definida em um arquivo Cargo.toml raiz que lista todos os membros. Cada membro é uma crate independente com seu próprio Cargo.toml, mas todos compartilham o mesmo target/ para artefatos compilados. Isso economiza espaço em disco e tempo de build significativamente em projetos grandes.
Estruturando seu Primeiro Workspace
Criando a Estrutura Base
Comece criando um diretório para seu workspace e o arquivo Cargo.toml raiz:
[workspace]
members = [
"core",
"cli",
"web_server",
]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["seu_nome"]
Agora crie os diretórios das crates:
mkdir -p core cli web_server
cargo init --lib core
cargo init --bin cli
cargo init --bin web_server
Estrutura de Diretórios Resultante
projeto_grande/
├── Cargo.toml # Arquivo raiz do workspace
├── Cargo.lock
├── target/ # Compartilhado entre todas as crates
├── core/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── cli/
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
└── web_server/
├── Cargo.toml
└── src/
└── main.rs
Gerenciando Dependências em Workspaces
Dependências Compartilhadas
O grande benefício dos workspaces é evitar recompilação de dependências comuns. Configure-as no Cargo.toml raiz:
[workspace]
members = ["core", "cli", "web_server"]
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.35", features = ["full"] }
log = "0.4"
Depois, em cada crate, referencie essas dependências sem repetir versões:
core/Cargo.toml:
[package]
name = "core"
version.workspace = true
edition.workspace = true
[dependencies]
serde = { workspace = true }
log = { workspace = true }
cli/Cargo.toml:
[package]
name = "cli"
version.workspace = true
edition.workspace = true
[dependencies]
tokio = { workspace = true }
core = { path = "../core" }
log = { workspace = true }
Dependências Internas
Uma crate pode depender de outra no mesmo workspace usando path. A grande vantagem: mudanças em core são imediatamente visíveis em cli sem publicar em crates.io. Exemplo prático no cli/src/main.rs:
use core::models::User;
use log::info;
#[tokio::main]
async fn main() {
env_logger::init();
let user = User::new("Alice", 30);
info!("Usuário criado: {:?}", user);
}
E em core/src/lib.rs:
pub mod models;
pub struct User {
name: String,
age: u32,
}
impl User {
pub fn new(name: &str, age: u32) -> Self {
User {
name: name.to_string(),
age,
}
}
}
Compilando e Testando Workspaces
Compilação e Execução
O Cargo entende automaticamente a estrutura do workspace. Para compilar tudo:
cargo build
Para compilar uma crate específica:
cargo build -p cli
cargo build -p web_server
Para executar um binário específico:
cargo run -p cli
Testes em Larga Escala
Teste todos os pacotes simultaneamente:
cargo test
Ou teste uma crate específica:
cargo test -p core
Com resultado detalhado:
cargo test -- --test-threads=1 --nocapture
Exemplo de teste em core/src/lib.rs:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_creation() {
let user = User::new("Bob", 25);
assert_eq!(user.name, "Bob");
assert_eq!(user.age, 25);
}
}
Boas Práticas e Otimizações
Um workspace bem organizado segue alguns princípios. Sempre mantenha dependências no escopo [workspace.dependencies] para evitar version skew. Use features condicionais para compilações mais eficientes — por exemplo, a crate core pode expor features que apenas web_server necessita. Documente a responsabilidade de cada crate em um README.md na raiz do workspace explicando qual module lidar com qual aspecto da aplicação.
Para projetos muito grandes, considere separar workspaces por domínio: um para infraestrutura, outro para features de negócio. Use cargo workspaces (ferramenta auxiliar) para gerenciar versionamento e publicação coordenada. Finalmente, configure Cargo.toml raiz com default-members caso nem todas as crates devam ser compiladas por padrão:
[workspace]
members = ["core", "cli", "web_server", "internal_tools"]
default-members = ["core", "cli"]
Conclusão
Workspaces no Cargo são essenciais para organizar projetos Rust complexos sem sacrificar performance. Os três aprendizados principais são: (1) compartilhar dependências e configuração via [workspace.dependencies] reduz tempo de compilação drasticamente, (2) dependências internas com path permitem desenvolvimento ágil sem publicar em crates.io, e (3) a estrutura unificada de target/ economiza espaço enquanto mantém cada crate independente e testável. Domine esses conceitos e você terá uma base sólida para escalar projetos em Rust.