Organize o código com módulos e crates

RustBeginner
Pratique Agora

Introdução

À medida que um programa Rust cresce, manter todos os tipos e funções em um único arquivo dificulta a visualização das responsabilidades. Você já viu uma biblioteca e um binário no Lab de CLI, além de um módulo de testes no Lab de testes. Agora, este Lab conecta esses exemplos preparados em um único modelo: um pacote é o projeto que o Cargo compila, crates são unidades de compilação e módulos organizam os nomes dentro de uma crate.

Você vai organizar um pequeno pacote de relatório de despensa. Os arquivos e a maior parte do código de implementação já estão preparados, portanto cada etapa se concentra em um limite: compartilhar algo a partir da crate de biblioteca, declarar um módulo em arquivo e importar uma pequena API pública para a crate binária.

Compartilhe uma função entre crates

Nesta etapa, você identificará as duas crates do pacote e disponibilizará uma função da biblioteca para o binário.

Acesse o pacote Cargo preparado:

cd /home/labex/project/pantry-report

Cargo.toml descreve um pacote chamado pantry-report. O Cargo reconhece src/lib.rs como a raiz de uma crate de biblioteca e src/main.rs como a raiz de uma crate binária. Elas pertencem ao mesmo pacote, mas são compiladas como crates separadas.

Inspecione as raízes curtas das crates. Os comandos sed -n exibem os intervalos de linhas solicitados sem editar os arquivos:

sed -n '1,120p' src/lib.rs
sed -n '1,120p' src/main.rs

O binário importa report_title usando o caminho da crate de biblioteca. Um nome de pacote que contém hífen se torna um nome de crate que contém sublinhado no código-fonte Rust; portanto, pantry-report se torna pantry_report.

O tipo de retorno preparado, &'static str, descreve uma referência emprestada a um literal de string que permanece válido durante todo o programa. O marcador 'static é uma anotação de tempo de vida. As regras explícitas de tempo de vida estão intencionalmente fora do escopo deste curso introdutório; nenhuma alteração feita pelo aluno neste Lab depende de entender ou escrever uma.

Os itens são privados para o módulo, a menos que sejam marcados com pub. Abra a raiz da biblioteca:

nano src/lib.rs

Altere somente a declaração da função, de:

fn report_title() -> &'static str {

para:

pub fn report_title() -> &'static str {

pub torna a função parte da interface pública da crate de biblioteca. Salve com Ctrl+O, pressione Enter e saia com Ctrl+X.

Use cargo check para verificar os tipos das duas crates sem produzir uma compilação final executável:

cargo check

Depois, execute a crate binária:

cargo run --quiet

A saída é:

Pantry Report

Isso comprova que o binário consegue atravessar o limite da crate e chamar a função pública da biblioteca.

Declare um módulo em um arquivo

Nesta etapa, você conectará o arquivo inventory.rs preparado à árvore de módulos da crate de biblioteca.

Um arquivo-fonte Rust não é compilado apenas porque existe. A raiz da crate precisa declarar o módulo. Para uma declaração chamada inventory, o Rust procura src/inventory.rs e coloca seus itens sob o caminho inventory::....

Inspecione o arquivo de módulo preparado. Como antes, sed -n '1,200p' usa -n para suprimir a saída automática, enquanto 1,200p imprime somente o intervalo de linhas solicitado:

sed -n '1,200p' src/inventory.rs

A linha #[derive(Debug)] solicita que o Rust gere suporte padrão para formatação de depuração para Item. O programa visível não depende desse recurso; portanto, considere-o metadado preparado, e não um novo requisito. O tipo Item, seus campos e describe são públicos. O auxiliar availability não tem pub, então continua privado dentro do módulo. describe pode chamar esse auxiliar privado, enquanto os chamadores externos recebem apenas o resultado público.

Abra a raiz da biblioteca:

nano src/lib.rs

Substitua // MODULE_DECLARATION por:

pub mod inventory;

O primeiro pub expõe o módulo por meio da crate de biblioteca. A parte mod inventory; conecta o arquivo à árvore de módulos. Salve e saia do Nano; em seguida, verifique o pacote:

cargo check
Finished `dev` profile ...

A verificação bem-sucedida comprova que o Rust encontrou src/inventory.rs e o compilou como pantry_report::inventory.

Importe e use a API pública

Nesta etapa, você usará uma declaração use para trazer itens do módulo para a crate binária e imprimir um registro da despensa.

Sem uma importação, os caminhos completos são pantry_report::inventory::Item e pantry_report::inventory::describe. Uma declaração use cria nomes mais curtos no escopo atual sem mover nem copiar os itens.

Abra o código-fonte do binário:

nano src/main.rs

Substitua // INVENTORY_IMPORT por esta importação agrupada:

use pantry_report::inventory::{describe, Item};

As chaves agrupam dois itens que compartilham o mesmo prefixo de caminho. Em seguida, substitua // INVENTORY_REPORT por:

    let lentils = Item {
        name: String::from("lentils"),
        quantity: 4,
    };
    println!("{}", describe(&lentils));

O binário pode criar Item porque o tipo e os dois campos são públicos. Ele empresta o item para describe; essa função pública chama privadamente availability dentro do próprio módulo.

Salve e saia do Nano; depois, execute o pacote:

cargo run --quiet
Pantry Report
lentils: 4 jars (stocked)

As duas linhas mostram que tanto o compartilhamento no nível da crate quanto a organização no nível do módulo estão funcionando por meio de uma pequena API pública.

Resumo

Você trabalhou com um pacote Cargo contendo crates de biblioteca e binária, tornou público um item da biblioteca, declarou um módulo em arquivo, seguiu caminhos de crates e módulos, encurtou esses caminhos com use e manteve um auxiliar de implementação privado por trás de uma pequena API pública.