sábado, 10 de abril de 2021

Separando Módulos em Arquivos Diferentes

Até agora, todos os exemplos neste capítulo definiram vários módulos em um arquivo. Quando os módulos ficam grandes, você pode querer mover suas definições para um arquivo separado para tornar o código mais fácil de navegar.

Por exemplo, vamos começar com o código da Listagem 7-17 e mover o módulo frente_da_casa para seu próprio arquivo src/frente_da_casa.rs alterando o arquivo raiz do crate para que contenha o código mostrado na Listagem 7-21. Neste caso, o arquivo raiz do crate é src/lib.rs, mas este procedimento também funciona com crates binários cujo crate raiz do arquivo é src/main.rs.

Nome do arquivo: src/lib.rs

mod frente_da_casa;

pub use crate::frente_da_casa::hospedagem;

pub fn comer_no_restaurante() {
    hospedagem::adicionar_a_lista_de_espera();
    hospedagem::adicionar_a_lista_de_espera();
    hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-21: Declarando o módulo frente_da_casa cujo corpo estará em src/frente_da_casa.rs

E src/frente_da_casa.rs obtém as definições do corpo do módulo frente_da_casa, conforme mostrado na Listagem 7-22.

Nome do arquivo: src/frente_da_casa.rs

pub mod hospedagem {
    pub fn adicionar_a_lista_de_espera() {}
}

Listagem 7-22: Definições dentro do módulo frente_da_casa em src/frente_da_casa.rs

Usar um ponto-e-vírgula depois de mod frente_da_casa em vez de usar um bloco informa ao Rust para carregar o conteúdo do módulo de outro arquivo com o mesmo nome do módulo. Para continuar com nosso exemplo e extrair o módulo hospedagem para seu próprio arquivo também, alteramos src/frente_da_casa.rs para conter apenas a declaração do módulo hospedagem:

Nome do arquivo: src/frente_da_casa.rs

pub mod hospedagem;

Em seguida, criamos um diretório src/frente_da_casa e um arquivo src/frente_da_casa/hospedagem.rs para conter as definições feitas no módulo hospedagem:

Nome do arquivo: src/frente_da_casa/hospedagem.rs


#![allow(unused)]
fn main() {
pub fn adicionar_a_lista_de_espera() {}
}

A árvore de módulos permanece a mesma e as chamadas da função comer_no_restaurante funcionarão sem nenhuma modificação, embora as definições estejam em arquivos diferentes. Essa técnica permite mover módulos para novos arquivos à medida que aumentam de tamanho.

Observe que a declaração pub use crate::frente_da_casa::hospedagem em src/lib.rs também não mudou, nem use tem qualquer impacto sobre quais arquivos são compilados como parte do crate. A palavra-chave mod declara módulos e Rust procura em um arquivo com o mesmo nome do módulo o código que entra naquele módulo.

Resumo

Rust permite dividir um pacote em vários crates e um crate em módulos para que você possa consultar os itens definidos em um módulo de outro módulo. Você pode fazer isso especificando caminhos absolutos ou relativos. Esses caminhos podem ser trazidos para o escopo com uma declaração use para que você possa usar um caminho mais curto para vários usos do item nesse escopo. O código do módulo é privado por padrão, mas você pode tornar as definições públicas adicionando a palavra-chave pub.

No próximo capítulo, veremos algumas estruturas de dados de coleção na biblioteca padrão que você pode usar em seu código bem organizado.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

sexta-feira, 9 de abril de 2021

Trazendo caminhos para o escopo com a palavra-chave use

Pode parecer que os caminhos que escrevemos para chamar funções até agora são inconvenientemente longos e repetitivos. Por exemplo, na Listagem 7-7, se escolhemos o caminho absoluto ou relativo para a função adicionar_a_lista_de_espera, sempre que quiséssemos chamar adicionar_a_lista_de_espera, tínhamos que especificar frente_da_casa e hospedagem também. Felizmente, existe uma maneira de simplificar esse processo. Podemos trazer um caminho para um escopo uma vez e, em seguida, chamar os itens nesse caminho como se fossem itens locais com a palavra-chave use.

Na Listagem 7-11, trazemos o módulo crate::frente_da_casa::hospedagem para o escopo da função comer_no_restaurante, portanto, só temos que especificar hospedagem::adicionar_a_lista_de_espera para chamar a função adicionar_a_lista_de_espera em comer_no_restaurante.

Nome do arquivo: src/lib.rs

mod frente_da_casa {
    pub mod hospedagem {
        pub fn adicionar_a_lista_de_espera() {}
    }
}

use crate::frente_da_casa::hospedagem;

pub fn comer_no_restaurante() {
    hospedagem::adicionar_a_lista_de_espera();
    hospedagem::adicionar_a_lista_de_espera();
    hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-11: Trazendo um módulo para o escopo com use

Adicionar use e um caminho em um escopo é semelhante a criar um link simbólico no sistema de arquivos. Ao adicionar use crate::frente_da_casa::hospedagem na raiz do crate, hospedagem agora é um nome válido nesse escopo, como se o módulo hospedagem tivesse sido definido na raiz do crate. Os caminhos trazidos ao escopo use também verificam a privacidade, como quaisquer outros caminhos.

Você também pode trazer um item para o escopo com use e um caminho relativo. A Listagem 7-12 mostra como especificar um caminho relativo para obter o mesmo comportamento da Listagem 7-11.

Nome do arquivo: src/lib.rs

mod frente_da_casa {
    pub mod hospedagem {
        pub fn adicionar_a_lista_de_espera() {}
    }
}

use self::frente_da_casa::hospedagem;

pub fn comer_no_restaurante() {
    hospedagem::adicionar_a_lista_de_espera();
    hospedagem::adicionar_a_lista_de_espera();
    hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-12: Trazendo um módulo para o escopo com usee um caminho relativo

Criação de caminhos de uso idiomáticos

Na Listagem 7-11, você pode ter se perguntado por que especificamos use crate::frente_da_casa::hospedagem e, em seguida, chamamos hospedagem::adicionar_a_lista_de_espera em comer_no_restaurante em vez de especificar o caminho use até a função adicionar_a_lista_de_espera para obter o mesmo resultado, como na Listagem 7-13.

Nome do arquivo: src/lib.rs

mod frente_da_casa {
    pub mod hospedagem {
        pub fn adicionar_a_lista_de_espera() {}
    }
}

use crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera;

pub fn comer_no_restaurante() {
    adicionar_a_lista_de_espera();
    adicionar_a_lista_de_espera();
    adicionar_a_lista_de_espera();
}

Listagem 7-13: Trazendo a função adicionar_a_lista_de_espera para o escopo com use, que é unidiomático

Embora as Listagens 7-11 e 7-13 realizem a mesma tarefa, a Listagem 7-11 é a maneira idiomática de trazer uma função para o escopo use. Trazendo o módulo pai da função para o escopo com use temos que especificar o módulo pai ao chamar a função torna claro que a função não é definida localmente enquanto ainda minimiza a repetição do caminho completo. O código na Listagem 7-13 não está claro sobre onde adicionar_a_lista_de_espera está definido.

Por outro lado, ao trazer structs, enums e outros itens com use, é idiomático especificar o caminho completo. A Listagem 7-14 mostra a maneira idiomática de trazer a estrutura HashMap da biblioteca padrão para o escopo de um crate binário.

Nome do arquivo: src/main.rs

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

Listagem 7-14: Trazendo HashMap para o escopo de uma forma idiomática

Não há nenhuma razão forte por trás desse idioma: é apenas a convenção que surgiu, e as pessoas se acostumaram a ler e escrever código Rust dessa maneira.

A exceção a esse idioma é se estivermos trazendo dois itens com o mesmo nome para o escopo com declarações use, porque Rust não permite isso. A Listagem 7-15 mostra como trazer dois tipos Result para o escopo que têm o mesmo nome, mas módulos pai diferentes e como fazer referência a eles.

Nome do arquivo: src/lib.rs

use std::fmt;
use std::io;

fn funcao1() -> fmt::Result {
    // --recorte--
    Ok(())
}

fn funcao2() -> io::Result<()> {
    // --recorte--
    Ok(())
}

Listagem 7-15: Trazer dois tipos com o mesmo nome para o mesmo escopo requer o uso de seus módulos pais.

Como você pode ver, o uso dos módulos pai distingue os dois tipos Result. Se, em vez disso, especificássemos use std::fmt::Result e use std::io::Result, teríamos dois tipos Result no mesmo escopo e Rust não saberia a qual deles se referia quando usamos Result.

Fornecimento de novos nomes com a palavra-chave as

Há outra solução para o problema de trazer dois tipos do mesmo nome para o mesmo escopo com use: após o caminho, podemos especificar as e um novo nome local, ou alias, para o tipo. A Listagem 7-16 mostra outra maneira de escrever o código na Listagem 7-15 renomeando um dos dois tipos Result usando as.

Nome do arquivo: src/lib.rs

use std::fmt::Result;
use std::io::Result as IoResult;

fn funcao1() -> Result {
    // --recorte--
    Ok(())
}

fn funcao2() -> IoResult<()> {
    // --recorte--
    Ok(())
}

Listagem 7-16: Renomeando um tipo quando ele é trazido ao escopo com a palavra-chave as

Na segunda declaração use, escolhemos o novo nome IoResult para o tipo std::io::Result, que não entrará em conflito com o Result de std::fmt que também incluímos no escopo. A Listagem 7-15 e a Listagem 7-16 são consideradas idiomáticas, então a escolha é sua!

Reexportando nomes com pub use

Quando colocamos um nome no escopo com a palavra-chave use, o nome disponível no novo escopo é privado. Para permitir que o código que chama nosso código se refira a esse nome como se tivesse sido definido no escopo desse código, podemos combinar pub e use. Essa técnica é chamada de reexportação porque estamos trazendo um item para o escopo, mas também disponibilizando esse item para que outros tragam ao seu escopo.

A Listagem 7-17 mostra o código da Listagem 7-11 com use no módulo raiz alterado para pub use.

Nome do arquivo: src/lib.rs

mod frente_da_casa {
    pub mod hospedagem {
        pub fn adicionar_a_lista_de_espera() {}
    }
}

pub use crate::frente_da_casa::hospedagem;

pub fn comer_no_restaurante() {
    hospedagem::adicionar_a_lista_de_espera();
    hospedagem::adicionar_a_lista_de_espera();
    hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-17: Disponibilizando um nome para qualquer código para uso de um novo escopo com pub use

Ao usar pub use, o código externo agora pode chamar a função adicionar_a_lista_de_espera usando hospedagem::adicionar_a_lista_de_espera. Se não tivéssemos especificado pub use, a função comer_no_restaurante poderia chamar hospedagem::adicionar_a_lista_de_espera em seu escopo, mas o código externo não poderia aproveitar esse novo caminho.

A reexportação é útil quando a estrutura interna de seu código é diferente de como os programadores que chamam seu código pensariam sobre o domínio. Por exemplo, nesta metáfora do restaurante, as pessoas que dirigem o restaurante pensam em "frente da casa" e "nos fundos da casa". Mas os clientes que visitam um restaurante provavelmente não pensarão nas partes do restaurante nesses termos. Com pub use, podemos escrever nosso código com uma estrutura, mas expor uma estrutura diferente. Isso torna nossa biblioteca bem organizada para programadores que trabalham na biblioteca e programadores que chamam a biblioteca.

Usando Pacotes Externos

No Capítulo 2, programamos um projeto de jogo de adivinhação em Rust que usava um pacote externo chamado rand para obter números aleatórios. Para usar rand em nosso projeto, adicionamos esta linha ao arquivo Cargo.toml:

Nome do arquivo: Cargo.toml

[dependencies]
rand = "0.5.5"

Adicionar rand como uma dependência em Cargo.toml diz ao Cargo para baixar o pacote rand e todas as dependências de crates.io e disponibilizar rand para nosso projeto.

Então, para trazer as definições rand para o escopo de nosso pacote, adicionamos uma linha use começando com o nome do crate rand, e listamos os itens que queríamos trazer para o escopo. Lembre-se de que na seção "Gerando um número aleatório" no Capítulo 2, colocamos o traço Rng no escopo e chamamos a função rand::thread_rng:

use std::io;
use rand::Rng;

fn main() {
    println!("Adivinhe o número!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("O número secreto é: {}", numero_secreto);

    println!("Por favor entre com o seu palpite.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Falha ao ler a linha");

    println!("Seu palpite: {}", guess);
}

Membros da comunidade Rust disponibilizaram muitos pacotes em crates.io, e colocar qualquer um deles em seu pacote envolve as mesmas etapas: listá-los no arquivo Cargo.toml do seu pacote e usar use para trazer itens de seus crates para o escopo.

Observe que a biblioteca padrão (std) também é um crate externo ao nosso pacote. Como a biblioteca padrão é enviada com a linguagem Rust, não precisamos alterar Cargo.toml para incluir std. Mas precisamos nos referir a ele use para trazer itens de lá para o escopo de nosso pacote. Por exemplo, com HashMap usaríamos esta linha:


#![allow(unused)]
fn main() {
use std::collections::HashMap;
}

Este é um caminho absoluto começando com std, o nome do crate da biblioteca padrão.

Usando caminhos aninhados para limpar listas grandes de use

Se estivermos usando vários itens definidos na mesmo crate ou mesmo módulo, listar cada item em sua própria linha pode ocupar muito espaço vertical em nossos arquivos. Por exemplo, essas duas declarações use que tivemos no Jogo de Adivinhação na Listagem 2-4 trazem itens do escopo std:

Nome do arquivo: src/main.rs

use rand::Rng;
// --recorte--
use std::cmp::Ordering;
use std::io;
// --recorte--

fn main() {
    println!("Adivinhe o número!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("O número secreto é: {}", numero_secreto);

    println!("Por favor entre com o seu palpite.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Falha ao ler a linha");

    println!("Seu palpite: {}", guess);

    match guess.cmp(&numero_secreto) {
        Ordering::Less => println!("Muito baixo!"),
        Ordering::Greater => println!("Muito alto!"),
        Ordering::Equal => println!("Você ganhou!"),
    }
}

Em vez disso, podemos usar caminhos aninhados para trazer os mesmos itens para o escopo em uma linha. Fazemos isso especificando a parte comum do caminho, seguida por dois pontos e, em seguida, colchetes ao redor de uma lista das partes dos caminhos que diferem, conforme mostrado na Listagem 7-18.

Nome do arquivo: src/main.rs

use rand::Rng;
// --recorte--
use std::{cmp::Ordering, io};
// --recorte--

fn main() {
    println!("Adivinhe o número!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("O número secreto é: {}", numero_secreto);

    println!("Por favor entre com o seu palpite.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Falha ao ler a linha");

    let guess: u32 = guess.trim().parse().expect("Por favor entre com um número!");

    println!("Seu palpite: {}", guess);

    match guess.cmp(&numero_secreto) {
        Ordering::Less => println!("Muito baixo!"),
        Ordering::Greater => println!("Muito alto!"),
        Ordering::Equal => println!("Você ganhou!"),
    }
}

Listagem 7-18: Especificando um caminho aninhado para trazer vários itens com o mesmo prefixo para o escopo

Em programas maiores, colocar muitos itens no escopo do mesmo crate ou módulo usando caminhos aninhados pode reduzir muito o número de declarações use separadas necessárias!

Podemos usar um caminho aninhado em qualquer nível de um caminho, o que é útil ao combinar duas declarações use que compartilham um subcaminho. Por exemplo, a Listagem 7-19 mostra duas declarações use: uma que traz std::io para o escopo e outra que traz std::io::Write para o escopo.

Nome do arquivo: src/lib.rs

use std::io;
use std::io::Write;

Listagem 7-19: Duas declarações use em que uma é um subcaminho da outra

A parte comum desses dois caminhos é std::io, e esse é o primeiro caminho completo. Para mesclar esses dois caminhos em uma declaraçãouse, podemos usar self no caminho aninhado, conforme mostrado na Listagem 7-20.

Nome do arquivo: src/lib.rs

use std::io::{self, Write};

Listagem 7-20: Combinando os caminhos na Listagem 7-19 em uma declaração use

Esta linha traz std::io e std::io::Write em seu escopo.

O Operador Glob

Se quisermos trazer todos os itens públicos definidos em um caminho para o escopo, podemos especificar esse caminho seguido pelo operador glob *:


#![allow(unused)]
fn main() {
use std::collections::*;
}

Esta declaração use traz todos os itens públicos definidos no escopo std::collections atual. Tenha cuidado ao usar o operador glob! Glob pode tornar mais difícil saber quais nomes estão no escopo e onde um nome usado em seu programa foi definido.

O operador glob é frequentemente usado ao testar para trazer tudo em teste para o módulo tests; falaremos sobre isso na seção “Como escrever testes” no Capítulo 11. Às vezes, o operador glob também é usado como parte do padrão de prelúdio: consulte a documentação da biblioteca padrão para obter mais informações sobre esse padrão.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

quinta-feira, 8 de abril de 2021

Caminhos para fazer referência a um item na árvore de módulo em Rust

Para mostrar ao Rust onde encontrar um item em uma árvore de módulo, usamos um caminho da mesma forma que usamos um caminho ao navegar em um sistema de arquivos. Se quisermos chamar uma função, precisamos saber seu caminho.

Um caminho pode assumir duas formas:

  • Um caminho absoluto começa a partir de uma raiz de crate usando um nome de crate ou um literal crate.
  • Um caminho relativo começa a partir do módulo atual e usa self, super ou um identificador no módulo atual.

Os caminhos absolutos e relativos são seguidos por um ou mais identificadores separados por dois pontos duplos (::).

Voltemos ao exemplo da Listagem 7-1. Como chamamos a função adicionar_a_lista_de_espera? Isso é o mesmo que perguntar: qual é o caminho da função adicionar_a_lista_de_espera? Na Listagem 7-3, simplificamos um pouco nosso código removendo alguns dos módulos e funções. Mostraremos duas maneiras de chamar a função adicionar_a_lista_de_espera a partir de uma nova função comer_no_restaurante definida na raiz do crate. A função comer_no_restaurante faz parte da API pública do nosso crate de biblioteca, então a marcamos com a palavra-chave pub. Na seção “Expondo caminhos com a palavra-chave pub”, entraremos em mais detalhes sobre pub. Observe que este exemplo ainda não será compilado; vamos explicar o porquê daqui a pouco.

Nome do arquivo: src/lib.rs

Esse código não compila Esse código não compila.

mod frente_da_casa {
    mod hospedagem {
        fn adicionar_a_lista_de_espera() {}
    }
}

pub fn comer_no_restaurante() {
    // Caminho absoluto
    crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();

    // Caminho relativo
    frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-3: Chamando a adicionar_a_lista_de_esperafunção usando caminhos absolutos e relativos.

Na primeira vez que chamamos a função adicionar_a_lista_de_espera em comer_no_restaurante, usamos um caminho absoluto. A função adicionar_a_lista_de_espera é definida no mesmo crate que comer_no_restaurante, o que significa que podemos usar a palavra-chave crate para iniciar um caminho absoluto.

Depois de crate, incluímos cada um dos módulos sucessivos até chegarmos a adicionar_a_lista_de_espera. Você pode imaginar um sistema de arquivos com a mesma estrutura, e nós especificaríamos o caminho /frente_da_casa/hospedagem/adicionar_a_lista_de_espera para executar o programa adicionar_a_lista_de_espera; usar o nome crate para iniciar a partir da raiz do crate é como usar / para iniciar a partir da raiz do sistema de arquivos em seu shell.

A segunda vez que chamar adicionar_a_lista_de_espera em comer_no_restaurante, usamos um caminho relativo. O caminho começa com frente_da_casa, o nome do módulo definido no mesmo nível da árvore de módulos que comer_no_restaurante. Aqui, o equivalente do sistema de arquivos seria o uso do caminho frente_da_casa/hospedagem/adicionar_a_lista_de_espera. Começar com um nome significa que o caminho é relativo.

A escolha de usar um caminho relativo ou absoluto é uma decisão que você tomará com base em seu projeto. A decisão deve depender da probabilidade de você mover o código de definição do item separadamente ou junto com o código que usa o item. Por exemplo, se movermos o módulo frente_da_casa e a função comer_no_restaurante para um módulo denominado esperiencia_do_cliente, precisaremos atualizar o caminho absoluto para adicionar_a_lista_de_espera, mas o caminho relativo ainda será válido. No entanto, se movêssemos a função comer_no_restaurante separadamente para um módulo denominado jantando, o caminho absoluto para a chamada adicionar_a_lista_de_espera permaneceria o mesmo, mas o caminho relativo precisaria ser atualizado. Nossa preferência é especificar caminhos absolutos porque é mais provável mover definições de código e chamadas de item independentemente umas das outras.

Vamos tentar compilar a Listagem 7-3 e descobrir por que ela ainda não compila! O erro que obtemos é mostrado na Listagem 7-4.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hospedagem` is private
 --> src/lib.rs:9:28
  |
9 |     crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
  |                            ^^^^^^^ private module
  |
note: the module `hospedagem` is defined here
 --> src/lib.rs:2:5
  |
2 |     mod hospedagem {
  |     ^^^^^^^^^^^

error[E0603]: module `hospedagem` is private
  --> src/lib.rs:12:21
   |
12 |     frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
   |                     ^^^^^^^ private module
   |
note: the module `hospedagem` is defined here
  --> src/lib.rs:2:5
   |
2  |     mod hospedagem {
   |     ^^^^^^^^^^^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant`

To learn more, run the command again with --verbose.

Listagem 7-4: Erros do compilador na construção do código na Listagem 7-3.

As mensagens de erro indicam que o módulo hospedagem é privado. Em outras palavras, temos os caminhos corretos para o módulo hospedagem e a função adicionar_a_lista_de_espera, mas Rust não nos deixa usá-los porque não tem acesso às seções privadas.

Módulos não são úteis apenas para organizar seu código. Eles também definem o limite de privacidade em Rust: a linha que encapsula os detalhes de implementação que o código externo não tem permissão para conhecer, chamar ou confiar. Portanto, se você quiser tornar um item como uma função ou estrutura privada, coloque-o em um módulo.

A forma como a privacidade funciona no Rust é que todos os itens (funções, métodos, estruturas, enums, módulos e constantes) são privados por padrão. Os itens em um módulo pai não podem usar os itens privados dentro dos módulos filhos, mas os itens nos módulos filhos podem usar os itens em seus módulos ancestrais. O motivo é que os módulos filhos envolvem e ocultam seus detalhes de implementação, mas os módulos filhos podem ver o contexto no qual estão definidos. Para continuar com a metáfora do restaurante, pense nas regras de privacidade como sendo o back office de um restaurante: o que acontece lá é privado para os clientes do restaurante, mas os gerentes de escritório podem ver e fazer tudo no restaurante em que operam.

Rust optou por fazer com que o sistema de módulo funcionasse dessa maneira, de modo que ocultar detalhes de implementação internos seja o padrão. Dessa forma, você sabe quais partes do código interno podem ser alteradas sem quebrar o código externo. Mas você pode expor partes internas do código dos módulos filhos para módulos ancestrais externos usando a palavra-chave pub para tornar um item público.

Expondo caminhos com a palavra-chave pub

Voltemos ao erro na Listagem 7-4 que nos disse que o módulo hospedagem é privado. Queremos que a função comer_no_restaurante no módulo pai tenha acesso à função adicionar_a_lista_de_espera no módulo filho, portanto, marcamos o módulo hospedagem com a palavra-chave pub, conforme mostrado na Listagem 7-5.

Nome do arquivo: src/lib.rs

Esse código não compila Esse código não compila.

mod frente_da_casa {
    pub mod hospedagem {
        fn adicionar_a_lista_de_espera() {}
    }
}

pub fn comer_no_restaurante() {
    // Caminho absoluto
    crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();

    // Caminho relativo
    frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-5: Declarando o módulo hospedagem como pub para usá-lo de comer_no_restaurante

Infelizmente, o código na Listagem 7-5 ainda resulta em um erro, conforme mostrado na Listagem 7-6.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `adicionar_a_lista_de_espera` is private
 --> src/lib.rs:9:37
  |
9 |     crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
  |                                     ^^^^^^^^^^^^^^^ private function
  |
note: the function `adicionar_a_lista_de_espera` is defined here
 --> src/lib.rs:3:9
  |
3 |         fn adicionar_a_lista_de_espera() {}
  |         ^^^^^^^^^^^^^^^^^^^^

error[E0603]: function `adicionar_a_lista_de_espera` is private
  --> src/lib.rs:12:30
   |
12 |     frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
   |                              ^^^^^^^^^^^^^^^ private function
   |
note: the function `adicionar_a_lista_de_espera` is defined here
  --> src/lib.rs:3:9
   |
3  |         fn adicionar_a_lista_de_espera() {}
   |         ^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant`

To learn more, run the command again with --verbose.

Listagem 7-6: Erros do compilador na construção do código na Listagem 7-5

O que aconteceu? Adicionar a palavra-chave pub antes de mod hospedagem torna o módulo público. Com essa mudança, se podemos acessar frente_da_casa, podemos acessar hospedagem. Mas o conteúdo de hospedagem ainda é privado; tornar o módulo público não torna seu conteúdo público. A palavra-chave pub em um módulo permite apenas que o código em seus módulos ancestrais se refira a ele.

Os erros na Listagem 7-6 dizem que a função adicionar_a_lista_de_espera é privada. As regras de privacidade se aplicam a structs, enums, funções e métodos, bem como a módulos.

Também tornemos a função adicionar_a_lista_de_espera pública adicionando a palavra-chave pub antes de sua definição, como na Listagem 7-7.

Nome do arquivo: src/lib.rs

mod frente_da_casa {
    pub mod hospedagem {
        pub fn adicionar_a_lista_de_espera() {}
    }
}

pub fn comer_no_restaurante() {
    // Caminho absoluto
    crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();

    // Caminho relativo
    frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-7: Adicionando a palavra-chave pub a mod hospedagem e fn adicionar_a_lista_de_espera permite-nos chamar a função de comer_no_restaurante

Agora o código será compilado! vamos dar uma olhada no caminho absoluto e relativo e verificar por que adicionar a palavra-chave pub nos permite usar esses caminhos em adicionar_a_lista_de_espera com relação às regras de privacidade.

No caminho absoluto, começamos com crate a raiz da árvore de módulos do nosso crate. Em seguida, o módulo frente_da_casa é definido na raiz do crate. O módulo frente_da_casa não é público, mas como a função comer_no_restaurante é definida no mesmo módulo que frente_da_casa (ou seja, comer_no_restaurante e frente_da_casa são irmãos), podemos nos referir a frente_da_casa a partir de comer_no_restaurante. O próximo é o módulo hospedagem marcado com pub. Podemos acessar o módulo pai de hospedagem, para que possamos acessar hospedagem. Finalmente, a função adicionar_a_lista_de_espera é marcada com pub e podemos acessar seu módulo pai, então esta chamada de função funciona!

No caminho relativo, a lógica é a mesma que o caminho absoluto, exceto para a primeira etapa: em vez de começar na raiz do crate, o caminho começa em frente_da_casa. O módulo frente_da_casa é definido dentro do mesmo módulo que comer_no_restaurante, portanto, o caminho relativo a partir do módulo no qual comer_no_restaurante está definido funciona. Então, porque hospedagem e adicionar_a_lista_de_espera estão marcados com pub, o resto do caminho funciona, e esta chamada de função é válida!

Iniciando caminhos relativos com super

Também podemos construir caminhos relativos que começam no módulo pai usando super no início do caminho. É como iniciar um caminho de sistema de arquivos com a sintaxe ... Por que queremos fazer isso?

Considere o código na Listagem 7-8 que modela a situação em que um chef corrige um pedido incorreto e o apresenta pessoalmente ao cliente. A função consertar_pedido_incorreto chama a função servir_pedido especificando o caminho para servir_pedido começar com super:

Nome do arquivo: src/lib.rs

fn servir_pedido() {}

mod fundo_da_casa {
    fn consertar_pedido_incorreto() {
        preparar_pedido();
        super::servir_pedido();
    }

    fn preparar_pedido() {}
}

Listagem 7-8: Chamando uma função usando um caminho relativo começando com super

A função consertar_pedido_incorreto está no módulo fundo_da_casa, então podemos usar super para ir para o módulo pai de fundo_da_casa, que neste caso é a raiz crate. A partir daí, procuramos servir_pedido e encontramos. Sucesso! Achamos que o módulo fundo_da_casa e a função servir_pedido provavelmente permanecerão na mesma relação um com o outro e serão movidos juntos se decidirmos reorganizar a árvore de módulos do crate. Portanto, usamos super por isso, teremos menos lugares para atualizar o código no futuro, se esse código for movido para um módulo diferente.

Tornando Structs e Enums Públicos

Também podemos usar pub para designar structs e enums como públicos, mas há alguns detalhes extras. Se usarmos pub antes de uma definição de estrutura, tornamos a estrutura pública, mas os campos da estrutura ainda serão privados. Podemos tornar cada campo público ou não, caso a caso. Na Listagem 7-9, definimos uma estrutura fundo_da_casa::cafeDaManha pública com um campo torada público, mas um campo fruta_temporada privado. Isso modela o caso de um restaurante onde o cliente pode escolher o tipo de pão que vem com a refeição, mas o chef decide qual fruta acompanhará a refeição com base na temporada e no estoque. As frutas disponíveis mudam rapidamente, então os clientes não podem escolher a fruta ou mesmo ver quais frutas irão receber.

Nome do arquivo: src/lib.rs

mod fundo_da_casa {
    pub struct cafeDaManha {
        pub torada: String,
        fruta_temporada: String,
    }

    impl cafeDaManha {
        pub fn verao(torada: &str) -> cafeDaManha {
            cafeDaManha {
                torada: String::from(torada),
                fruta_temporada: String::from("Pessegos"),
            }
        }
    }
}

pub fn comer_no_restaurante() {
    // Peça um café da manhã no verão com torradas de centeio
    let mut meal = fundo_da_casa::cafeDaManha::verao("Centeio");
    // Mudar de ideia sobre o pão que gostaríamos
    meal.torada = String::from("Trigo");
    println!("Eu gostaria de torrada de {} por favor", meal.torada);

    // A próxima linha não será compilada se descomentarmos; não foram permitidos
    // para ver ou modificar as frutas da estação que vêm com a refeição
    // meal.fruta_temporada = String::from("amoras");
}

Listagem 7-9: Uma estrutura com alguns campos públicos e alguns campos privados

Como o campo torada na estrutura fundo_da_casa::cafeDaManha é público, no comer_no_restaurante podemos escrever e ler do campo torada usando a notação de ponto. Observe que não podemos usar o campo fruta_temporada em comer_no_restaurante porque fruta_temporada é privado. Tente remover o comentário da linha modificando o valor do campo fruta_temporada para ver qual erro você obtém!

Além disso, observe que, por fundo_da_casa::cafeDaManha ter um campo privado, a estrutura precisa fornecer uma função pública associada que constrói uma instância de cafeDaManha (nós a nomeamos verao aqui). Se cafeDaManha não tivéssemos essa função, não poderíamos criar uma instância de cafeDaManha no comer_no_restaurante porque não poderíamos definir o valor do campo privado fruta_temporada em comer_no_restaurante.

Em contraste, se tornarmos um enum público, todas as suas variantes serão públicas. Precisamos apenas da palavra-chave pub antes de enum, conforme mostrado na Listagem 7-10.

Nome do arquivo: src/lib.rs

mod fundo_da_casa {
    pub enum Appetizer {
        Sopa,
        Salada,
    }
}

pub fn comer_no_restaurante() {
    let pedido1 = fundo_da_casa::Appetizer::Sopa;
    let pedido2 = fundo_da_casa::Appetizer::Salada;
}

Listagem 7-10: Designar um enum como público torna todas as suas variantes públicas

Como tornamos o enum Appetizer público, podemos usar as variantes Sopa e Salada em comer_no_restaurante. Enums não são muito úteis, a menos que suas variantes sejam públicas; seria irritante ter que anotar todas as variantes de enum pub em todos os casos, então o padrão para variantes de enum é ser público. As estruturas são frequentemente úteis sem que seus campos sejam públicos, portanto, os campos de estrutura seguem a regra geral de tudo ser privado por padrão, a menos que anotado com pub.

Há mais uma situação envolvendo pub que não cobrimos, e esse é nosso último recurso do sistema de módulo: a palavra-chave use. Vamos cobrir use por si só primeiro e, em seguida, mostraremos como combinar pub e use.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

quarta-feira, 7 de abril de 2021

Definindo Módulos para Controle de Escopo e Privacidade

Nesta seção, falaremos sobre módulos e outras partes do sistema de módulos, nomeadamente caminhos que permitem nomear itens; a palavra-chave use que traz um caminho ao escopo; e a palavra-chave pub para tornar os itens públicos. Também discutiremos a palavra-chave as, os pacotes externos e o operador glob. Por enquanto, vamos nos concentrar nos módulos!

Os módulos nos permitem organizar o código dentro de um crate em grupos para facilitar a leitura e reutilização. Os módulos também controlam a privacidade dos itens, que é se um item pode ser usado por código externo (público) ou é um detalhe de implementação interno e não está disponível para uso externo (privado).

Como exemplo, vamos escrever um crate de biblioteca que fornece a funcionalidade de um restaurante. Definiremos as assinaturas das funções, mas deixaremos seus corpos vazios para nos concentrar na organização do código, em vez de realmente implementar um restaurante no código.

Na indústria de restaurantes, algumas partes de um restaurante são chamadas de frente da casa e outras de fundos. A frente da casa é onde os clientes estão; é onde os anfitriões acomodam os clientes, os servidores anotam os pedidos e os pagamentos e os bartenders fazem as bebidas. A parte de trás da casa é onde os chefs e cozinheiros trabalham na cozinha, os lava-louças limpam e os gerentes fazem o trabalho administrativo.

Para estruturar nosso crate da mesma maneira que um restaurante real funciona, podemos organizar as funções em módulos aninhados. Crie uma nova biblioteca nomeada restaurant executando cargo new --lib restaurant; em seguida, coloque o código da Listagem 7-1 em src / lib.rs para definir alguns módulos e assinaturas de função.

Nome do arquivo: src / lib.rs

mod frente_da_casa {
    mod hospedagem {
        fn adicionar_a_lista_de_espera() {}

        fn sentar_a_mesa() {}
    }

    mod servindo {
        fn anotar_pedido() {}

        fn servir_pedido() {}

        fn receber_pagamento() {}
    }
}

Listagem 7-1: Um módulo frente_da_casa contendo outros módulos que contêm funções

Definimos um módulo começando com a palavra-chave mod e, em seguida, especificamos o nome do módulo (neste caso, frente_da_casa) e colocamos chaves ao redor do corpo do módulo. Dentro dos módulos, podemos ter outros módulos, como neste caso com os módulos hospedagem e servindo. Os módulos também podem conter definições para outros itens, como structs, enums, constantes, características ou - como na Listagem 7-1 - funções.

Usando módulos, podemos agrupar definições relacionadas e nomear por que estão relacionadas. Os programadores que usam esse código teriam mais facilidade em encontrar as definições que desejam usar, porque eles poderiam navegar no código com base nos grupos, em vez de ter que ler todas as definições. Os programadores que adicionam novas funcionalidades a este código sabem onde colocar o código para manter o programa organizado.

Anteriormente, mencionamos que src / main.rs e src / lib.rs são chamados de raízes de crates. O motivo do nome é que o conteúdo de qualquer um desses dois arquivos forma um módulo nomeado crate na raiz da estrutura do módulo do crate, conhecido como árvore de módulos.

A Listagem 7-2 mostra a árvore do módulo para a estrutura da Listagem 7-1.

crate
 └── frente_da_casa
     ├── hospedagem
     │   ├── adicionar_a_lista_de_espera
     │   └── sentar_a_mesa
     └── servindo
         ├── anotar_pedido
         ├── servir_pedido
         └── receber_pagamento

Listagem 7-2: A árvore do módulo para o código na Listagem 7-1

Esta árvore mostra como alguns dos módulos se aninham uns nos outros (por exemplo, hospedagem aninhado dentro de frente_da_casa). A árvore também mostra que alguns módulos são irmãos entre si, o que significa que eles estão definidos no mesmo módulo (hospedagem e servindo são definidos dentro de frente_da_casa). Para continuar a metáfora da família, se o módulo A está 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 do módulo está enraizada no módulo implícito denominado crate.

A árvore de módulos pode lembrá-lo da árvore de diretórios do sistema de arquivos em seu computador; esta é uma comparação muito apropriada! Assim como os diretórios em um sistema de arquivos, você usa módulos para organizar seu código. E, assim como os arquivos em um diretório, precisamos encontrar nossos módulos.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

Pacotes e Crates

As primeiras partes do sistema de módulos que cobriremos são pacotes e crates. Uma crate é um binário ou biblioteca. A raiz dos crates é um arquivo fonte a partir do qual o compilador Rust inicia e constitui o módulo raiz da sua crate (explicaremos os módulos em detalhes na seção “Definindo Módulos para Controlar o Escopo e Privacidade”). Um pacote é um ou mais crates que fornecem um conjunto de funcionalidades. Um pacote contém um arquivo Cargo.toml que descreve como construir esses crates.

Várias regras determinam o que um pacote pode conter. Um pacote deve conter zero ou um crate de biblioteca e nada mais. Ele pode conter quantos crates binários você desejar, mas deve conter pelo menos um crate (biblioteca ou binária).

Vamos ver o que acontece quando criamos um pacote. Primeiro, inserimos o comando cargo new:

$ cargo new my-project
     Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs

Quando inserimos o comando, Cargo criou um arquivo Cargo.toml, dando-nos um pacote. Olhando para o conteúdo de Cargo.toml, não há menção de src/main.rs porque Cargo segue uma convenção de que src/main.rs é a raiz do crate de uma crate binária com o mesmo nome do pacote. Da mesma forma, Cargo sabe que se o diretório do pacote contém src/lib.rs , o pacote contém um crate de biblioteca com o mesmo nome do pacote e src/lib.rs é a raiz do crate. O Cargo passa os arquivos raiz do crate para rustc construir a biblioteca ou binário.

Aqui, temos um pacote que contém apenas src/main.rs, o que significa que contém apenas um crate binária chamada my-project. Se um pacote contém src/main.rs e src/lib.rs, ele tem dois crates: uma biblioteca e um binário, ambos com o mesmo nome do pacote. Um pacote pode ter vários crates binárias colocando arquivos no diretório src/bin : cada arquivo será um crate binário separado.

Um crate agrupará funcionalidades relacionadas em um escopo para que a funcionalidade seja fácil de compartilhar entre vários projetos. Por exemplo, o crate rand que usamos no Capítulo 2 fornece funcionalidade que gera números aleatórios. Podemos usar essa funcionalidade em nossos próprios projetos, trazendo o crate rand para o escopo do nosso projeto. Todas as funcionalidades fornecidas pelo crate rand estão acessíveis através do nome do crate rand.

Manter a funcionalidade de um crate em seu próprio escopo esclarece se uma funcionalidade específica é definida em nosso crate ou no crate rand e evita conflitos em potencial. Por exemplo, o crate rand fornece uma característica chamada Rng. Também podemos definir uma struct chamada Rng em nosso próprio crate. Como a funcionalidade de um crate tem um namespace em seu próprio escopo, quando adicionamos rand como uma dependência, o compilador não fica confuso sobre a que o nome Rng se refere. Em nosso crate, refere-se a struct Rng que definimos. Iríamos acessar a característica Rng do crate rand como rand::Rng.

Vamos seguir em frente e falar sobre o sistema de módulos!

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

Gerenciando Projetos Crescentes com Pacotes, Crates e Módulos

Conforme você escreve programas grandes, organizar seu código será importante porque manter o controle de todo o programa em sua cabeça se tornará impossível. Ao agrupar a funcionalidade relacionada e separar o código com recursos distintos, você esclarecerá onde encontrar o código que implementa um recurso específico e onde ir para alterar o funcionamento de um recurso.

Os programas que escrevemos até agora estão em um módulo em um arquivo. Conforme um projeto cresce, você pode organizar o código dividindo-o em vários módulos e, em seguida, em vários arquivos. Um pacote pode conter vários crates binários e, opcionalmente, um crate de biblioteca. Conforme um pacote cresce, você pode extrair partes em crates separados que se tornam dependências externas. Este capítulo cobre todas essas técnicas. Para projetos muito grandes de um conjunto de pacotes inter-relacionados que evoluem juntos, o Cargo fornece espaços de trabalho, que abordaremos na seção "Espaços de trabalho de carga" no Capítulo 14.

Além da funcionalidade de agrupamento, o encapsulamento de detalhes de implementação permite reutilizar o código em um nível superior: depois de implementar uma operação, outro código pode chamar esse código por meio da interface pública do código sem saber como a implementação funciona. A maneira como você escreve o código define quais partes são públicas para outro código usar e quais partes são detalhes de implementação privados que você se reserva o direito de alterar. Essa é outra maneira de limitar a quantidade de detalhes que você precisa manter na cabeça.

Um conceito relacionado é o escopo: o contexto aninhado no qual o código é escrito tem um conjunto de nomes que são definidos como "no escopo". Ao ler, escrever e compilar código, os programadores e compiladores precisam saber se um nome específico em um determinado local se refere a uma variável, função, estrutura, enum, módulo, constante ou outro item e o que esse item significa. Você pode criar escopos e alterar quais nomes estão dentro ou fora do escopo. Você não pode ter dois itens com o mesmo nome no mesmo escopo; ferramentas estão disponíveis para resolver conflitos de nomes.

O Rust tem vários recursos que permitem gerenciar a organização do seu código, incluindo quais detalhes são expostos, quais detalhes são privados e quais nomes estão em cada escopo em seus programas. Esses recursos, às vezes chamados coletivamente de sistema de módulo, incluem:

  • Pacotes: um recurso de carga que permite construir, testar e compartilhar crates.
  • Crates: uma árvore de módulos que produz uma biblioteca ou executável.
  • Módulos e use: permitem que você controle a organização, o escopo e a privacidade dos caminhos.
  • Caminhos: uma maneira de nomear um item, como uma estrutura, função ou módulo.

Neste capítulo, vamos cobrir todos esses recursos, discutir como eles interagem e explicar como usá-los para gerenciar o escopo. No final, você deve ter um conhecimento sólido do sistema de módulos e ser capaz de trabalhar com escopos como um profissional!

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

terça-feira, 6 de abril de 2021

Fluxo de controle conciso com if let

A sintaxe if let permite combinar ife let de uma forma menos prolixa para lidar com valores que correspondem a um padrão, enquanto ignora o resto. Considere o programa na Listagem 6-6 que corresponde a um valor Option<u8>, mas só deseja executar o código se o valor for 3.

fn main() {
    let algum_valor_u8 = Some(0u8);
    match algum_valor_u8 {
        Some(3) => println!("tres"),
        _ => (),
    }
}

Listagem 6-6: match que só se preocupa com a execução do código quando o valor for Some(3).

Queremos fazer algo com a correspondência Some(3), mas nada fazer com nenhum outro valor Some<u8> ou valor None. Para satisfazer a expressão match, temos que adicionar, _ => ()após o processamento, apenas uma variante, que é muito código clichê para adicionar.

Em vez disso, poderíamos escrever isso de forma mais curta usando if let. O código a seguir se comporta da mesma forma que o match da Listagem 6-6:

fn main() {
    let algum_valor_u8 = Some(0u8);
    if let Some(3) = algum_valor_u8 {
        println!("tres");
    }
}

A sintaxe if let assume um padrão e uma expressão separados por um sinal de igual. Funciona da mesma forma que match, onde a expressão é dada a match e o padrão é seu primeiro braço.

Usar if let significa menos digitação, menos indentação e menos código clichê. No entanto, você perde a verificação exaustiva que match impõe. A escolha entre match e if let depende do que você está fazendo em sua situação particular e se ganhar concisão é uma compensação apropriada para perder uma verificação exaustiva.

Em outras palavras, você pode pensar em if let como um açúcar de sintaxe para um match que executa o código quando o valor corresponde a um padrão e, em seguida, ignora todos os outros valores.

Podemos incluir um else com um if let. O bloco de código que acompanha o else é o mesmo que o bloco de código que acompanha o caso _ na expressão match que é equivalente a if let e else. Lembre-se da definição do enum Coin na Listagem 6-4, em que a variante Quarter também continha um valor UsState. Se quiséssemos contar todas as moedas que não são um quarto que vemos e, ao mesmo tempo, anunciar o estado dos quartos, poderíamos fazer isso com uma expressão match como esta:

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --recorte--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn main() {
    let coin = Coin::Penny;
    let mut count = 0;
    match coin {
        Coin::Quarter(state) => println!("quarter do Estado {:?}!", state),
        _ => count += 1,
    }
}

Ou podemos usar uma expressão if let e else assim:

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --recorte--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn main() {
    let coin = Coin::Penny;
    let mut count = 0;
    if let Coin::Quarter(state) = coin {
        println!("quarter do Estado {:?}!", state);
    } else {
        count += 1;
    }
}

Se você tiver uma situação em que seu programa tenha uma lógica muito prolixa para ser expressa usando um match, lembre-se de que if let também está em sua caixa de ferramentas Rust.

Resumo

Agora cobrimos como usar enums para criar tipos personalizados que podem ser um de um conjunto de valores enumerados. Mostramos como o tipo Option<T> da biblioteca padrão ajuda você a usar o sistema de tipos para evitar erros. Quando os valores enum têm dados dentro deles, você pode usar match ou if let para extrair e usar esses valores, dependendo de quantos casos você precisa tratar.

Seus programas Rust agora podem expressar conceitos em seu domínio usando structs e enums. A criação de tipos personalizados para usar em sua API garante a segurança do tipo: o compilador fará com que suas funções obtenham apenas valores do tipo que cada função espera.

Para fornecer aos seus usuários uma API bem organizada, fácil de usar e que apenas exponha exatamente o que os usuários precisarão, vamos agora nos módulos do Rust.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença