teoria

Organizando o Código com Módulos e Caminhos

Aprenda sobre organizando o código com módulos e caminhos

25 min
Aula 1 de 4

Organizando o Código com Módulos e Caminhos

Olá, futuros mestres do Rust! 👋 Nesta aula, mergulharemos em um dos pilares da organização de código em Rust: Módulos e Caminhos. À medida que seus projetos crescem, manter o código limpo, legível e gerenciável se torna crucial. Rust nos oferece ferramentas poderosas para isso, e os módulos são a peça central.

1. Introdução: Por Que Organizar o Código? 🤔

Imagine um livro sem capítulos ou um armário sem gavetas. Seria um caos, certo? O mesmo acontece com o código. Em projetos pequenos, um único arquivo pode ser suficiente. Mas quando seu projeto cresce para centenas ou milhares de linhas, você precisará de uma forma de:

  • Dividir responsabilidades: Cada parte do código faz uma coisa bem feita.
  • Reutilizar código: Evitar repetição e promover a DRY (Don't Repeat Yourself).
  • Controlar a visibilidade: Decidir o que é público (acessível de fora) e o que é privado (detalhes de implementação internos).
  • Gerenciar escopo: Evitar conflitos de nomes e manter o código mais fácil de raciocinar.

É aqui que os módulos entram em cena! Eles nos permitem organizar o código em grupos lógicos, como capítulos de um livro, e os caminhos nos dão a capacidade de navegar e referenciar itens dentro desses grupos.


2. Explicação Detalhada com Exemplos 🚀

A. O que são Módulos? O Keyword mod

Em Rust, um módulo é uma coleção de código nomeada. Ele pode conter funções, structs, enums, traits, constantes e até outros módulos! Módulos formam uma árvore hierárquica, começando pelo "crate root" (geralmente src/main.rs para binários ou src/lib.rs para bibliotecas).

Você define um módulo usando a palavra-chave mod, seguida por um nome e um bloco de código {}.

// src/main.rs ou src/lib.rs
 
fn main() {
    println!("Bem-vindo ao nosso restaurante!");
    front_of_house::hosting::add_to_waitlist(); // Acessando uma função dentro de um módulo
    front_of_house::serving::take_order();
}
 
mod front_of_house { // Define um módulo chamado `front_of_house`
    mod hosting { // Define um submódulo `hosting` dentro de `front_of_house`
        fn add_to_waitlist() { // Esta função é privada por padrão
            println!("Adicionado à lista de espera.");
        }
    }
 
    mod serving { // Define outro submódulo `serving`
        fn take_order() { // Esta função também é privada
            println!("Pedido anotado.");
        }
    }
}

Observação Importante: Por padrão, todos os itens dentro de um módulo (funções, structs, etc.) são privados. Isso significa que eles só podem ser acessados de dentro do próprio módulo ou de seus módulos irmãos. Para torná-los acessíveis de fora, precisamos usar a palavra-chave pub.

B. Controlando a Visibilidade com pub

Para permitir que funções, structs ou outros itens dentro de um módulo sejam usados por código externo, você precisa marcá-los como pub (públicos).

// src/main.rs ou src/lib.rs
 
fn main() {
    println!("Bem-vindo ao nosso restaurante!");
    front_of_house::hosting::add_to_waitlist(); // Agora isso funciona!
    front_of_house::serving::take_order();
}
 
mod front_of_house {
    pub mod hosting { // O módulo 'hosting' é público
        pub fn add_to_waitlist() { // A função 'add_to_waitlist' é pública
            println!("Adicionado à lista de espera.");
        }
    }
 
    pub mod serving { // O módulo 'serving' é público
        pub fn take_order() { // A função 'take_order' é pública
            println!("Pedido anotado.");
        }
    }
}

Regras de pub:

  • Se um módulo é pub, seus conteúdos ainda são privados por padrão. Você precisa marcar itens específicos dentro dele como pub se quiser que sejam acessíveis.
  • Se uma struct é pub, seus campos ainda são privados por padrão. Você precisa marcar campos específicos como pub para que sejam acessíveis.
  • Se um enum é pub, todas as suas variantes são automaticamente pub.

C. Caminhos para Referenciar Itens em Módulos

Para usar um item definido em um módulo, você precisa especificar o caminho para ele. Existem dois tipos de caminhos:

  1. Caminhos Absolutos: Começam do "crate root" (crate).
  2. Caminhos Relativos: Começam do módulo atual ou de um módulo pai (self ou super).

Caminhos Absolutos (crate::)

Começam com crate, que representa a raiz do seu crate (geralmente src/main.rs ou src/lib.rs).

// src/main.rs
 
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("Adicionado à lista de espera.");
        }
    }
}
 
pub fn eat_at_restaurant() {
    // Caminho absoluto para `add_to_waitlist`
    crate::front_of_house::hosting::add_to_waitlist();
}
 
fn main() {
    eat_at_restaurant();
}

Caminhos Relativos (self:: e super::)

  • self::: Começa do módulo atual. Raramente usado explicitamente, pois item_name é o mesmo que self::item_name.
  • super::: Começa do módulo pai do módulo atual. Útil para acessar itens que estão um nível acima na hierarquia.
// src/main.rs
 
fn deliver_order() {
    println!("Pedido entregue!");
}
 
mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        // Usando 'super' para acessar uma função no módulo pai (crate root)
        super::deliver_order();
    }
 
    fn cook_order() {
        println!("Cozinhando o pedido.");
    }
}
 
fn main() {
    back_of_house::fix_incorrect_order(); // Isso não compila, 'fix_incorrect_order' é privado
}

Para que o exemplo acima funcione, fix_incorrect_order e back_of_house precisariam ser pub.

// src/main.rs
 
fn deliver_order() {
    println!("Pedido entregue!");
}
 
pub mod back_of_house { // Módulo público
    pub fn fix_incorrect_order() { // Função pública
        cook_order();
        super::deliver_order(); // Acessando `deliver_order` no módulo pai
    }
 
    fn cook_order() {
        println!("Cozinhando o pedido.");
    }
}
 
fn main() {
    back_of_house::fix_incorrect_order();
}

D. Trazendo Caminhos para o Escopo com use

Caminhos longos podem ser tediosos e repetitivos. A palavra-chave use permite que você traga um caminho para o escopo atual, permitindo que você use o item como se ele estivesse definido localmente.

// src/main.rs
 
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("Adicionado à lista de espera.");
        }
    }
}
 
// Traz `hosting` para o escopo atual
use crate::front_of_house::hosting;
 
fn main() {
    // Agora podemos chamar `add_to_waitlist` diretamente através de `hosting`
    hosting::add_to_waitlist();
}

Regras e Boas Práticas com use:

  • Preferência por Caminhos Completos: A convenção em Rust é trazer o pai do item para o escopo, não o item em si. Por exemplo, use std::collections::HashMap; é preferível a use std::collections::HashMap::HashMap;. Isso ajuda a evitar conflitos de nomes e deixa claro de onde o item vem.

    use std::collections::HashMap; // Boa prática
     
    fn main() {
        let mut map = HashMap::new();
        map.insert(1, 2);
    }

    Exceção: Se você está usando dois itens com o mesmo nome, pode ser necessário trazer o caminho completo ou usar as.

  • as para Renomear: Se você tiver dois itens com o mesmo nome em escopos diferentes, ou se quiser um nome mais curto, pode usar as.

    use std::fmt;
    use std::io;
     
    fn function1() -> fmt::Result {
        // ...
        Ok(())
    }
     
    fn function2() -> io::Result<()> {
        // ...
        Ok(())
    }
     
    // Se tivéssemos dois `Result` e quiséssemos diferenciá-los:
    use std::fmt::Result as FmtResult;
    use std::io::Result as IoResult;
     
    fn function3() -> FmtResult {
        // ...
        Ok(())
    }
     
    fn function4() -> IoResult<()> {
        // ...
        Ok(())
    }
  • {} para Múltiplos Itens: Você pode trazer vários itens do mesmo módulo para o escopo em uma única linha usando chaves.

    use std::collections::{HashMap, HashSet};
     
    fn main() {
        let mut map = HashMap::new();
        let mut set = HashSet::new();
    }
  • * (Glob Operator): Traz todos os itens públicos definidos em um caminho para o escopo atual. Geralmente desencorajado em código de aplicação, mas útil em testes (use super::*;) e em alguns casos de prelude de bibliotecas.

    use std::collections::*; // Traz HashMap, HashSet, VecDeque, etc.
     
    fn main() {
        let mut map = HashMap::new();
        let mut set = HashSet::new();
    }

E. Re-exportando Nomes com pub use

Às vezes, você pode querer que o código que chama seu módulo possa usar um item como se ele tivesse sido definido diretamente no seu módulo, mesmo que você o tenha trazido com use de outro lugar. Isso é chamado de re-exportação e é feito com pub use.

// src/lib.rs (Exemplo de biblioteca)
 
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("Adicionado à lista de espera.");
        }
    }
}
 
// Re-exporta `hosting` para que ele possa ser acessado diretamente de `crate::hosting`
pub use crate::front_of_house::hosting;
 
// Agora, um usuário da sua biblioteca pode fazer:
// use my_library::hosting;
// ou
// use my_library::hosting::add_to_waitlist;

Sem pub use, um usuário da sua biblioteca teria que fazer use my_library::front_of_house::hosting;. A re-exportação simplifica a API pública.

F. Separando Módulos em Arquivos Diferentes

Manter todo o código em um único arquivo main.rs ou lib.rs rapidamente se torna inviável. Rust permite que você separe módulos em arquivos diferentes.

Quando você declara um módulo com mod nome_do_modulo; (com ponto e vírgula, sem chaves), o Rust procurará o código desse módulo em um dos dois locais:

  1. src/nome_do_modulo.rs
  2. src/nome_do_modulo/mod.rs

Exemplo:

Se você tem src/main.rs:

// src/main.rs
mod front_of_house; // Declara o módulo `front_of_house`
 
fn main() {
    front_of_house::hosting::add_to_waitlist();
}

Você pode criar um arquivo src/front_of_house.rs:

// src/front_of_house.rs
// Este arquivo contém o código para o módulo `front_of_house`
pub mod hosting {
    pub fn add_to_waitlist() {
        println!("Adicionado à lista de espera de front_of_house.");
    }
}
 
pub mod serving {
    pub fn take_order() {
        println!("Pedido anotado em front_of_house.");
    }
}

Ou, alternativamente, você pode criar um diretório src/front_of_house/ e colocar o código dentro de src/front_of_house/mod.rs:

src/
├── main.rs
└── front_of_house/
    └── mod.rs
// src/front_of_house/mod.rs
pub mod hosting {
    pub fn add_to_waitlist() {
        println!("Adicionado à lista de espera de front_of_house.");
    }
}

Se front_of_house tiver muitos submódulos, você pode até mesmo separá-los:

src/
├── main.rs
└── front_of_house/
    ├── mod.rs // Contém 'mod hosting;' e 'mod serving;'
    ├── hosting.rs
    └── serving.rs
// src/front_of_house/mod.rs
pub mod hosting; // Declara o submódulo 'hosting'
pub mod serving; // Declara o submódulo 'serving'
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {
    println!("Adicionado à lista de espera do hosting.");
}
// src/front_of_house/serving.rs
pub fn take_order() {
    println!("Pedido anotado do serving.");
}

Essa estrutura permite que você organize seu código em uma hierarquia de arquivos e diretórios que espelha a hierarquia de módulos, tornando projetos grandes muito mais gerenciáveis.

3. Código de Exemplo Oficial (Adaptado do Rust Book) 📚

Este exemplo é uma adaptação do Capítulo 7 do Rust Book, que ilustra a estrutura de um restaurante com módulos e como eles interagem.

// src/lib.rs (ou src/main.rs se for um binário simples)
 
// Declara o módulo `front_of_house` e diz ao Rust para procurar seu código
// em `src/front_of_house.rs` ou `src/front_of_house/mod.rs`.
mod front_of_house;
 
// Usando `pub use` para re-exportar `hosting` do `front_of_house`
// Isso permite que o código externo use `restaurant::hosting::add_to_waitlist()`
// em vez de `restaurant::front_of_house::hosting::add_to_waitlist()`.
pub use crate::front_of_house::hosting;
 
pub fn eat_at_restaurant() {
    // Ordem de café da manhã com pão de centeio
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // Mude a torrada que queremos
    meal.toast = String::from("Wheat");
    println!("Eu gostaria de {} torrada, por favor.", meal.toast);
 
    // O próximo campo não será compilado se descomentado, pois é privado
    // meal.seasonal_fruit = String::from("blueberries");
 
    // Ordem de um aperitivo
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
 
    // Chamando funções através de caminhos absolutos e relativos
    hosting::add_to_waitlist(); // Usando o `hosting` re-exportado
    front_of_house::serving::take_order(); // Usando o caminho completo
 
    back_of_house::cook_order(); // Acessando uma função no `back_of_house`
}
 
fn deliver_order() {
    println!("Pedido entregue ao cliente!");
}
 
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String, // Privado por padrão
    }
 
    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
 
    pub enum Appetizer {
        Soup,
        Salad,
    }
 
    pub fn cook_order() {
        println!("Cozinhando o pedido na cozinha.");
        // Usando `super` para chamar uma função no módulo pai (crate root)
        super::deliver_order();
    }
 
    fn fix_incorrect_order() { // Esta função é privada
        cook_order();
        super::deliver_order();
    }
}

E o conteúdo dos arquivos de módulo separados:

// src/front_of_house.rs
// Este arquivo é o módulo `front_of_house`
pub mod hosting {
    pub fn add_to_waitlist() {
        println!("Adicionado à lista de espera.");
    }
}
 
pub mod serving {
    pub fn take_order() {
        println!("Pedido anotado.");
    }
 
    fn serve_order() {
        println!("Pedido servido.");
    }
 
    mod back_room { // Um submódulo privado dentro de `serving`
        fn clean_table() {
            println!("Mesa limpa.");
        }
    }
}

Para rodar este exemplo:

  1. Crie um novo projeto Rust: cargo new restaurant_org --lib (se for uma biblioteca) ou cargo new restaurant_org (se for um binário).
  2. Substitua o conteúdo de src/lib.rs (ou src/main.rs) pelo primeiro bloco de código.
  3. Crie um novo arquivo src/front_of_house.rs e coloque o segundo bloco de código nele.
  4. Execute com cargo run (se for binário) ou cargo test (se tiver testes e for biblioteca). Se for uma biblioteca, você precisará de um main.rs simples para chamar eat_at_restaurant().

Exemplo de src/main.rs para chamar a função da biblioteca:

// src/main.rs
use restaurant_org::eat_at_restaurant; // Assumindo que a biblioteca se chama `restaurant_org`
 
fn main() {
    eat_at_restaurant();
}

4. Exercícios/Desafios 🧑‍💻

Para solidificar seu conhecimento, tente os seguintes desafios:

  1. Crie um Mini-Aplicativo de Biblioteca:

    • Crie um novo projeto de biblioteca (cargo new my_library --lib).
    • Dentro de src/lib.rs, crie um módulo math que contenha:
      • Uma função pub fn add(a: i32, b: i32) -> i32.
      • Uma função pub fn subtract(a: i32, b: i32) -> i32.
      • Um submódulo geometry com uma função pub fn circle_area(radius: f64) -> f64.
    • Crie um arquivo src/main.rs (no mesmo projeto) que use sua biblioteca:
      • Importe as funções add e circle_area usando use.
      • Chame essas funções e imprima os resultados.
  2. Organize um Projeto de "RPG Simples":

    • Crie um novo projeto binário (cargo new simple_rpg).
    • Crie um módulo player que contenha uma struct pub struct Player { name: String, health: i32, attack: i32 }. Implemente um método pub fn new(...) e pub fn take_damage(&mut self, damage: i32).
    • Crie um módulo enemies com uma struct pub struct Goblin { health: i32 } e um método pub fn attack_player(&self, player: &mut player::Player).
    • Separe o módulo player para src/player.rs e o módulo enemies para src/enemies.rs.
    • No src/main.rs:
      • Crie um Player e um Goblin.
      • Simule um ataque do goblin ao jogador.
      • Imprima o status (vida) do jogador após o ataque.
      • Desafio extra: Use pub use em main.rs para re-exportar Player e Goblin para que possam ser acessados diretamente.

5. Resumo e Próximos Passos 🎯

Nesta aula, desvendamos o mundo da organização de código em Rust:

  • Módulos (mod): Permitem agrupar código relacionado em uma hierarquia.
  • Visibilidade (pub): Controla o que é acessível de fora de um módulo. Por padrão, tudo é privado.
  • Caminhos: crate:: (absoluto), self:: e super:: (relativos) nos permitem referenciar itens em módulos.
  • use: Simplifica o uso de caminhos longos, trazendo itens para o escopo atual.
  • pub use: Re-exporta itens, alterando o caminho público para eles.
  • Módulos em Arquivos Separados: Permite organizar seu projeto em uma estrutura de diretórios lógica.

Dominar módulos é fundamental para escrever código Rust escalável e fácil de manter. Eles são a base para construir bibliotecas e aplicações complexas.

Próximos Passos: Na próxima aula, aprofundaremos nossa compreensão de como os módulos se encaixam no ecossistema Rust, explorando Crates e Pacotes – as unidades de compilação e distribuição de código em Rust. Preparado para construir seu próprio pacote? 📦

© 2025 Escola All Dev. Todos os direitos reservados.

Organizando o Código com Módulos e Caminhos - Curso gratuito de Rust: A linguagem mais amada | escola.all.dev.br