Подключение путей ключевым словом use
Выписывать полные пути при каждом вызове функции неудобно и однообразно. В
листинге 7-7, какой бы путь к 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]: cannot find module or crate `hosting` in this scope
--> 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)]` (part of `#[warn(unused)]`) 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 делают одно и то же, но идиоматичный способ подключить
функцию через use — как в листинге 7-11. Если подключить родительский модуль
функции, то при вызове придётся указывать этот модуль. А это сразу показывает,
что функция определена не здесь, и при этом полный путь повторять не нужно.
По коду из листинга 7-13 непонятно, где определена add_to_waitlist.
А вот структуры, перечисления и прочие элементы через 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 {
// --пропуск--
Ok(())
}
fn function2() -> io::Result<()> {
// --пропуск--
Ok(())
}
Как видите, родительские модули позволяют различить два типа Result. Если
бы мы написали use std::fmt::Result и use std::io::Result, в одной области
видимости оказались бы два типа Result, и Rust не понял бы, какой из них мы
имеем в виду, когда пишем Result.
Новые имена с помощью as
Есть и другое решение проблемы двух одноимённых типов в одной области
видимости: после пути можно написать as и новое локальное имя — псевдоним
(alias) — для типа. В листинге 7-16 показан другой вариант кода из листинга
7-15: один из двух типов Result переименован с помощью as.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --пропуск--
Ok(())
}
fn function2() -> IoResult<()> {
// --пропуск--
Ok(())
}
asВо второй инструкции use мы дали типу std::io::Result новое имя —
IoResult. Оно не конфликтует с Result из std::fmt, который мы тоже
подключили. И листинг 7-15, и листинг 7-16 считаются идиоматичными, так что
выбирайте сами!
Реэкспорт имён с помощью pub use
Когда мы подключаем имя через use, оно приватно для той области видимости,
куда мы его импортировали. Чтобы код за пределами этой области мог
обращаться к имени так, будто оно определено в ней, можно объединить pub и
use. Такой приём называется реэкспортом (re-exporting): мы подключаем
элемент в свою область видимости и одновременно делаем его доступным другим,
чтобы они могли подключить его в свою.
В листинге 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.10.1"
Добавив rand в зависимости в Cargo.toml, мы говорим Cargo скачать пакет
rand и все его зависимости с crates.io и сделать
rand доступным в нашем проекте.
Затем, чтобы подключить определения из rand в область видимости нашего
пакета, мы добавили строку use, которая начинается с имени крейта, rand,
и перечислили элементы, которые хотим подключить. Вспомните: в разделе
«Генерируем случайное число» главы 2 мы подключили
элементы из модуля rand::prelude и вызвали функцию rand::rng:
use std::io;
use rand::prelude::*;
fn main() {
println!("Guess the number!");
let secret_number = rand::rng().random_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, поэтому
добавлять std в Cargo.toml не нужно. Но чтобы подключить из неё элементы
в область видимости нашего пакета, нужно сослаться на неё через use.
Например, для HashMap строка будет такой:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
Это абсолютный путь, который начинается с std — имени крейта стандартной
библиотеки.
Вложенные пути для аккуратных списков use
Если мы используем несколько элементов из одного крейта или модуля и пишем
каждый на отдельной строке, это занимает много места по вертикали. Например,
эти две инструкции use из игры в листинге 2-4 подключают элементы из std:
// --пропуск--
use std::cmp::Ordering;
use std::io;
// --пропуск--
use rand::prelude::*;
fn main() {
println!("Guess the number!");
let secret_number = rand::rng().random_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::prelude::*;
// --пропуск--
use std::{cmp::Ordering, io};
// --пропуск--
fn main() {
println!("Guess the number!");
let secret_number = rand::rng().random_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):
#![allow(unused)]
fn main() {
use std::collections::*;
}
Эта инструкция use подключает в текущую область видимости все публичные
элементы из std::collections. Будьте осторожны с этим оператором! С ним
труднее понять, какие имена видны в области видимости и где определено имя,
которое используется в программе. Кроме того, если в зависимости изменятся
определения, изменится и то, что вы импортировали. Например, при обновлении
зависимости могут появиться ошибки компиляции, если в ней добавится
определение с тем же именем, что и ваше в той же области видимости.
Оператор * часто используют в тестах, чтобы подключить всё тестируемое в
модуль tests, — об этом мы поговорим в разделе «Как писать
тесты» главы 11. Ещё его иногда используют в
шаблоне «прелюдии» — подробнее см. документацию стандартной
библиотеки.