Controlando escopo e privacidade com módulos
Nesta seção, vamos falar sobre módulos e outras partes do sistema de módulos:
paths, que permitem nomear itens; a palavra-chave use, que traz um caminho
para o escopo; e a palavra-chave pub, que torna itens públicos. Também vamos
discutir a palavra-chave as, pacotes externos e o operador glob.
Folha de referência de módulos
Antes de entrarmos nos detalhes de módulos e caminhos, aqui está uma referência
rápida de como módulos, caminhos, a palavra-chave use e a palavra-chave
pub funcionam no compilador, e de como a maior parte das pessoas organiza o
código. Ao longo deste capítulo veremos exemplos de cada uma dessas regras, mas
esta é uma ótima seção para consultar quando você quiser relembrar como módulos
funcionam.
- Comece pela raiz da crate: ao compilar uma crate, o compilador primeiro procura código para compilar no arquivo raiz da crate, que geralmente é src/lib.rs para uma crate de biblioteca e src/main.rs para uma crate binária.
- Declarando módulos: no arquivo raiz da crate, você pode declarar novos
módulos. Digamos que você declare um módulo
gardencommod garden;. O compilador procurará o código desse módulo nestes lugares:- Inline, entre chaves que substituem o ponto e vírgula após
mod garden - No arquivo src/garden.rs
- No arquivo src/garden/mod.rs
- Inline, entre chaves que substituem o ponto e vírgula após
- Declarando submódulos: em qualquer arquivo que não seja a raiz da crate,
você pode declarar submódulos. Por exemplo, você poderia declarar
mod vegetables;em src/garden.rs. O compilador procurará o código do submódulo dentro do diretório nomeado em função do módulo pai, nestes lugares:- Inline, logo após
mod vegetables, entre chaves no lugar do ponto e vírgula - No arquivo src/garden/vegetables.rs
- No arquivo src/garden/vegetables/mod.rs
- Inline, logo após
- Caminhos para código em módulos: depois que um módulo passa a fazer parte
da crate, você pode se referir ao código dele de qualquer outro lugar dessa
mesma crate, desde que as regras de privacidade permitam, usando o caminho
até esse código. Por exemplo, um tipo
Asparagusno módulogarden::vegetablesseria encontrado emcrate::garden::vegetables::Asparagus. - Privado versus público: por padrão, o código dentro de um módulo é
privado para seus módulos pai. Para tornar um módulo público, declare-o com
pub modem vez demod. Para tornar públicos também os itens contidos em um módulo público, usepubantes de suas declarações. - A palavra-chave
use: dentro de um escopo, a palavra-chaveusecria atalhos para itens, reduzindo a repetição de caminhos longos. Em qualquer escopo que possa se referir acrate::garden::vegetables::Asparagus, você pode criar um atalho comuse crate::garden::vegetables::Asparagus;, e a partir daí só precisa escreverAsparaguspara usar esse tipo naquele escopo.
Aqui, criamos uma crate binária chamada backyard que ilustra essas regras. O
diretório da crate, também chamado backyard, contém estes arquivos e
diretórios:
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│ └── vegetables.rs
├── garden.rs
└── main.rs
O arquivo raiz da crate, neste caso, é src/main.rs, e ele contém:
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("I'm growing {plant:?}!");
}
A linha pub mod garden; diz ao compilador para incluir o código encontrado em
src/garden.rs, que é:
pub mod vegetables;
Aqui, pub mod vegetables; também significa que o código em
src/garden/vegetables.rs será incluído. Esse código é:
#[derive(Debug)]
pub struct Asparagus {}
Agora vamos entrar nos detalhes dessas regras e vê-las em ação!
Agrupando código relacionado em módulos
Módulos nos permitem organizar o código dentro de uma crate para facilitar a leitura e a reutilização. Eles também nos permitem controlar a privacidade dos itens, porque o código dentro de um módulo é privado por padrão. Itens privados são detalhes internos de implementação, não disponíveis para uso externo. Podemos optar por tornar públicos os módulos e os itens dentro deles, o que os expõe para que código externo possa usá-los e depender deles.
Como exemplo, vamos escrever uma crate de biblioteca que forneça a funcionalidade de um restaurante. Vamos definir as assinaturas das funções, mas deixaremos seus corpos vazios para nos concentrarmos na organização do código, e não na implementação de um restaurante.
Na indústria de restaurantes, algumas partes de um restaurante são chamadas de front of house e outras de back of house. Front of house é onde ficam os clientes; isso inclui onde as pessoas da recepção acomodam os clientes, onde os atendentes recebem pedidos e pagamentos e onde bartenders preparam bebidas. Back of house é onde chefs e cozinheiros trabalham na cozinha, onde a equipe lava a louça e onde gerentes fazem trabalho administrativo.
Para estruturar nossa crate dessa forma, podemos organizar suas funções em
módulos aninhados. Crie uma nova biblioteca chamada restaurant executando
cargo new restaurant --lib. Em seguida, insira o código da Listagem 7-1 em
src/lib.rs para definir alguns módulos e assinaturas de função; esse código
representa a seção de front of house.
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
front_of_house contendo outros módulos que, por sua vez, contêm funçõesDefinimos um módulo com a palavra-chave mod, seguida do nome do módulo, neste
caso front_of_house. O corpo do módulo vai entre chaves. Dentro de módulos,
podemos colocar outros módulos, como hosting e serving neste exemplo.
Módulos também podem conter definições de outros itens, como structs, enums,
constantes, traits e, como na Listagem 7-1, funções.
Ao usar módulos, podemos agrupar definições relacionadas e dar nome ao motivo de essa relação existir. Pessoas que usam esse código podem navegar por ele com base nesses agrupamentos, em vez de ter de ler todas as definições, o que facilita encontrar os trechos relevantes. Pessoas adicionando novas funcionalidades a esse código também saberiam onde colocar o código para manter o programa organizado.
Antes, mencionamos que src/main.rs e src/lib.rs são chamados de raízes de
crate. A razão desse nome é que o conteúdo de qualquer um desses dois arquivos
forma um módulo chamado crate, na raiz da estrutura de módulos da crate,
conhecida como árvore de módulos.
A Listagem 7-2 mostra a árvore de módulos da estrutura definida na Listagem 7-1.
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
Essa árvore mostra como alguns módulos se aninham dentro de outros; por
exemplo, hosting está aninhado dentro de front_of_house. A árvore também
mostra que alguns módulos são irmãos, ou seja, são definidos dentro do mesmo
módulo; hosting e serving são irmãos definidos dentro de
front_of_house. Se o módulo A estiver contido dentro do módulo B, dizemos que
o módulo A é filho do módulo B e que o módulo B é o pai do módulo A.
Observe que toda a árvore de módulos está enraizada sob o módulo implícito
chamado crate.
A árvore de módulos pode lembrar a árvore de diretórios do sistema de arquivos do seu computador, e essa comparação é muito apropriada! Assim como usamos diretórios para organizar arquivos, usamos módulos para organizar código. E, assim como arquivos em um diretório, precisamos de um jeito de encontrar nossos módulos.