O sistema de módulos do Rust é poderoso, mas pode ser confuso para iniciantes — especialmente quem vem de linguagens onde a estrutura de arquivos determina automaticamente os módulos. Em Rust, a hierarquia de módulos é explícita: você precisa declarar cada módulo e suas relações. Neste artigo, vamos desmistificar mod, use, pub e a organização de projetos, desde um arquivo único até workspaces multi-crate.
Conceitos Fundamentais
Crate, Módulo e Path
Hierarquia do sistema de módulos:
──────────────────────────────────
Crate (unidade de compilação)
└── Módulo raiz (lib.rs ou main.rs)
├── Módulo A
│ ├── Submódulo A1
│ └── Submódulo A2
└── Módulo B
└── Submódulo B1
Paths (caminhos para itens):
crate::modulo_a::submodulo_a1::funcao()
self::funcao_local()
super::funcao_do_pai()
- Crate: a unidade de compilação — um binário ou uma biblioteca
- Módulo: um namespace que agrupa itens relacionados
- Path: o caminho para acessar um item (
crate::modulo::item)
Módulos Inline
A forma mais simples de criar módulos: declarar diretamente no arquivo:
mod matematica {
pub fn somar(a: i32, b: i32) -> i32 {
a + b
}
pub fn multiplicar(a: i32, b: i32) -> i32 {
a * b
}
// Função privada — não acessível fora do módulo
fn auxiliar() -> i32 {
42
}
// Submódulo
pub mod avancado {
pub fn potencia(base: f64, exp: u32) -> f64 {
(0..exp).fold(1.0, |acc, _| acc * base)
}
}
}
fn main() {
println!("{}", matematica::somar(2, 3));
println!("{}", matematica::multiplicar(4, 5));
println!("{}", matematica::avancado::potencia(2.0, 10));
// matematica::auxiliar(); // ERRO: função privada
}
Módulos em Arquivos Separados
Para projetos maiores, cada módulo vai em seu próprio arquivo. Existem duas convenções:
Convenção Moderna (Rust 2018+): Arquivo por Módulo
meu_projeto/
├── Cargo.toml
└── src/
├── main.rs
├── config.rs ← módulo config
├── database.rs ← módulo database
└── handlers/
├── mod.rs ← módulo handlers (raiz)
├── usuarios.rs ← submódulo handlers::usuarios
└── produtos.rs ← submódulo handlers::produtos
Ou a alternativa sem mod.rs (recomendada para novos projetos):
meu_projeto/
├── Cargo.toml
└── src/
├── main.rs
├── config.rs
├── database.rs
├── handlers.rs ← declara submódulos
└── handlers/
├── usuarios.rs
└── produtos.rs
Implementação
src/main.rs:
mod config;
mod database;
mod handlers;
fn main() {
let cfg = config::carregar();
println!("Porta: {}", cfg.porta);
database::inicializar(&cfg);
handlers::usuarios::listar();
handlers::produtos::listar();
}
src/config.rs:
pub struct Config {
pub porta: u16,
pub host: String,
pub database_url: String,
}
pub fn carregar() -> Config {
Config {
porta: 8080,
host: "localhost".to_string(),
database_url: "postgres://localhost/app".to_string(),
}
}
src/database.rs:
use crate::config::Config;
pub fn inicializar(config: &Config) {
println!("Conectando ao banco: {}", config.database_url);
}
pub fn executar_query(sql: &str) -> Vec<String> {
println!("Executando: {}", sql);
vec!["resultado1".to_string(), "resultado2".to_string()]
}
src/handlers.rs (declara submódulos):
pub mod usuarios;
pub mod produtos;
src/handlers/usuarios.rs:
use crate::database;
pub fn listar() {
let resultados = database::executar_query("SELECT * FROM usuarios");
println!("Usuários: {:?}", resultados);
}
pub fn buscar_por_id(id: u32) -> Option<String> {
let resultados = database::executar_query(&format!("SELECT * FROM usuarios WHERE id = {}", id));
resultados.into_iter().next()
}
src/handlers/produtos.rs:
use crate::database;
pub fn listar() {
let resultados = database::executar_query("SELECT * FROM produtos");
println!("Produtos: {:?}", resultados);
}
Visibilidade: pub e Suas Variantes
O Rust tem um sistema de visibilidade granular:
mod externo {
pub mod interno {
// pub — visível para todos
pub fn publica() {}
// (sem pub) — visível apenas dentro deste módulo
fn privada() {}
// pub(crate) — visível em qualquer lugar DENTRO do crate
pub(crate) fn interna_crate() {}
// pub(super) — visível no módulo pai
pub(super) fn interna_pai() {}
// pub(in crate::externo) — visível no caminho especificado
pub(in crate::externo) fn interna_externo() {}
}
pub fn usar_interno() {
interno::publica(); // OK
// interno::privada(); // ERRO: privada
interno::interna_crate(); // OK: mesmo crate
interno::interna_pai(); // OK: somos o super
interno::interna_externo(); // OK: estamos em crate::externo
}
}
fn main() {
externo::interno::publica(); // OK
// externo::interno::privada(); // ERRO
externo::interno::interna_crate(); // OK: mesmo crate
// externo::interno::interna_pai(); // ERRO: não somos o super
}
Structs com Campos Privados
mod banco {
pub struct ContaBancaria {
titular: String, // privado!
pub numero: u64,
saldo: f64, // privado!
}
impl ContaBancaria {
// Construtor público necessário porque campos são privados
pub fn nova(titular: &str, numero: u64) -> Self {
ContaBancaria {
titular: titular.to_string(),
numero,
saldo: 0.0,
}
}
pub fn titular(&self) -> &str {
&self.titular
}
pub fn saldo(&self) -> f64 {
self.saldo
}
pub fn depositar(&mut self, valor: f64) {
if valor > 0.0 {
self.saldo += valor;
}
}
}
}
fn main() {
let mut conta = banco::ContaBancaria::nova("Ana", 12345);
conta.depositar(1000.0);
println!("Conta {}: titular={}, saldo=R${:.2}",
conta.numero, // OK: campo público
conta.titular(), // OK: método público
conta.saldo(), // OK: método público
);
// conta.saldo = -100.0; // ERRO: campo privado
}
use — Importando Itens
Básico
// Caminho completo — verboso
fn exemplo1() {
let mapa = std::collections::HashMap::<String, i32>::new();
println!("{:?}", mapa);
}
// Com use — limpo
use std::collections::HashMap;
fn exemplo2() {
let mapa = HashMap::<String, i32>::new();
println!("{:?}", mapa);
}
fn main() {
exemplo1();
exemplo2();
}
Importações Múltiplas e Aninhadas
// Importar múltiplos itens do mesmo módulo
use std::collections::{HashMap, HashSet, BTreeMap};
// Importar com renomeação (alias)
use std::collections::HashMap as Mapa;
use std::io::Result as IoResult;
// Importar tudo de um módulo (use com cautela)
// use std::collections::*;
// Importações aninhadas
use std::io::{self, Read, Write, BufRead};
// Equivalente a:
// use std::io;
// use std::io::Read;
// use std::io::Write;
// use std::io::BufRead;
fn main() {
let _: HashMap<String, i32> = HashMap::new();
let _: HashSet<i32> = HashSet::new();
let _: BTreeMap<String, i32> = BTreeMap::new();
let _: Mapa<String, i32> = Mapa::new();
}
Convenções de Importação
// CONVENÇÃO: importar o módulo pai para funções
use std::fmt;
// Uso: fmt::Display, fmt::Formatter
// CONVENÇÃO: importar o tipo diretamente para structs/enums/traits
use std::collections::HashMap;
// Uso: HashMap::new()
// CONVENÇÃO: re-exportar no nível adequado
mod modelos {
pub mod usuario {
pub struct Usuario {
pub nome: String,
}
}
}
// Re-exportar para simplificar o acesso
pub use modelos::usuario::Usuario;
fn main() {
let u = Usuario { nome: "Ana".into() };
println!("{}", u.nome);
}
Resolução de Paths
crate, self, super
mod nivel1 {
pub fn funcao_nivel1() -> &'static str {
"nível 1"
}
pub mod nivel2 {
pub fn funcao_nivel2() -> &'static str {
"nível 2"
}
// super:: refere-se ao módulo pai (nivel1)
pub fn chamar_pai() -> &'static str {
super::funcao_nivel1()
}
pub mod nivel3 {
// crate:: começa da raiz do crate
pub fn chamar_raiz() {
println!("{}", crate::nivel1::funcao_nivel1());
}
// super:: vai um nível acima (nivel2)
pub fn chamar_nivel2() {
println!("{}", super::funcao_nivel2());
}
// super::super:: vai dois níveis acima (nivel1)
pub fn chamar_nivel1() {
println!("{}", super::super::funcao_nivel1());
}
// self:: refere-se ao módulo atual
pub fn local() -> &'static str {
"nível 3"
}
pub fn chamar_local() {
println!("{}", self::local());
}
}
}
}
fn main() {
nivel1::nivel2::nivel3::chamar_raiz();
nivel1::nivel2::nivel3::chamar_nivel2();
nivel1::nivel2::nivel3::chamar_nivel1();
nivel1::nivel2::nivel3::chamar_local();
}
Re-exports: Organizando a API Pública
Re-exports (pub use) permitem criar uma API pública limpa independente da estrutura interna:
// Estrutura interna complexa
mod internos {
pub mod v2 {
pub mod processamento {
pub struct Processador {
pub nome: String,
}
impl Processador {
pub fn novo(nome: &str) -> Self {
Processador { nome: nome.to_string() }
}
pub fn executar(&self) {
println!("Processador '{}' executando", self.nome);
}
}
}
pub mod erros {
#[derive(Debug)]
pub enum AppError {
NaoEncontrado,
Permissao,
}
}
}
}
// Re-exports criam uma API pública limpa
pub use internos::v2::processamento::Processador;
pub use internos::v2::erros::AppError;
fn main() {
// Usuários usam o caminho curto
let p = Processador::novo("principal");
p.executar();
let _erro = AppError::NaoEncontrado;
// Em vez de:
// let p = internos::v2::processamento::Processador::novo("principal");
}
Padrão Prelude
Muitas bibliotecas usam um módulo prelude para re-exportar os itens mais usados:
mod tipos {
pub struct Config {
pub valor: String,
}
pub struct Conexao {
pub url: String,
}
pub trait Executavel {
fn executar(&self);
}
}
mod erros {
#[derive(Debug)]
pub enum Erro {
Conexao,
Timeout,
}
pub type Resultado<T> = Result<T, Erro>;
}
// Prelude: re-exporta tudo que o usuário geralmente precisa
pub mod prelude {
pub use crate::tipos::{Config, Conexao, Executavel};
pub use crate::erros::{Erro, Resultado};
}
// Uso:
use crate::prelude::*;
impl Executavel for Config {
fn executar(&self) {
println!("Executando config: {}", self.valor);
}
}
fn main() {
let config = Config { valor: "teste".into() };
config.executar();
let resultado: Resultado<i32> = Ok(42);
println!("{:?}", resultado);
}
Workspaces: Projetos Multi-Crate
Para projetos grandes, o Cargo suporta workspaces — múltiplos crates que compartilham um Cargo.lock:
Estrutura
meu_workspace/
├── Cargo.toml ← workspace root
├── crates/
│ ├── core/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── lib.rs
│ ├── api/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── main.rs
│ └── cli/
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
└── shared/
├── Cargo.toml
└── src/
└── lib.rs
Configuração
Cargo.toml (raiz do workspace):
[workspace]
members = [
"crates/core",
"crates/api",
"crates/cli",
"shared",
]
# Dependências compartilhadas por todos os membros
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
crates/core/Cargo.toml:
[package]
name = "meu-core"
version = "0.1.0"
edition = "2021"
[dependencies]
serde.workspace = true # usa a versão do workspace
crates/api/Cargo.toml:
[package]
name = "meu-api"
version = "0.1.0"
edition = "2021"
[dependencies]
meu-core = { path = "../core" } # dependência local
tokio.workspace = true
serde.workspace = true
Comandos Úteis
# Compilar todo o workspace
cargo build
# Compilar apenas um crate
cargo build -p meu-api
# Rodar testes de todo o workspace
cargo test
# Rodar testes de um crate específico
cargo test -p meu-core
# Rodar o binário de um crate
cargo run -p meu-api
cargo run -p meu-cli
Padrão de Organização Recomendado
Para projetos de médio porte, esta é uma organização robusta:
meu_projeto/
├── Cargo.toml
├── src/
│ ├── main.rs ← ponto de entrada, setup e coordenação
│ ├── lib.rs ← API pública da biblioteca (opcional)
│ ├── config.rs ← carregamento de configuração
│ ├── error.rs ← tipos de erro customizados
│ ├── models/
│ │ ├── mod.rs
│ │ ├── usuario.rs
│ │ └── produto.rs
│ ├── services/
│ │ ├── mod.rs
│ │ ├── auth.rs
│ │ └── pedidos.rs
│ ├── handlers/ ← (para web: endpoints HTTP)
│ │ ├── mod.rs
│ │ ├── api.rs
│ │ └── health.rs
│ └── db/
│ ├── mod.rs
│ ├── pool.rs
│ └── queries.rs
├── tests/ ← testes de integração
│ └── api_test.rs
└── benches/ ← benchmarks
└── performance.rs
src/main.rs:
mod config;
mod error;
mod models;
mod services;
mod handlers;
mod db;
// Re-exports para uso fácil
use error::AppError;
fn main() -> Result<(), AppError> {
let cfg = config::carregar()?;
println!("Servidor iniciando na porta {}", cfg.porta);
Ok(())
}
src/error.rs:
use std::fmt;
#[derive(Debug)]
pub enum AppError {
Config(String),
Database(String),
NotFound(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Config(msg) => write!(f, "Erro de configuração: {}", msg),
AppError::Database(msg) => write!(f, "Erro de banco: {}", msg),
AppError::NotFound(msg) => write!(f, "Não encontrado: {}", msg),
}
}
}
impl std::error::Error for AppError {}
src/models/mod.rs:
pub mod usuario;
pub mod produto;
// Re-exports dos tipos principais
pub use usuario::Usuario;
pub use produto::Produto;
Erros Comuns
1. Unresolved Import
// ERRO: mod 'utils' não foi declarado
// use crate::utils::helper;
// SOLUÇÃO: declare o módulo em main.rs ou lib.rs
// mod utils; // ← adicionar isso
Veja Cargo: Unresolved Import para mais detalhes.
2. Esquecer pub em itens exportados
mod meu_modulo {
// ERRO: struct é pública mas campo é privado
// pub struct Config {
// porta: u16, // privado!
// }
// CORRETO
pub struct Config {
pub porta: u16,
}
}
fn main() {
let c = meu_modulo::Config { porta: 8080 };
println!("{}", c.porta);
}
3. Confusão entre mod.rs e arquivo com nome do módulo
// Ambos representam o módulo 'handlers':
// Opção 1: handlers/mod.rs
src/
├── handlers/
│ └── mod.rs
// Opção 2: handlers.rs (+ pasta handlers/ para submódulos)
src/
├── handlers.rs
├── handlers/
│ └── sub.rs
// NÃO PODE ter ambos — é um erro de compilação!
4. Caminho circular
// ERRO: módulos não podem ter dependências circulares
// mod a { use crate::b::funcao_b; }
// mod b { use crate::a::funcao_a; }
// SOLUÇÃO: extraia a parte compartilhada para um módulo separado
mod compartilhado {
pub fn funcao_compartilhada() {}
}
mod a {
use crate::compartilhado;
pub fn funcao_a() {
compartilhado::funcao_compartilhada();
}
}
mod b {
use crate::compartilhado;
pub fn funcao_b() {
compartilhado::funcao_compartilhada();
}
}
fn main() {
a::funcao_a();
b::funcao_b();
}
Veja Também
- Cargo: Unresolved Import — como resolver erros de importação
- Instalação e Configuração do Cargo — gerenciamento de dependências
- Traits e Generics em Rust — organização de traits em módulos
- Macros em Rust — macros e sistema de módulos
- Unsafe Rust — módulos para encapsular código unsafe