Rust Admin

Como Usar Workspaces no Cargo: Organizando Projetos Grandes em Rust em Produção Já leu

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 raiz que lista todos os membros. Cada membro é uma crate independente com seu próprio , mas todos compartilham o mesmo 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 raiz: Agora crie os diretórios das crates: Estrutura de Diretórios Resultante Gerenciando Dependências em Workspaces Dependências Compartilhadas O

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.

Referências


Artigos relacionados