Введение путей в область видимости с помощью ключевого слова use
Необходимость каждый раз полностью записывать пути для вызова функций может
казаться неудобной и повторяющейся. В листинге 7-7, независимо от того,
выбрали ли мы абсолютный или относительный путь к функции add_to_waitlist,
каждый раз при вызове add_to_waitlist нам также приходилось указывать
front_of_house и hosting. К счастью, есть способ упростить этот процесс:
мы можем один раз создать сокращение для пути с помощью ключевого слова use,
а затем использовать более короткое имя во всей остальной области видимости.
В листинге 7-11 мы вводим модуль crate::front_of_house::hosting в область
видимости функции eat_at_restaurant, чтобы для вызова функции
add_to_waitlist в eat_at_restaurant нам нужно было указывать только
hosting::add_to_waitlist.
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();
}
useДобавление use и пути в область видимости похоже на создание символической
ссылки в файловой системе. Добавляя use crate::front_of_house::hosting в
корень крейта, мы делаем hosting допустимым именем в этой области видимости,
как если бы модуль hosting был определен в корне крейта. Пути, введенные в
область видимости с помощью use, также проверяются на приватность, как и
любые другие пути.
Обратите внимание, что use создает сокращение только для той конкретной
области видимости, в которой он находится. Листинг 7-12 перемещает функцию
eat_at_restaurant в новый дочерний модуль с именем customer, который
является областью видимости, отличной от инструкции use, поэтому тело
функции не скомпилируется.
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 применяется только в той области видимости, где она находится.Ошибка компилятора показывает, что сокращение больше не действует внутри
модуля 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
Обратите внимание, что также есть предупреждение: use больше не используется
в своей области видимости! Чтобы исправить эту проблему, переместите use
также внутрь модуля customer или обращайтесь к сокращению в родительском
модуле через super::hosting внутри дочернего модуля customer.
Создание идиоматичных путей use
В листинге 7-11 вы могли задаться вопросом, почему мы указали use crate::front_of_house::hosting, а затем вызвали
hosting::add_to_waitlist в eat_at_restaurant, вместо того чтобы указать
путь use до самой функции add_to_waitlist и получить тот же результат, как
в листинге 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 в область видимости с помощью use, что не является идиоматичнымХотя листинг 7-11 и листинг 7-13 выполняют одну и ту же задачу, листинг 7-11
является идиоматичным способом ввести функцию в область видимости с помощью
use. Введение родительского модуля функции в область видимости с помощью
use означает, что при вызове функции нужно указывать родительский модуль.
Указание родительского модуля при вызове функции ясно показывает, что функция
не определена локально, но при этом все еще сокращает повторение полного пути.
В коде листинга 7-13 неясно, где определена add_to_waitlist.
С другой стороны, при введении структур, enum и других элементов в область
видимости с помощью use идиоматично указывать полный путь. Листинг 7-14
показывает идиоматичный способ ввести структуру HashMap из стандартной
библиотеки в область видимости бинарного крейта.
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
HashMap в область видимостиЗа этой идиомой нет строгой причины: это просто сложившееся соглашение, и люди привыкли читать и писать код Rust именно так.
Исключение из этой идиомы возникает, если мы вводим в область видимости два
элемента с одним и тем же именем с помощью инструкций use, потому что Rust не
разрешает этого. Листинг 7-15 показывает, как ввести в область видимости два
типа Result, у которых одинаковое имя, но разные родительские модули, и как
к ним обращаться.
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
Как видите, использование родительских модулей различает два типа Result.
Если бы вместо этого мы указали use std::fmt::Result и use std::io::Result,
в одной области видимости оказалось бы два типа Result, и Rust не знал бы,
какой из них мы имеем в виду при использовании Result.
Предоставление новых имен с помощью ключевого слова as
Есть еще одно решение проблемы введения двух типов с одинаковым именем в одну
область видимости с помощью use: после пути можно указать as и новое
локальное имя, или псевдоним, для типа. Листинг 7-16 показывает другой
способ записать код из листинга 7-15, переименовав один из двух типов Result
с помощью as.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
asВо второй инструкции use мы выбрали новое имя IoResult для типа
std::io::Result, которое не будет конфликтовать с Result из std::fmt,
также введенным в область видимости. Листинги 7-15 и 7-16 считаются
идиоматичными, поэтому выбор за вами!
Повторный экспорт имен с помощью pub use
Когда мы вводим имя в область видимости с помощью ключевого слова use, это
имя является приватным для области видимости, в которую мы его импортировали.
Чтобы код вне этой области видимости мог обращаться к этому имени так, как
если бы оно было определено в этой области, можно объединить pub и use.
Этот прием называется повторным экспортом, потому что мы вводим элемент в
область видимости и одновременно делаем его доступным для другого кода, чтобы
тот мог вводить этот элемент в свою область видимости.
Листинг 7-17 показывает код из листинга 7-11, где use в корневом модуле
заменен на 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 useДо этого изменения внешний код должен был бы вызывать функцию add_to_waitlist
по пути restaurant::front_of_house::hosting::add_to_waitlist(), что также
требовало бы пометить модуль front_of_house как pub. Теперь, когда этот
pub use повторно экспортировал модуль hosting из корневого модуля, внешний
код может вместо этого использовать путь restaurant::hosting::add_to_waitlist().
Повторный экспорт полезен, когда внутренняя структура вашего кода отличается
от того, как программисты, вызывающие ваш код, воспринимали бы предметную
область. Например, в этой ресторанной метафоре люди, управляющие рестораном,
мыслят категориями «зал» и «служебная часть». Но клиенты, посещающие ресторан,
скорее всего, не будут думать о частях ресторана в этих терминах. С помощью
pub use мы можем писать код с одной структурой, но предоставлять внешнему
коду другую структуру. Это делает нашу библиотеку хорошо организованной и для
программистов, работающих над библиотекой, и для программистов, вызывающих
библиотеку. Мы рассмотрим еще один пример pub use и то, как он влияет на
документацию крейта, в разделе «Экспорт удобного публичного API» главы 14.
Использование внешних пакетов
В главе 2 мы программировали проект игры в угадывание, который использовал
внешний пакет rand для получения случайных чисел. Чтобы использовать rand
в нашем проекте, мы добавили эту строку в Cargo.toml:
rand = "0.8.5"
Добавление rand как зависимости в Cargo.toml говорит Cargo загрузить пакет
rand и любые его зависимости с crates.io и сделать
rand доступным нашему проекту.
Затем, чтобы ввести определения rand в область видимости нашего пакета, мы
добавили строку use, начинающуюся с имени крейта, rand, и перечислили
элементы, которые хотели ввести в область видимости. Вспомните, что в разделе
«Генерация случайного числа» главы 2 мы ввели трейт
Rng в область видимости и вызвали функцию 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}");
}
Участники сообщества Rust сделали множество пакетов доступными на
crates.io, и подключение любого из них к вашему пакету
включает те же шаги: перечислить их в файле Cargo.toml вашего пакета и
использовать use, чтобы ввести элементы из их крейтов в область видимости.
Обратите внимание, что стандартная библиотека std также является крейтом,
внешним по отношению к нашему пакету. Поскольку стандартная библиотека
поставляется вместе с языком Rust, нам не нужно менять Cargo.toml, чтобы
включить std. Но нам нужно обращаться к ней с помощью use, чтобы ввести
элементы оттуда в область видимости нашего пакета. Например, для HashMap мы
использовали бы такую строку:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
Это абсолютный путь, начинающийся с std, имени крейта стандартной
библиотеки.
Использование вложенных путей для очистки списков use
Если мы используем несколько элементов, определенных в одном и том же крейте
или модуле, перечисление каждого элемента в отдельной строке может занимать
много вертикального пространства в наших файлах. Например, эти две инструкции
use, которые были у нас в игре в угадывание в листинге 2-4, вводят элементы
из std в область видимости:
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!"),
}
}
Вместо этого мы можем использовать вложенные пути, чтобы ввести те же элементы в область видимости одной строкой. Мы делаем это, указывая общую часть пути, затем два двоеточия, а затем в фигурных скобках список частей путей, которые различаются, как показано в листинге 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!"),
}
}
В больших программах введение множества элементов из одного и того же крейта
или модуля в область видимости с помощью вложенных путей может значительно
уменьшить количество отдельных инструкций use!
Мы можем использовать вложенный путь на любом уровне пути, что полезно при
объединении двух инструкций use, у которых есть общая часть пути. Например,
листинг 7-19 показывает две инструкции use: одна вводит std::io в область
видимости, а другая вводит std::io::Write в область видимости.
use std::io;
use std::io::Write;
use, где одна является частью пути другойОбщая часть этих двух путей – std::io, и это полный первый путь. Чтобы
объединить эти два пути в одну инструкцию use, мы можем использовать self
во вложенном пути, как показано в листинге 7-20.
use std::io::{self, Write};
useЭта строка вводит std::io и std::io::Write в область видимости.
Импорт элементов с помощью glob-оператора
Если мы хотим ввести в область видимости все публичные элементы,
определенные в пути, мы можем указать этот путь, за которым следует
glob-оператор *:
#![allow(unused)]
fn main() {
use std::collections::*;
}
Эта инструкция use вводит все публичные элементы, определенные в
std::collections, в текущую область видимости. Будьте осторожны при
использовании glob-оператора! Glob может затруднить понимание того, какие
имена находятся в области видимости и где было определено имя, используемое в
вашей программе. Кроме того, если зависимость изменит свои определения,
изменится и то, что вы импортировали; например, это может привести к ошибкам
компилятора при обновлении зависимости, если зависимость добавит определение с
тем же именем, что и ваше определение в той же области видимости.
Glob-оператор часто используется при тестировании, чтобы ввести все, что
тестируется, в модуль tests; мы поговорим об этом в разделе «Как писать
тесты» главы 11. Glob-оператор также иногда
используется как часть шаблона прелюдии: дополнительную информацию об этом
шаблоне смотрите в документации стандартной
библиотеки.