Introdução: Por que um monorepositório em Rust?
Quando você começa a aprender Rust, a primeira coisa que encontra é um projeto único, criado com cargo new. Mas o que fazer quando seu projeto cresce: surgem bibliotecas compartilhadas, vários arquivos executáveis (por exemplo, servidor e cliente) ou você deseja extrair código repetitivo para um pacote separado?
É aí que entra o monorepositório (monorepo) — uma abordagem onde o código de vários projetos inter-relacionados (bibliotecas e executáveis) é armazenado em um único repositório. O Rust fornece uma solução integrada para isso: Cargo Workspaces (espaços de trabalho).
Neste artigo, vamos explorar como organizar um monorepositório em Rust do zero: desde a estrutura de pastas até exemplos práticos. Você aprenderá como dividir corretamente o código em módulos, conectar dependências locais e gerenciar a compilação.
1. O que é um Cargo Workspace e como criá-lo?
Um workspace (espaço de trabalho) é um conjunto de crates que compartilham o mesmo arquivo Cargo.lock e o diretório target comum. Isso evita conflitos de versão de dependências e acelera a compilação.
Criando a estrutura do monorepositório
Vamos criar um monorepositório simples com dois crates: uma biblioteca utils e um arquivo executável app.
mkdir my_monorepocd my_monorepocargo new utils --libcargo new appAgora, crie o arquivo raiz Cargo.toml que declarará o workspace:
[workspace]members = [ "utils", "app",]Importante: Certifique-se de que nos arquivos utils/Cargo.toml e app/Cargo.toml não haja uma seção [package] com o campo workspace — ela não é necessária. O Cargo entenderá automaticamente que fazem parte do workspace.
Estrutura do projeto
Após a criação, você terá a seguinte estrutura:
my_monorepo/├── Cargo.toml # workspace raiz├── utils/│ ├── Cargo.toml│ └── src/│ └── lib.rs└── app/ ├── Cargo.toml └── src/ └── main.rs2. Organização do código: bibliotecas e módulos
Agora, vamos adicionar código à biblioteca utils. Suponha que queremos criar funções comuns para trabalhar com números e strings.
Código da biblioteca (utils/src/lib.rs)
pub mod math;pub mod strings;Crie os arquivos dos módulos:
// utils/src/math.rspub fn add(a: i32, b: i32) -> i32 { a + b}
pub fn multiply(a: i32, b: i32) -> i32 { a * b}// utils/src/strings.rspub fn greet(name: &str) -> String { format!("Olá, {}!", name)}
pub fn to_uppercase(text: &str) -> String { text.to_uppercase()}Conectando a biblioteca no arquivo executável
No arquivo app/Cargo.toml, especifique a dependência do crate local:
[package]name = "app"version = "0.1.0"edition = "2021"
[dependencies]utils = { path = "../utils" }Agora, em app/src/main.rs, podemos usar as funções de utils:
use utils::math;use utils::strings;
fn main() { let sum = math::add(5, 10); println!("5 + 10 = {}", sum);
let greeting = strings::greet("Mundo"); println!("{}", greeting);
let upper = strings::to_uppercase("rust rocks"); println!("{}", upper);}Execute o projeto a partir da raiz do monorepositório com o comando:
cargo run -p appVocê verá a saída:
5 + 10 = 15Olá, Mundo!RUST ROCKS3. Organização avançada: múltiplos arquivos executáveis
Frequentemente, em um monorepositório, é necessário ter vários