---
title: "Cargo Workspaces: Organizando Projetos Grandes em Rust — 2026"
url: "https://rustlang.com.br/blog/cargo-workspaces-monorepos-rust-2026/"
markdown_url: "https://rustlang.com.br/blog/cargo-workspaces-monorepos-rust-2026.MD"
description: "Aprenda a usar Cargo workspaces para organizar monorepos em Rust. Dependências compartilhadas, builds otimizados e boas práticas para projetos grandes."
date: "2026-04-21"
author: "Equipe Rust Brasil"
---

# Cargo Workspaces: Organizando Projetos Grandes em Rust — 2026

Aprenda a usar Cargo workspaces para organizar monorepos em Rust. Dependências compartilhadas, builds otimizados e boas práticas para projetos grandes.


## Introdução

Quando seu projeto Rust cresce além de um único crate, a organização do código se torna um desafio real. Você começa com um binário simples, depois extrai uma biblioteca, adiciona uma CLI, talvez um servidor web, e logo tem 5+ crates que precisam compartilhar dependências e compilar juntos. É aqui que os **Cargo workspaces** brilham.

Um workspace é um conjunto de crates gerenciados por um único `Cargo.lock`, com compilação unificada e dependências compartilhadas. Grandes projetos Rust como o [Tokio](/ecossistema/tokio/), [Axum](/ecossistema/axum/) e o próprio compilador do Rust usam workspaces extensivamente.

Neste guia, vamos montar um workspace do zero, explorar os recursos avançados do [Cargo](/ecossistema/cargo/) para workspaces e compartilhar boas práticas que aprendemos com projetos reais.

---

## Por que Usar Workspaces?

Sem workspaces, cada crate tem seu próprio `Cargo.lock` e compila suas dependências independentemente. Isso causa:

- **Duplicação de compilação**: o mesmo crate compilado N vezes
- **Versões divergentes**: um crate usa `serde 1.0.200`, outro usa `serde 1.0.198`
- **Builds lentos**: sem cache compartilhado entre crates
- **Gerenciamento manual**: atualizar dependências em vários `Cargo.toml`

Com workspaces, você ganha:

- Um único `Cargo.lock` para todo o projeto
- Cache de compilação compartilhado no mesmo diretório `target/`
- Dependências unificadas com `workspace.dependencies`
- Comandos como `cargo test --workspace` que rodam tudo de uma vez

---

## Criando um Workspace do Zero

Vamos criar um projeto com 3 crates: uma biblioteca core, uma CLI e um servidor HTTP.

### 1. Estrutura Inicial

```bash
mkdir meu-projeto && cd meu-projeto
```

Crie o `Cargo.toml` raiz. Ele **não** define um `[package]` — apenas o workspace:

```toml
[workspace]
members = [
    "core",
    "cli",
    "servidor",
]
resolver = "2"

[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Sua Equipe <equipe@exemplo.com>"]
license = "MIT"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
clap = { version = "4", features = ["derive"] }
axum = "0.8"
```

### 2. Crate Core (Biblioteca)

```bash
cargo init core --lib
```

Edite `core/Cargo.toml`:

```toml
[package]
name = "meu-projeto-core"
version.workspace = true
edition.workspace = true

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }
```

Crie `core/src/lib.rs`:

```rust
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Usuario {
    pub id: u64,
    pub nome: String,
    pub email: String,
}

impl Usuario {
    pub fn novo(id: u64, nome: impl Into<String>, email: impl Into<String>) -> Self {
        Self {
            id,
            nome: nome.into(),
            email: email.into(),
        }
    }

    pub fn validar(&self) -> anyhow::Result<()> {
        if self.nome.is_empty() {
            anyhow::bail!("Nome não pode ser vazio");
        }
        if !self.email.contains('@') {
            anyhow::bail!("Email inválido: {}", self.email);
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn usuario_valido() {
        let u = Usuario::novo(1, "Maria", "maria@email.com");
        assert!(u.validar().is_ok());
    }

    #[test]
    fn email_invalido() {
        let u = Usuario::novo(1, "Maria", "invalido");
        assert!(u.validar().is_err());
    }
}
```

### 3. Crate CLI

```bash
cargo init cli
```

Edite `cli/Cargo.toml`:

```toml
[package]
name = "meu-projeto-cli"
version.workspace = true
edition.workspace = true

[dependencies]
meu-projeto-core = { path = "../core" }
clap = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }
```

Crie `cli/src/main.rs`:

```rust
use clap::Parser;
use meu_projeto_core::Usuario;

#[derive(Parser)]
#[command(name = "meu-projeto")]
#[command(about = "Gerenciador de usuários via CLI")]
enum Cli {
    /// Cria um novo usuário
    Criar {
        #[arg(short, long)]
        nome: String,
        #[arg(short, long)]
        email: String,
    },
    /// Lista usuários em formato JSON
    Listar,
}

fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();

    match cli {
        Cli::Criar { nome, email } => {
            let usuario = Usuario::novo(1, &nome, &email);
            usuario.validar()?;
            let json = serde_json::to_string_pretty(&usuario)?;
            println!("Usuário criado:\n{}", json);
        }
        Cli::Listar => {
            let usuarios = vec![
                Usuario::novo(1, "Ana", "ana@rust.br"),
                Usuario::novo(2, "Carlos", "carlos@rust.br"),
            ];
            let json = serde_json::to_string_pretty(&usuarios)?;
            println!("{}", json);
        }
    }

    Ok(())
}
```

### 4. Crate Servidor

```bash
cargo init servidor
```

Edite `servidor/Cargo.toml`:

```toml
[package]
name = "meu-projeto-servidor"
version.workspace = true
edition.workspace = true

[dependencies]
meu-projeto-core = { path = "../core" }
axum = { workspace = true }
tokio = { workspace = true }
serde_json = { workspace = true }
```

Crie `servidor/src/main.rs`:

```rust
use axum::{routing::get, Json, Router};
use meu_projeto_core::Usuario;

async fn listar_usuarios() -> Json<Vec<Usuario>> {
    let usuarios = vec![
        Usuario::novo(1, "Ana", "ana@rust.br"),
        Usuario::novo(2, "Carlos", "carlos@rust.br"),
    ];
    Json(usuarios)
}

async fn saude() -> &'static str {
    "OK"
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/usuarios", get(listar_usuarios))
        .route("/saude", get(saude));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    println!("Servidor rodando em http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}
```

---

## Comandos Essenciais do Workspace

Com o workspace configurado, o [Cargo](/ecossistema/cargo/) oferece comandos poderosos:

```bash
# Compilar todo o workspace
cargo build --workspace

# Rodar testes de todos os crates
cargo test --workspace

# Rodar apenas a CLI
cargo run -p meu-projeto-cli -- criar --nome "Ana" --email "ana@email.com"

# Rodar o servidor
cargo run -p meu-projeto-servidor

# Verificar código sem compilar
cargo check --workspace

# Rodar o Clippy em tudo
cargo clippy --workspace -- -D warnings

# Formatar todo o código
cargo fmt --all
```

Para mais detalhes sobre ferramentas de qualidade, confira nosso artigo sobre [Clippy](/ecossistema/clippy/) e [Rustfmt](/ecossistema/rustfmt/).

---

## Boas Práticas para Workspaces

### 1. Use workspace.dependencies Sempre

Centralizar dependências no `Cargo.toml` raiz evita versões divergentes:

```toml
[workspace.dependencies]
# Defina aqui, use com { workspace = true } nos crates
tracing = "0.1"
tracing-subscriber = "0.3"
```

Confira nosso guia sobre [gerenciamento de dependências](/artigos/gerenciamento-dependencias/) para estratégias avançadas.

### 2. Separe Lógica de Negócio de I/O

O crate core deve ser puro — sem dependências de runtime como Tokio. Isso permite reusar a lógica em CLIs, servidores, [WebAssembly](/artigos/rust-para-webassembly/) e [testes](/blog/testes-rust-estrategias-boas-praticas-2026/) sem carregar dependências desnecessárias.

### 3. Use Features para Funcionalidade Opcional

```toml
# core/Cargo.toml
[features]
default = []
persistencia = ["sqlx"]

[dependencies]
sqlx = { version = "0.8", optional = true }
```

Veja nosso artigo sobre [compilação condicional com cfg e features](/blog/compilacao-condicional-rust-cfg-features/) para dominar esse recurso.

### 4. Configure CI/CD para Workspaces

No seu pipeline de [CI/CD](/artigos/ci-cd-rust/), use `--workspace` em todos os comandos:

```yaml
steps:
  - name: Check
    run: cargo check --workspace --all-targets
  - name: Test
    run: cargo test --workspace
  - name: Clippy
    run: cargo clippy --workspace -- -D warnings
  - name: Format
    run: cargo fmt --all -- --check
```

### 5. Evite Dependências Cíclicas

Crates dentro de um workspace **não podem** depender um do outro de forma circular. Planeje a hierarquia:

```
core (sem deps internas)
  ↑
cli (depende de core)
servidor (depende de core)
```

Se dois crates precisam de funcionalidade compartilhada, extraia para um terceiro crate `common` ou `shared`.

---

## Workspace com Padrão Default-Members

Para projetos grandes, você pode definir quais crates são compilados por padrão:

```toml
[workspace]
members = ["core", "cli", "servidor", "benchmarks", "ferramentas/*"]
default-members = ["core", "cli", "servidor"]
```

Assim, `cargo build` compila apenas os membros padrão, e `cargo build --workspace` compila tudo incluindo benchmarks e ferramentas auxiliares.

O padrão glob `ferramentas/*` é suportado e automaticamente inclui todos os crates dentro desse diretório.

---

## Quando NÃO Usar Workspaces

Nem todo projeto precisa de workspace. Considere alternativas quando:

- **Crates com ciclos de release independentes**: se cada crate é publicado separadamente no crates.io com versões diferentes, um workspace pode complicar
- **Projeto com menos de 2 crates**: overhead desnecessário
- **Equipes diferentes mantendo crates diferentes**: um monorepo pode gerar conflitos

Para esses casos, dependências via crates.io com versionamento semântico podem ser mais adequadas.

---

## Conclusão

Cargo workspaces são a ferramenta padrão para organizar projetos Rust que cresceram além de um único crate. Com dependências centralizadas, builds compartilhados e comandos unificados, eles reduzem drasticamente o atrito de manter código grande.

Se você está começando um novo projeto, considere já estruturá-lo como workspace desde o início — é muito mais fácil do que migrar depois.

## Leia Também

- [Cargo: O Gerenciador de Pacotes do Rust](/ecossistema/cargo/) — fundamentos do Cargo
- [CI/CD com Rust](/artigos/ci-cd-rust/) — pipelines para projetos Rust
- [Compilação Condicional com cfg e Features](/blog/compilacao-condicional-rust-cfg-features/) — features no Cargo
- [Rust 1.86 e 1.87: Trait Upcasting e Novidades](/blog/rust-1-86-1-87-trait-upcasting-novidades-2026/) — novidades recentes
- [Ecossistema Rust em 2026](/blog/ecossistema-rust-2026/) — visão geral do ecossistema
- [Testes em Rust: Estratégias e Boas Práticas](/blog/testes-rust-estrategias-boas-praticas-2026/) — testando seu workspace

Se você trabalha com outras linguagens que também usam monorepos, compare como <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go</a> organiza módulos e workspaces, ou como <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python</a> lida com monorepos usando ferramentas como Poetry. Para projetos de backend, <a href="https://kotlin.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'kotlin.dev.br' })">Kotlin</a> com Gradle e <a href="https://ziglang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'ziglang.com.br' })">Zig</a> com seu build system também oferecem abordagens interessantes para projetos grandes.
