Rust

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

8 min de leitura

Como Usar Workspaces no Cargo: Organizando Projetos Grandes em Rust em Produçã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 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

<h2>O que são Workspaces no Cargo?</h2>

<p>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.</p>

<p>A estrutura de um workspace é definida em um arquivo <code>Cargo.toml</code> raiz que lista todos os membros. Cada membro é uma crate independente com seu próprio <code>Cargo.toml</code>, mas todos compartilham o mesmo <code>target/</code> para artefatos compilados. Isso economiza espaço em disco e tempo de build significativamente em projetos grandes.</p>

<h2>Estruturando seu Primeiro Workspace</h2>

<h3>Criando a Estrutura Base</h3>

<p>Comece criando um diretório para seu workspace e o arquivo <code>Cargo.toml</code> raiz:</p>

<pre><code class="language-toml">[workspace]

members = [

&quot;core&quot;,

&quot;cli&quot;,

&quot;web_server&quot;,

]

resolver = &quot;2&quot;

[workspace.package]

version = &quot;0.1.0&quot;

edition = &quot;2021&quot;

authors = [&quot;seu_nome&quot;]</code></pre>

<p>Agora crie os diretórios das crates:</p>

<pre><code class="language-bash">mkdir -p core cli web_server

cargo init --lib core

cargo init --bin cli

cargo init --bin web_server</code></pre>

<h3>Estrutura de Diretórios Resultante</h3>

<pre><code>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</code></pre>

<h2>Gerenciando Dependências em Workspaces</h2>

<h3>Dependências Compartilhadas</h3>

<p>O grande benefício dos workspaces é evitar recompilação de dependências comuns. Configure-as no <code>Cargo.toml</code> raiz:</p>

<pre><code class="language-toml">[workspace]

members = [&quot;core&quot;, &quot;cli&quot;, &quot;web_server&quot;]

[workspace.dependencies]

serde = { version = &quot;1.0&quot;, features = [&quot;derive&quot;] }

tokio = { version = &quot;1.35&quot;, features = [&quot;full&quot;] }

log = &quot;0.4&quot;</code></pre>

<p>Depois, em cada crate, referencie essas dependências sem repetir versões:</p>

<p><strong>core/Cargo.toml:</strong></p>

<pre><code class="language-toml">[package]

name = &quot;core&quot;

version.workspace = true

edition.workspace = true

[dependencies]

serde = { workspace = true }

log = { workspace = true }</code></pre>

<p><strong>cli/Cargo.toml:</strong></p>

<pre><code class="language-toml">[package]

name = &quot;cli&quot;

version.workspace = true

edition.workspace = true

[dependencies]

tokio = { workspace = true }

core = { path = &quot;../core&quot; }

log = { workspace = true }</code></pre>

<h3>Dependências Internas</h3>

<p>Uma crate pode depender de outra no mesmo workspace usando <code>path</code>. A grande vantagem: mudanças em <code>core</code> são imediatamente visíveis em <code>cli</code> sem publicar em crates.io. Exemplo prático no <code>cli/src/main.rs</code>:</p>

<pre><code class="language-rust">use core::models::User;

use log::info;

#[tokio::main]

async fn main() {

env_logger::init();

let user = User::new(&quot;Alice&quot;, 30);

info!(&quot;Usuário criado: {:?}&quot;, user);

}</code></pre>

<p>E em <code>core/src/lib.rs</code>:</p>

<pre><code class="language-rust">pub mod models;

pub struct User {

name: String,

age: u32,

}

impl User {

pub fn new(name: &amp;str, age: u32) -&gt; Self {

User {

name: name.to_string(),

age,

}

}

}</code></pre>

<h2>Compilando e Testando Workspaces</h2>

<h3>Compilação e Execução</h3>

<p>O Cargo entende automaticamente a estrutura do workspace. Para compilar tudo:</p>

<pre><code class="language-bash">cargo build</code></pre>

<p>Para compilar uma crate específica:</p>

<pre><code class="language-bash">cargo build -p cli

cargo build -p web_server</code></pre>

<p>Para executar um binário específico:</p>

<pre><code class="language-bash">cargo run -p cli</code></pre>

<h3>Testes em Larga Escala</h3>

<p>Teste todos os pacotes simultaneamente:</p>

<pre><code class="language-bash">cargo test</code></pre>

<p>Ou teste uma crate específica:</p>

<pre><code class="language-bash">cargo test -p core</code></pre>

<p>Com resultado detalhado:</p>

<pre><code class="language-bash">cargo test -- --test-threads=1 --nocapture</code></pre>

<p>Exemplo de teste em <code>core/src/lib.rs</code>:</p>

<pre><code class="language-rust">#[cfg(test)]

mod tests {

use super::*;

#[test]

fn test_user_creation() {

let user = User::new(&quot;Bob&quot;, 25);

assert_eq!(user.name, &quot;Bob&quot;);

assert_eq!(user.age, 25);

}

}</code></pre>

<h2>Boas Práticas e Otimizações</h2>

<p>Um workspace bem organizado segue alguns princípios. Sempre mantenha dependências no escopo <code>[workspace.dependencies]</code> para evitar version skew. Use features condicionais para compilações mais eficientes — por exemplo, a crate <code>core</code> pode expor features que apenas <code>web_server</code> necessita. Documente a responsabilidade de cada crate em um <code>README.md</code> na raiz do workspace explicando qual module lidar com qual aspecto da aplicação.</p>

<p>Para projetos muito grandes, considere separar workspaces por domínio: um para infraestrutura, outro para features de negócio. Use <code>cargo workspaces</code> (ferramenta auxiliar) para gerenciar versionamento e publicação coordenada. Finalmente, configure <code>Cargo.toml</code> raiz com <code>default-members</code> caso nem todas as crates devam ser compiladas por padrão:</p>

<pre><code class="language-toml">[workspace]

members = [&quot;core&quot;, &quot;cli&quot;, &quot;web_server&quot;, &quot;internal_tools&quot;]

default-members = [&quot;core&quot;, &quot;cli&quot;]</code></pre>

<h2>Conclusão</h2>

<p>Workspaces no Cargo são essenciais para organizar projetos Rust complexos sem sacrificar performance. Os três aprendizados principais são: <strong>(1)</strong> compartilhar dependências e configuração via <code>[workspace.dependencies]</code> reduz tempo de compilação drasticamente, <strong>(2)</strong> dependências internas com <code>path</code> permitem desenvolvimento ágil sem publicar em crates.io, e <strong>(3)</strong> a estrutura unificada de <code>target/</code> 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.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://doc.rust-lang.org/cargo/reference/workspaces.html" target="_blank" rel="noopener noreferrer">The Cargo Book - Workspaces</a></li>

<li><a href="https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html" target="_blank" rel="noopener noreferrer">Rust Book - Packages and Crates</a></li>

<li><a href="https://doc.rust-lang.org/cargo/reference/workspaces.html#the-package-table" target="_blank" rel="noopener noreferrer">Cargo Workspace Dependencies</a></li>

<li><a href="https://www.rust-lang.org/what/wg-cargo/" target="_blank" rel="noopener noreferrer">Managing Large Projects in Rust</a></li>

<li><a href="https://tokio.rs/" target="_blank" rel="noopener noreferrer">Tokio Documentation - Using Workspaces</a></li>

</ul>

Comentários

Mais em Rust

Como Usar Enums em Rust: Definição, Variantes e Dados Associados em Produção
Como Usar Enums em Rust: Definição, Variantes e Dados Associados em Produção

Introdução: O Que São Enums em Rust? Enums (enumerações) são um dos pilares d...

O que Todo Dev Deve Saber sobre Async e Await em Rust: Introdução à Programação Assíncrona
O que Todo Dev Deve Saber sobre Async e Await em Rust: Introdução à Programação Assíncrona

Entendendo Async e Await em Rust A programação assíncrona permite que seu pro...

Traits em Rust: Definindo Comportamento Compartilhado: Do Básico ao Avançado
Traits em Rust: Definindo Comportamento Compartilhado: Do Básico ao Avançado

Introdução: O que são Traits? Traits em Rust são abstrações poderosas que per...