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

0 comentários:

Postar um comentário