Monorepositório com Rust: organização de código e melhores práticas

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course
```html

Introdução



Monorepositório (monorepo) é uma abordagem para gerenciamento de código-fonte onde vários projetos (bibliotecas, aplicações, serviços) são armazenados em um único repositório. No ecossistema Rust, essa abordagem recebeu forte suporte através do mecanismo Cargo Workspace. Neste artigo, vamos analisar como organizar eficientemente um monorepositório em Rust, considerando estrutura, gerenciamento de dependências e melhores práticas.



Por que um monorepositório é útil para projetos Rust? Ele permite:

  • Reutilizar código entre múltiplos crates (bibliotecas e binários).
  • Simplificar versionamento e CI/CD.
  • Garantir atomicidade das alterações (um único commit pode atualizar vários crates).
  • Reduzir a complexidade do gerenciamento de dependências.


Fundamentos do Cargo Workspace



Cargo Workspace é o mecanismo nativo do Rust para gerenciar monorepositórios. Ele combina vários crates sob um único arquivo Cargo.toml de nível superior.



Criando um workspace



Para começar, vamos criar um diretório de projeto e um arquivo Cargo.toml na raiz:



# Cargo.toml (raiz)[workspace]members = [    "core",    "cli",    "web-api",    "utils"]

[workspace.dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1.37", features = ["full"] }


Aqui definimos quatro crates: core, cli, web-api e utils. O ponto chave é [workspace.dependencies]. Isso permite definir dependências compartilhadas para todos os crates do workspace. Cada crate pode referenciá-las sem duplicar versões.



Estrutura de diretórios



Uma estrutura típica de monorepositório em Rust se parece com isto:



my-monorepo/├── Cargo.toml          # workspace raiz├── Cargo.lock          # arquivo de lock compartilhado├── core/│   ├── Cargo.toml│   └── src/│       └── lib.rs├── cli/│   ├── Cargo.toml│   └── src/│       └── main.rs├── web-api/│   ├── Cargo.toml│   └── src/│       └── main.rs├── utils/│   ├── Cargo.toml│   └── src/│       └── lib.rs└── README.md


Cada subdiretório é um crate separado com seu próprio Cargo.toml. Os crates podem ser tanto bibliotecas (lib.rs) quanto aplicações executáveis (main.rs).



Gerenciamento de dependências e reutilização de código



Dependências internas



Crates dentro do workspace podem referenciar uns aos outros através de caminhos locais. Por exemplo, cli pode depender de core e utils:



# cli/Cargo.toml[package]name = "cli"version = "0.1.0"edition = "2021"

[dependencies]core = { path = "../core" }utils = { path = "../utils" }serde = { workspace = true }tokio = { workspace = true }


Observe a sintaxe { workspace = true } — ela permite usar dependências compartilhadas declaradas no Cargo.toml raiz. Isso garante que todos os crates usem a mesma versão da biblioteca.



Exemplo: biblioteca compartilhada core



Vamos criar uma biblioteca simples core que conterá a lógica de negócio:



// core/src/lib.rsuse serde::{Serialize, Deserialize};

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

pub fn create_user(name: String, email: String) -> User { User { id: rand::random(), name, email, }}

pub fn greet(user: &User) -> String { format!("Hello, {}!", user.name)}


Agora o crate cli pode usar esta biblioteca:



// cli/src/main.rsuse core::{create_user, greet};

fn main() { let user = create ```

Blogs

Book Recommendations