Trazendo caminhos para o escopo com a palavra-chave use
Ter de escrever os caminhos completos para chamar funções pode parecer
inconveniente e repetitivo. Na Listagem 7-7, independentemente de escolhermos o
caminho absoluto ou relativo para a função add_to_waitlist, toda vez que
queríamos chamar add_to_waitlist precisávamos especificar também
front_of_house e hosting. Felizmente, há uma forma de simplificar esse
processo: podemos criar um atalho para um caminho com a palavra-chave use uma
única vez e então usar o nome mais curto em qualquer outro lugar daquele
escopo.
Na Listagem 7-11, trazemos o módulo crate::front_of_house::hosting para o
escopo da função eat_at_restaurant, de modo que só precisamos especificar
hosting::add_to_waitlist para chamar a função add_to_waitlist dentro de
eat_at_restaurant.
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();
}
useAdicionar use e um caminho dentro de um escopo é semelhante a criar um link
simbólico no sistema de arquivos. Ao adicionar
use crate::front_of_house::hosting na raiz da crate, hosting passa a ser um
nome válido naquele escopo, como se o módulo hosting tivesse sido definido na
raiz da crate. Caminhos trazidos para o escopo com use também respeitam as
regras de privacidade, como quaisquer outros caminhos.
Observe que use cria o atalho apenas para o escopo específico em que aparece.
A Listagem 7-12 move a função eat_at_restaurant para um novo módulo filho
chamado customer, que então se torna um escopo diferente daquele da
instrução use, e por isso o corpo da função não compilará.
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();
}
}
use se aplica apenas ao escopo em que ela foi declaradaO erro do compilador mostra que o atalho já não se aplica dentro do módulo
customer:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of unresolved module or unlinked crate `hosting`
|
= help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
|
10 + use crate::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
For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
Observe também que há um aviso de que use não está mais sendo usada em seu
escopo! Para corrigir esse problema, mova a use para dentro do módulo
customer também, ou então faça referência ao atalho presente no módulo pai
com super::hosting dentro do módulo filho customer.
Criando caminhos use idiomáticos
Na Listagem 7-11, você talvez tenha se perguntado por que especificamos
use crate::front_of_house::hosting e então chamamos
hosting::add_to_waitlist em eat_at_restaurant, em vez de especificar o
caminho de use até a função add_to_waitlist para obter o mesmo resultado,
como na Listagem 7-13.
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();
}
add_to_waitlist para o escopo com use, de uma maneira pouco idiomáticaEmbora a Listagem 7-11 e a Listagem 7-13 façam a mesma coisa, a Listagem 7-11
é a forma 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 precisamos
especificar o módulo pai ao chamar a função. Especificar esse módulo pai ao
chamar a função deixa claro que ela não foi definida localmente, enquanto ainda
minimiza a repetição do caminho completo. O código da Listagem 7-13 torna menos
claro onde add_to_waitlist está definida.
Por outro lado, ao trazer structs, enums e outros itens para o escopo com
use, o idiomático é especificar o caminho completo. A Listagem 7-14 mostra a
forma idiomática de trazer a struct HashMap da biblioteca padrão para o
escopo de uma crate binária.
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
HashMap para o escopo de forma idiomáticaNão há um motivo técnico forte por trás desse idiomatismo: é simplesmente a convenção que surgiu, e as pessoas se acostumaram a ler e escrever código Rust dessa maneira.
A exceção a esse idiomatismo aparece quando trazemos para o escopo dois itens
com o mesmo nome por meio de instruções use, porque Rust não permite isso. A
Listagem 7-15 mostra como trazer para o escopo dois tipos Result que têm o
mesmo nome, mas módulos-pai diferentes, e como se referir a eles.
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
Como você pode ver, usar os módulos-pai distingue os dois tipos Result. Se,
em vez disso, tivéssemos especificado 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 estamos nos referindo ao usar Result.
Fornecendo novos nomes com a palavra-chave as
Existe outra solução para o problema de trazer para o mesmo escopo dois tipos
com o mesmo nome usando use: depois do 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 da Listagem 7-15, renomeando um dos dois tipos Result
com as.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
asNa 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. Tanto a Listagem 7-15 quanto a Listagem 7-16
são consideradas idiomáticas, então a escolha fica a seu critério!
Reexportando nomes com pub use
Quando trazemos um nome para o escopo com a palavra-chave use, esse nome fica
privado ao escopo para o qual o importamos. Para permitir que código fora desse
escopo se refira a esse nome como se ele tivesse sido definido ali, podemos
combinar pub e use. Essa técnica é chamada de reexportação, porque
estamos trazendo um item para o escopo, mas também tornando esse item
disponível para que outras pessoas o tragam para seus próprios escopos.
A Listagem 7-17 mostra o código da Listagem 7-11 com use no módulo raiz
alterado para pub use.
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();
}
pub useAntes dessa mudança, código externo teria de chamar a função
add_to_waitlist usando o caminho
restaurant::front_of_house::hosting::add_to_waitlist(), o que também exigiria
que o módulo front_of_house fosse marcado como pub. Agora que esse pub use
reexportou o módulo hosting a partir do módulo raiz, código externo pode usar
o caminho restaurant::hosting::add_to_waitlist().
A reexportação é útil quando a estrutura interna do seu código difere da forma
como pessoas chamando o seu código pensam no domínio do problema. Por exemplo,
nesta metáfora do restaurante, as pessoas que trabalham no restaurante pensam
em termos como “front of house” e “back of house”. Mas clientes visitando um
restaurante provavelmente não pensariam nas partes do restaurante dessa forma.
Com pub use, podemos escrever nosso código com uma estrutura e expor outra
estrutura. Isso faz com que a biblioteca fique bem organizada tanto para quem
trabalha nela quanto para quem a utiliza. Veremos outro exemplo de pub use e
como isso afeta a documentação da crate em “Exportando uma API pública
conveniente”, no Capítulo 14.
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
no nosso projeto, adicionamos esta linha a Cargo.toml:
rand = "0.8.5"
Adicionar rand como dependência em Cargo.toml informa ao Cargo para baixar
o pacote rand e quaisquer dependências dele a partir de
crates.io, além de disponibilizar rand ao nosso
projeto.
Depois, para trazer definições de rand para o escopo do nosso pacote,
adicionamos uma linha use começando com o nome da crate, rand, e listamos
os itens que queríamos trazer para o escopo. Lembre-se de que, em
“Gerando um número aleatório” no Capítulo 2, trouxemos a
trait Rng para o escopo e chamamos a função rand::thread_rng:
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
Membros da comunidade Rust disponibilizaram muitos pacotes em
crates.io, e trazer qualquer um deles para o seu pacote
envolve esses mesmos passos: listá-lo no arquivo Cargo.toml do pacote e usar
use para trazer itens de suas crates para o escopo.
Observe que a biblioteca padrão std também é uma crate externa ao nosso
pacote. Como a biblioteca padrão acompanha 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:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
Este é um caminho absoluto começando com std, o nome da crate da biblioteca
padrão.
Usando caminhos aninhados para limpar listas de use
Se estivermos usando vários itens definidos na mesma crate ou no mesmo módulo,
listar cada item em sua própria linha pode ocupar bastante espaço vertical em
nossos arquivos. Por exemplo, estas duas instruções use que tínhamos no jogo
de adivinhação da Listagem 2-4 trazem itens de std para o escopo:
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
Em vez disso, podemos usar caminhos aninhados para trazer os mesmos itens para o escopo em uma única linha. Fazemos isso especificando a parte comum do caminho, seguida por dois pontos duplos, e depois chaves em torno de uma lista das partes do caminho que diferem, como mostra a Listagem 7-18.
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
Em programas maiores, trazer muitos itens da mesma crate ou do mesmo módulo
para o escopo usando caminhos aninhados pode reduzir bastante a quantidade de
instruções use separadas necessárias!
Podemos usar um caminho aninhado em qualquer nível de um caminho, o que é útil
ao combinar duas instruções use que compartilham um subcaminho. Por exemplo,
a Listagem 7-19 mostra duas instruções use: uma que traz std::io para o
escopo e outra que traz std::io::Write para o escopo.
use std::io;
use std::io::Write;
use em que uma é um subcaminho da outraA parte comum desses dois caminhos é std::io, e esse é o primeiro caminho
inteiro. Para mesclar esses dois caminhos em uma só instrução use, podemos
usar self no caminho aninhado, como mostra a Listagem 7-20.
use std::io::{self, Write};
useEssa linha traz std::io e std::io::Write para o escopo.
Importando itens com o operador glob
Se quisermos trazer para o escopo todos os itens públicos definidos em um
caminho, podemos especificar esse caminho seguido pelo operador glob *:
#![allow(unused)]
fn main() {
use std::collections::*;
}
Essa instrução use traz todos os itens públicos definidos em
std::collections para o escopo atual. Tenha cuidado ao usar o operador glob!
Ele pode dificultar descobrir quais nomes estão no escopo e onde um nome usado
no programa foi definido. Além disso, se a dependência mudar suas definições,
aquilo que foi importado também muda, o que pode levar a erros de compilação ao
atualizar a dependência, por exemplo se ela adicionar uma definição com o mesmo
nome de uma definição sua no mesmo escopo.
O operador glob é frequentemente usado em testes para trazer tudo que está
sendo testado para dentro do módulo tests; falaremos disso em “Como escrever
testes” no Capítulo 11. O operador glob também é
às vezes usado como parte do padrão prelude; veja a documentação da biblioteca
padrão para mais
informações sobre esse padrão.