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