Добавление путей в область видимости ключевым словом use
Необходимость записывать пути к функциям вызова может показаться неудобной и многословной. В Листинге 7-7 независимо от того, выбирали ли мы абсолютный или относительный путь к функции add_to_waitlist
, каждый раз, когда мы хотели вызвать add_to_waitlist
, нам приходилось также указывать front_of_house
и hosting
. К счастью, есть способ упростить этот процесс: мы можем один раз создать псевдоним на путь при помощи ключевого слова use
, а затем использовать более короткое имя везде в области видимости.
В Листинге 7-11 мы подключили модуль crate::front_of_house::hosting
в область видимости функции eat_at_restaurant
, поэтому нам достаточно только указать hosting::add_to_waitlist
для вызова функции add_to_waitlist
внутри 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();
}
Добавление use
и пути в область видимости аналогично созданию символической ссылки в файловой системе. С добавлением use crate::front_of_house::hosting
в корневой модуль крейта, hosting
становится допустимым именем в этой области, как если бы модуль hosting
был определён в корне крейта. Пути, подключённые в область видимости с помощью use
, также проверяются на приватность, как и любые другие пути.
Обратите внимание, что 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();
}
}
Ошибка компилятора показывает, что данный псевдоним не может использоваться в модуле customer
:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
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`
|
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();
}
Хотя Листинги 7-11 и 7-13 выполняют одну и ту же задачу, Листинг 7-11 является идиоматическим способом подключения функции в область видимости с помощью use
. Подключение родительского модуля функции в область видимости при помощи use
означает, что мы должны указывать родительский модуль при вызове функции. Указание родительского модуля при вызове функции даёт понять, что функция не определена локально, но в то же время сводя к минимуму повторение полного пути. В коде Листинга 7-13 не ясно, где именно определена add_to_waitlist
.
С другой стороны, при подключении структур, перечислений и других элементов использованием use
, идиоматически правильным будет указывать полный путь. Листинг 7-14 показывает идиоматический способ подключения структуры стандартной библиотеки HashMap
в область видимости бинарного крейта.
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
За этой идиомой нет веской причины: это просто соглашение, которое появилось само собой. Люди привыкли читать и писать код на Rust таким образом.
Исключением из этой идиомы является случай, когда мы подключаем два элемента с одинаковыми именами в область видимости используя инструкцию use
— Rust просто не позволяет этого сделать. Листинг 7-15 показывает, как подключить в область действия два типа с одинаковыми именами Result
, но из разных родительских модулей, и как на них ссылаться.
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --код сокращён--
Ok(())
}
fn function2() -> io::Result<()> {
// --код сокращён--
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 {
// --код сокращён--
Ok(())
}
fn function2() -> IoResult<()> {
// --код сокращён--
Ok(())
}
Во второй инструкции 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();
}
До этого изменения внешний код должен был вызывать функцию add_to_waitlist
, используя путь restaurant::front_of_house::hosting::add_to_waitlist()
. Теперь, поскольку это объявление pub use
реэкспортировало модуль hosting
из корневого модуля, внешний код теперь может использовать вместо него путь restaurant::hosting::add_to_waitlist()
.
Реэкспорт полезен, когда внутренняя структура вашего кода отличается от того, как программисты, вызывающие ваш код, думают о предметной области. Например, по аналогии с рестораном люди, управляющие им, думают о "front of house" и "back of house". Но клиенты, посещающие ресторан, вероятно, не будут думать о частях ресторана в таких терминах. Используя pub use
, мы можем написать наш код с одной структурой, но сделать общедоступной другую структуру. Благодаря этому наша библиотека хорошо организована и для программистов, работающих над библиотекой, и для программистов, вызывающих библиотеку. Мы рассмотрим ещё один пример pub use
и его влияние на документацию вашего крейта в разделе "Экспорт удобного общедоступного API с pub use
" Главы 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!("Угадайте число!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("Загаданное число: {secret_number}");
println!("Введите свою догадку.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Не удалось прочесть ввод.");
println!("Вы предположили: {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;
// --код сокращён--
use std::cmp::Ordering;
use std::io;
// --код сокращён--
fn main() {
println!("Угадайте число!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("Загаданное число: {secret_number}");
println!("Введите свою догадку.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Не удалось прочесть ввод.");
println!("Вы предположили: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Слишком маленькое!"),
Ordering::Greater => println!("Слишком большое!"),
Ordering::Equal => println!("Вы победили!"),
}
}
Вместо этого, мы можем использовать перечисление путей, чтобы добавить эти элементы в область видимости одной строкой. Это делается, как показано в Листинге 7-18: указывается общая часть пути, за которой следуют два двоеточия, а затем фигурные скобки вокруг списка тех частей пути, которые отличаются.
use rand::Rng;
// --код сокращён--
use std::{cmp::Ordering, io};
// --код сокращён--
fn main() {
println!("Угадайте число!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("Загаданное число: {secret_number}");
println!("Введите свою догадку.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Не удалось прочесть ввод.");
let guess: u32 = guess.trim().parse().expect("Пожалуйста, введите число!");
println!("Вы предположили: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Слишком маленькое!"),
Ordering::Greater => println!("Слишком большое!"),
Ordering::Equal => println!("Вы победили!"),
}
}
В больших программах, подключение множества элементов из одного пакета или модуля с использованием перечисления путей может значительно сократить количество необходимых отдельных инструкций use
!
Использовать перечисление путей можно на любом уровне, что полезно при объединении двух инструкций use
, которые имеют общую часть пути. Например, в Листинге 7-19 показаны две инструкции use
: одна в область видимости подключает std::io
, а другая — std::io::Write
.
use std::io;
use std::io::Write;
Общей частью этих двух путей является std::io
, и это полный первый путь. Чтобы объединить эти два пути в одной инструкции use
, мы можем использовать ключевое слово self
в перечислении путей, как показано в Листинге 7-20.
use std::io::{self, Write};
Эта строка подключает std::io
и std::io::Write
в область видимости.
Оператор *
If we want to bring all public items defined in a path into scope, we can specify that path followed by the *
glob operator:
#![allow(unused)] fn main() { use std::collections::*; }
Эта инструкция use
подключает все открытые элементы из модуля std::collections
в текущую область видимости. Будьте осторожны при использовании оператора *
! Он может усложнить понимание, какие имена находятся в области видимости и где были определены имена, используемые в вашей программе.
Оператор *
часто используется при тестировании для подключения всего, что есть в модуле tests
; мы поговорим об этом в разделе "Как писать тесты" Главы 11. Оператор *
также иногда используется как часть шаблона prelude: обратитесь к документации стандартной библиотеки для получения дополнительной информации.