Trazendo Caminhos para o Escopo com a Palavra-chave Use

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Bem-vindo a Bringing Paths Into Scope With the Use Keyword. Este laboratório faz parte do Rust Book. Você pode praticar suas habilidades em Rust no LabEx.

Neste laboratório, aprenderemos como trazer caminhos para o escopo usando a palavra-chave use para criar atalhos para chamar funções e módulos.

Trazendo Caminhos para o Escopo com a Palavra-chave use

Ter que escrever os caminhos para chamar funções pode parecer inconveniente e repetitivo. Na Listagem 7-7, independentemente de termos escolhido o caminho absoluto ou relativo para a função add_to_waitlist, toda vez que queríamos chamar add_to_waitlist, tínhamos que especificar front_of_house e hosting também. Felizmente, existe uma maneira de simplificar esse processo: podemos criar um atalho para um caminho com a palavra-chave use uma vez e, em seguida, usar o nome mais curto em todos os outros lugares no escopo.

Na Listagem 7-11, trazemos o módulo crate::front_of_house::hosting para o escopo da função eat_at_restaurant, para que só tenhamos que especificar hosting::add_to_waitlist para chamar a função add_to_waitlist em eat_at_restaurant.

Nome do arquivo: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

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::front_of_house::hosting na raiz do crate, hosting agora é um nome válido nesse escopo, como se o módulo hosting tivesse sido definido na raiz do crate. Caminhos trazidos para o escopo com use também verificam a privacidade, como quaisquer outros caminhos.

Observe que use apenas cria o atalho para o escopo específico em que o use ocorre. A Listagem 7-12 move a função eat_at_restaurant para um novo módulo filho chamado customer, que então é um escopo diferente da instrução use, portanto, o corpo da função não compilará.

Nome do arquivo: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}

Listagem 7-12: Uma instrução use só se aplica no escopo em que está.

O erro do compilador mostra que o atalho não se aplica mais dentro do módulo customer:

error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of undeclared crate or module `hosting`

warning: unused import: `crate::front_of_house::hosting`
 --> src/lib.rs:7:5
  |
7 | use crate::front_of_house::hosting;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

Observe que também há um aviso de que o use não é mais usado em seu escopo! Para corrigir esse problema, mova o use para dentro do módulo customer também, ou referencie o atalho no módulo pai com super::hosting dentro do módulo filho customer.

Criando Caminhos use Idiomáticos

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

Nome do arquivo: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
}

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

Embora tanto a Listagem 7-11 quanto a Listagem 7-13 realizem a mesma tarefa, a Listagem 7-11 é a maneira idiomática de trazer uma função para o escopo com use. Trazer o módulo pai da função para o escopo com use significa que temos que especificar o módulo pai ao chamar a função. Especificar o módulo pai ao chamar a função deixa claro que a função não é definida localmente, ao mesmo tempo em que minimiza a repetição do caminho completo. O código na Listagem 7-13 não deixa claro onde add_to_waitlist é 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 struct 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 maneira idiomática

Não há uma 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 instruções use, porque o 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 se referir a eles.

Nome do arquivo: src/lib.rs

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

fn function1() -> fmt::Result {
    --snip--
}

fn function2() -> io::Result<()> {
    --snip--
}

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

Como você pode ver, usar os 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 o Rust não saberia a qual nos referíamos quando usássemos Result.

Fornecendo Novos Nomes com a Palavra-chave as

Existe outra solução para o problema de trazer dois tipos com o mesmo nome para o mesmo escopo com use: após o caminho, podemos especificar as e um novo nome local, ou alias (apelido), 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 function1() -> Result {
    --snip--
}

fn function2() -> IoResult<()> {
    --snip--
}

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

Na segunda instruçã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 trouxemos para o 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 trazemos um nome para o 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 ele tivesse sido definido no escopo desse código, podemos combinar pub e use. Essa técnica é chamada de reexportação (re-exporting) porque estamos trazendo um item para o escopo, mas também tornando esse item disponível para que outros o tragam para seu escopo.

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

Nome do arquivo: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

Listagem 7-17: Tornando um nome disponível para qualquer código usar de um novo escopo com pub use

Antes dessa alteração, o código externo teria que chamar a função add_to_waitlist usando o caminho restaurant::front_of_house::hosting::add_to_waitlist(). Agora que este pub use reexportou o módulo hosting do módulo raiz, o código externo pode usar o caminho restaurant::hosting::add_to_waitlist() em vez disso.

A reexportação é útil quando a estrutura interna do 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 administram o restaurante pensam em "frente da casa" e "fundo 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. Fazer isso torna nossa biblioteca bem organizada para programadores que trabalham na biblioteca e programadores que chamam a biblioteca. Veremos outro exemplo de pub use e como ele afeta a documentação do seu crate em "Exportando uma API Pública Conveniente com pub use".

Usando Pacotes Externos

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

Nome do arquivo: Cargo.toml

rand = "0.8.5"

Adicionar rand como uma dependência em Cargo.toml diz ao Cargo para baixar o pacote rand e quaisquer dependências de https://crates.io, e tornar rand disponível para nosso projeto.

Então, para trazer as definições de rand para o escopo do 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. Recorde que em "Gerando um Número Aleatório", trouxemos o trait Rng para o escopo e chamamos a função rand::thread_rng:

use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);
}

Membros da comunidade Rust disponibilizaram muitos pacotes em https://crates.io, e puxar qualquer um deles para 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 que é externo ao nosso pacote. Como a biblioteca padrão é fornecida com a linguagem Rust, não precisamos alterar Cargo.toml para incluir std. Mas precisamos nos referir a ela com use para trazer itens de lá para o escopo do nosso pacote. Por exemplo, com HashMap usaríamos esta linha:

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 use Grandes

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

Nome do arquivo: src/main.rs

--snip--
use std::cmp::Ordering;
use std::io;
--snip--

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 dois pontos, e então chaves ao redor de uma lista das partes dos caminhos que diferem, como mostrado na Listagem 7-18.

Nome do arquivo: src/main.rs

--snip--
use std::{cmp::Ordering, io};
--snip--

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

Em programas maiores, trazer muitos itens para o 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 em 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 onde 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ção use, podemos usar self no caminho aninhado, como 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 para o 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 *:

use std::collections::*;

Esta declaração use traz todos os itens públicos definidos em std::collections para o escopo atual. Tenha cuidado ao usar o operador glob! O glob pode dificultar a identificação de 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 sob teste para o módulo tests; falaremos sobre isso em "Como Escrever Testes". O operador glob também é usado às vezes como parte do padrão prelude: consulte a documentação da biblioteca padrão para obter mais informações sobre esse padrão.

Resumo

Parabéns! Você concluiu o laboratório Trazendo Caminhos para o Escopo com a Palavra-chave Use. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.