Módulos e o sistema de visibilidade

1. Introdução ao Sistema de Módulos

Em Rust, módulos são a principal ferramenta para organizar código, encapsular implementações e promover reutilização. Eles permitem agrupar funcionalidades relacionadas, controlar o que é exposto como API pública e criar hierarquias lógicas dentro de um crate.

A árvore de módulos reflete a estrutura do sistema de arquivos. Existem duas convenções principais:
- Estilo 2015: src/modulo/mod.rs
- Estilo 2018+: src/modulo.rs (recomendado)

Para declarar um módulo, usamos a palavra-chave mod:

// src/main.rs
mod utils; // procura por utils.rs ou utils/mod.rs

2. Definindo e Aninhando Módulos

Módulos podem ser definidos inline ou em arquivos separados:

// Módulo inline
mod calculos {
    fn somar(a: i32, b: i32) -> i32 {
        a + b
    }
}

// Módulo em arquivo separado (src/matematica.rs)
mod matematica;

// Submódulo (src/matematica/geometria.rs)
mod matematica {
    pub mod geometria;
}

Por padrão, tudo dentro de um módulo é privado. Isso significa que itens definidos em um módulo não são acessíveis externamente a menos que explicitamente tornados públicos.

3. Controle de Visibilidade com pub

A palavra-chave pub torna itens públicos:

pub mod utilidades {
    pub fn saudacao() -> String {
        "Olá!".to_string()
    }

    fn interna() {
        // Função privada
    }

    pub struct Config {
        pub nome: String,    // Campo público
        versao: u32,         // Campo privado
    }

    pub enum Status {
        Ativo,   // Variantes públicas (se o enum é pub)
        Inativo,
    }
}

Para structs, cada campo precisa ser explicitamente marcado como pub para ser acessível externamente. Já para enums, se o enum é público, todas as variantes são públicas automaticamente.

4. Visibilidade Restrita com pub(crate) e pub(super)

Rust oferece níveis de visibilidade mais granulares:

pub(crate) fn funcao_interna_ao_crate() {
    // Visível apenas dentro do crate atual
}

pub(super) fn funcao_do_modulo_pai() {
    // Visível apenas no módulo pai
}

mod nivel1 {
    pub(super) fn ajuda() {} // Visível no módulo pai

    pub mod nivel2 {
        pub fn publica() {}
        pub(crate) fn interna() {} // Visível em todo o crate
    }
}

Caminhos relativos e absolutos ajudam a navegar na hierarquia:

use crate::modulo::Item;  // Caminho absoluto
use super::Item;          // Caminho relativo ao pai
use self::Item;           // Caminho relativo ao módulo atual

5. A Palavra-chave use e Reexportação

use traz itens para o escopo atual:

// src/main.rs
use crate::utilidades::saudacao;
use std::collections::HashMap;

// Reexportação com pub use
pub mod internals {
    pub fn helper() -> u32 { 42 }
}

pub use internals::helper; // Reexporta helper como parte da API pública

// Apelidos com as
use std::collections::HashMap as Map;
use crate::utilidades::saudacao as ola;

Reexportação é poderosa para criar APIs limpas, escondendo a estrutura interna do módulo.

6. Módulos e o Sistema de Arquivos

Estrutura típica de um projeto:

meu_projeto/
├── src/
│   ├── main.rs
│   ├── lib.rs
│   ├── modulo.rs          # Módulo raiz
│   ├── modulo/
│   │   ├── mod.rs         # (estilo 2015)
│   │   └── submodulo.rs
│   └── utils/
│       ├── mod.rs
│       └── string_utils.rs

No estilo 2018+, preferimos src/modulo.rs em vez de src/modulo/mod.rs. Para projetos grandes, organize por funcionalidade:

// src/lib.rs
pub mod auth;
pub mod database;
pub mod api;

// src/auth.rs
pub mod login;
pub mod tokens;

7. Visibilidade de Tipos e Traits

Encapsulamento com structs públicas e campos privados:

pub struct BancoDeDados {
    conexao: String,      // Privado
    pub nome: String,     // Público
}

impl BancoDeDados {
    pub fn new(nome: &str) -> Self {
        BancoDeDados {
            conexao: format!("localhost/{}", nome),
            nome: nome.to_string(),
        }
    }

    pub fn conectar(&self) {
        // Método público
    }
}

Traits podem ser públicos ou privados:

pub trait Serializavel {
    fn para_json(&self) -> String;
}

trait Interno {
    fn processar(&self);
}

Módulos pub mod com conteúdo seletivamente público:

pub mod api {
    pub fn endpoint() {}     // Público
    fn interno() {}          // Privado
    pub struct Resposta {    // Público, mas campos privados
        dados: Vec<u8>,
    }
}

8. Exemplo Prático: Projeto com Módulos e Visibilidade

Vamos criar uma biblioteca de processamento de texto:

// src/lib.rs
pub mod processamento;
pub mod formatacao;

pub use processamento::analisar;
pub use formatacao::formatar;

// src/processamento.rs
mod tokenizador;
mod analisador;

pub fn analisar(texto: &str) -> Result<String, String> {
    let tokens = tokenizador::tokenizar(texto)?;
    Ok(analisador::analisar(&tokens))
}

// src/processamento/tokenizador.rs
pub(crate) fn tokenizar(texto: &str) -> Result<Vec<String>, String> {
    Ok(texto.split_whitespace().map(String::from).collect())
}

// src/formatacao.rs
pub fn formatar(texto: &str) -> String {
    texto.trim().to_string()
}

Testando privacidade:

// Isso compila:
use minha_biblioteca::analisar;
let resultado = analisar("exemplo").unwrap();

// Isso NÃO compila (tokenizador é privado):
// use minha_biblioteca::processamento::tokenizador;

// Isso compila (pub use reexportou):
use minha_biblioteca::formatar;

Erros comuns:
- Tentar acessar item privado de outro módulo
- Esquecer pub em campos de struct
- Não usar pub use para reexportar itens aninhados

Conclusão

O sistema de módulos e visibilidade em Rust oferece controle granular sobre a organização e exposição do código. Com pub, pub(crate), pub(super) e reexportação via pub use, é possível criar APIs limpas e seguras, escondendo detalhes de implementação enquanto expõe apenas o necessário. A relação com o sistema de arquivos torna a navegação intuitiva, e as regras de privacidade padrão incentivam o encapsulamento desde o início.

Referências