Curso gratuito de Rust: A linguagem mais amada
Organizando o Código com Módulos e Caminhos
Aprenda sobre organizando o código com módulos e caminhos
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 comopubse quiser que sejam acessíveis. - Se uma
structépub, seus campos ainda são privados por padrão. Você precisa marcar campos específicos comopubpara que sejam acessíveis. - Se um
enumépub, todas as suas variantes são automaticamentepub.
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:
- Caminhos Absolutos: Começam do "crate root" (
crate). - Caminhos Relativos: Começam do módulo atual ou de um módulo pai (
selfousuper).
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, poisitem_nameé o mesmo queself::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 ause 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. -
aspara Renomear: Se você tiver dois itens com o mesmo nome em escopos diferentes, ou se quiser um nome mais curto, pode usaras.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 depreludede 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:
src/nome_do_modulo.rssrc/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:
- Crie um novo projeto Rust:
cargo new restaurant_org --lib(se for uma biblioteca) oucargo new restaurant_org(se for um binário). - Substitua o conteúdo de
src/lib.rs(ousrc/main.rs) pelo primeiro bloco de código. - Crie um novo arquivo
src/front_of_house.rse coloque o segundo bloco de código nele. - Execute com
cargo run(se for binário) oucargo test(se tiver testes e for biblioteca). Se for uma biblioteca, você precisará de ummain.rssimples para chamareat_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:
-
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ódulomathque 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
geometrycom uma funçãopub fn circle_area(radius: f64) -> f64.
- Uma função
- Crie um arquivo
src/main.rs(no mesmo projeto) que use sua biblioteca:- Importe as funções
addecircle_areausandouse. - Chame essas funções e imprima os resultados.
- Importe as funções
- Crie um novo projeto de biblioteca (
-
Organize um Projeto de "RPG Simples":
- Crie um novo projeto binário (
cargo new simple_rpg). - Crie um módulo
playerque contenha uma structpub struct Player { name: String, health: i32, attack: i32 }. Implemente um métodopub fn new(...)epub fn take_damage(&mut self, damage: i32). - Crie um módulo
enemiescom uma structpub struct Goblin { health: i32 }e um métodopub fn attack_player(&self, player: &mut player::Player). - Separe o módulo
playerparasrc/player.rse o móduloenemiesparasrc/enemies.rs. - No
src/main.rs:- Crie um
Playere umGoblin. - Simule um ataque do goblin ao jogador.
- Imprima o status (vida) do jogador após o ataque.
- Desafio extra: Use
pub useemmain.rspara re-exportarPlayereGoblinpara que possam ser acessados diretamente.
- Crie um
- Crie um novo projeto binário (
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::esuper::(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? 📦