Материал: Язык программирования Rust

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Файл: src/lib.rs
1   ...   11   12   13   14   15   16   17   18   ...   62
Листинг 7-13: Добавление функции add_to_waitlist в область видимости с use неидиоматическимспособомХотя листинги 7-11 и 7-13 выполняют одну и ту же задачу, листинг 7-11 является идиоматическим способом подключения функции в область видимости с помощью useПодключение родительского модуля функции в область видимости при помощи use означает, что мы должны указывать родительский модуль при вызове функции. Указание родительского модуля при вызове функции даёт понять, что функция не определена локально, но в то же время сводя к минимуму повторение полного пути. В коде листинга7-13 не ясно, где именно определена add_to_waitlistС другой стороны, при подключении структур, перечислений и других элементов используя use, идиоматически правильным будет указывать полный путь. Листинг 7-14показывает идиоматический способ подключения структуры стандартной библиотеки HashMap в область видимости бинарного крейта.Файл: src/main.rsЛистинг 7-14. Включение HashMap в область видимости идиоматическим способомЗа этой идиомой нет веской причины: это просто соглашение, которое появилось само собой. Люди привыкли читать и писать код на Rust таким образом.Исключением из этой идиомы является случай, когда мы подключаем два элемента с одинаковыми именами в область видимости используя оператор use - Rust просто не позволяет этого сделать. Листинг 7-15 показывает, как подключить в область действия два типа с одинаковыми именами Result, но из разных родительских модулей и как на них ссылаться.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(); } use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }

Файл: src/lib.rs
Листинг 7-15. Для включения двух типов с одинаковыми именами в одну область видимости необходимо
использовать их родительские модули.
Как видите, использование имени родительских модулей позволяет различать два типа
Result
. Если бы вместо этого мы указали use std::fmt::Result и use std::io::Result
,
мы бы имели два типа
Result в одной области видимости, и Rust не смог бы понять какой из двух
Result мы имели в виду, когда нашёл бы их употребление в коде.
Предоставление новых имён с помощью ключевого слова as
Есть другое решение проблемы добавления двух типов с одинаковыми именами в одну и ту же область видимости используя use
: после пути можно указать as и новое локальное имя (псевдоним) для типа. Листинг 7-16 показывает как по-другому написать код из листинга 7-15, путём переименования одного из двух типов
Result используя as
Файл: src/lib.rs
Листинг 7-16: Переименование типа, когда он включён в область видимости с помощью ключевого слова
as
Во втором операторе use мы выбрали новое имя
IoResult для типа std::io::Result
,
которое теперь не будет конфликтовать с типом
Result из std::fmt
, который также подключён в область видимости. Листинги 7-15 и 7-16 считаются идиоматичными,
поэтому выбор за вами!
use std::fmt; use std::io; fn function1
() -> fmt::
Result
{
// --snip--
} fn function2
() -> io::
Result
<()> {
// --snip--
} use std::fmt::
Result
; use std::io::
Result as
IoResult; fn function1
() ->
Result
{
// --snip--
} fn function2
() -> IoResult<()> {
// --snip--
}

Реэкспорт имён с pub use
Когда мы подключаем имя в область видимости, используя ключевое слово use
, то имя,
доступное в новой области видимости, является приватным. Чтобы позволить коду,
который вызывает наш код, ссылаться на это имя, как если бы оно было определено в области видимости данного кода, можно объединить pub и use
. Этот метод называется
реэкспортом (re-exporting), потому что мы подключаем элемент в область видимости, но также делаем этот элемент доступным для подключения в других областях видимости.
Листинг 7-17 показывает код из листинга 7-11, где use в корневом модуле заменено на pub use
Файл: src/lib.rs
Листинг 7-17. Предоставление имени для использования любым кодом из новой области при помощи
pub use
До этого изменения внешний код должен был вызывать функцию 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.
Использование внешних пакетов
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();
}

В Главе 2 мы запрограммировали игру угадывания числа, где использовался внешний пакет с именем rand для генерации случайного числа. Чтобы использовать rand в
нашем проекте, мы добавили эту строку в Cargo.toml:
Файл: Cargo.toml
Добавление rand в качестве зависимости в Cargo.toml указывает Cargo загрузить пакет rand и все его зависимости из crates.io и сделать rand доступным для нашего проекта.
Затем, чтобы подключить определения rand в область видимости нашего пакета, мы добавили строку use начинающуюся с названия пакета rand и списка элементов,
которые мы хотим подключить в область видимости. Напомним, что в разделе "Генерация случайного числа"
Главы 2, мы подключили трейт
Rng в область видимости и вызвали функцию rand::thread_rng
:
Члены сообщества Rust сделали много пакетов доступными на ресурсе crates.io
, и добавление любого из них в ваш пакет включает в себя одни и те же шаги: добавить внешние пакеты в файл Cargo.toml вашего пакета, использовать use для подключения элементов внешних пакетов в область видимости.
Обратите внимание, что стандартная библиотека std также является крейтом, внешним по отношению к нашему пакету. Поскольку стандартная библиотека поставляется с языком Rust, нам не нужно изменять Cargo.toml для подключения std
. Но нам нужно ссылаться на неё при помощи use
, чтобы добавить элементы оттуда в область видимости нашего пакета. Например, с
HashMap мы использовали бы эту строку:
Это абсолютный путь, начинающийся с std
, имени крейта стандартной библиотеки.
Использование вложенных путей для уменьшения длинных списков
use
Если мы используем несколько элементов, определённых в одном крейте или в том же модуле, то перечисление каждого элемента в отдельной строке может занимать много вертикального пространства в файле. Например, эти два объявления use используются rand =
"0.8.3"
use rand::Rng; fn main
() { let secret_number = rand::thread_rng().gen_range(
1
..=
100
);
} use std::collections::HashMap;
в программе угадывания числа (листинг 2-4) для подключения элементов из std в
область видимости:
Файл: src/main.rs
Вместо этого, мы можем использовать вложенные пути, чтобы добавить эти элементы в область видимости одной строкой. Мы делаем это, как показано в листинге 7-18,
указывая общую часть пути, за которой следуют два двоеточия, а затем фигурные скобки вокруг списка тех частей продолжения пути, которые отличаются.
Файл: src/main.rs
Листинг 7-18. Указание вложенного пути для добавления нескольких элементов с одинаковым префиксом
в область видимости
В больших программах, подключение множества элементов из одного пакета или модуля с использованием вложенных путей может значительно сократить количество необходимых отдельных операторов use
!
Можно использовать вложенный путь на любом уровне, что полезно при объединении двух операторов use
, которые имеют общую часть пути. Например, в листинге 7-19
показаны два оператора use
: один подключает std::io
, а другой подключает std::io::Write в область видимости.
Файл: src/lib.rs
Листинг 7-19: Два оператора
use
, где один является частью другого
Общей частью этих двух путей является std::io
, и это полный первый путь. Чтобы объединить эти два пути в одном операторе use
, мы можем использовать ключевое слово self во вложенном пути, как показано в листинге 7-20.
Файл: src/lib.rs
Листинг 7-20: Объединение путей из Листинга 7-19 в один оператор
use
// --snip-- use std::cmp::Ordering; use std::io;
// --snip--
// --snip-- use std::{cmp::Ordering, io};
// --snip-- use std::io; use std::io::Write; use std::io::{
self
, Write};

Эта строка подключает std::io и std::io::Write в область видимости.
Оператор * (glob)
Если мы хотим включить в область видимости все общедоступные элементы,
определённые в пути, мы можем указать этот путь, за которым следует оператор
*
:
Этот оператор use подключает все открытые элементы из модуля std::collections в
текущую область видимости. Будьте осторожны при использовании оператора
*
! Он может усложнить понимание, какие имена находятся в области видимости и где были определены имена, используемые в вашей программе.
Оператор
*
часто используется при тестировании для подключения всего что есть в модуле tests
; мы поговорим об этом в разделе "Как писать тесты"
Главы 11. Оператор
*
также иногда используется как часть шаблона автоматического импорта (prelude):
смотрите документацию по стандартной библиотеке для получения дополнительной информации об этом шаблоне.
use std::collections::*;

Разделение модулей на разные файлы
До сих пор все примеры в этой главе определяли несколько модулей в одном файле.
Когда модули становятся большими, вы можете захотеть переместить их определения в отдельные файлы, чтобы упростить навигацию по коду.
Например, давайте начнём с кода из листинга 7-17, в котором было несколько модулей ресторана. Мы будем извлекать модули в файлы вместо того, чтобы определять все модули в корневом модуле крейта. В нашем случае корневой модуль крейта - src/lib.rs, но это разделение также работает и с бинарными крейтами, у которых корневой модуль крейта — src/main.rs.
Сначала мы извлечём модуль front_of_house в свой собственный файл. Удалите код внутри фигурных скобок для модуля front_of_house
, оставив только объявление mod front_of_house;
, так что теперь src/lib.rs содержит код, показанный в листинге 7-21.
Обратите внимание, что этот вариант не скомпилируется, пока мы не создадим файл
src/front_of_house.rs из листинге 7-22.
Файл: src/lib.rs
Листинг 7-21. Объявление модуля
front_of_house
, чьё содержимое будет в src/front_of_house.rs
Затем поместим код, который был в фигурных скобках, в новый файл с именем
src/front_of_house.rs, как показано в листинге 7-22. Компилятор знает, что нужно искать в этом файле, потому что он наткнулся в корневом модуле крейта на объявление модуля с именем front_of_house
Файл: src/front_of_house.rs
Листинг 7-22. Определение содержимого модуля
front_of_house
в файле src/front_of_house.rs
Обратите внимание, что вам нужно только один раз загрузить файл с помощью объявления mod в вашем дереве модулей. Как только компилятор узнает, что файл является частью проекта (и узнает, где в дереве модулей находится код из-за того, куда вы поместили оператор mod
), другие файлы в вашем проекте должны ссылаться на код загруженного файла, используя путь к месту, где он был объявлен, как описано в разделе mod front_of_house; pub use crate::front_of_house::hosting; pub fn eat_at_restaurant
() { hosting::add_to_waitlist();
} pub mod hosting { pub fn add_to_waitlist
() {}
}

«Пути для ссылки на элемент в дереве модулей»
. Другими словами, mod
— это не
операция «включения», которую вы могли видеть в других языках программирования.
Далее мы извлечём модуль hosting в его собственный файл. Процесс немного отличается, потому что hosting является дочерним модулем для front_of_house
, а не корневого модуля. Мы поместим файл для hosting в новый каталог, который будет назван по имени его предка в дереве модулей, в данном случае это src/front_of_house/.
Чтобы начать перенос hosting
, мы меняем src/front_of_house.rs так, чтобы он содержал только объявление модуля hosting
:
Файл: src/front_of_house.rs
Затем мы создаём каталог src/front_of_house и файл hosting.rs, в котором будут определения, сделанные в модуле hosting
:
Файл: src/front_of_house/hosting.rs
Если вместо этого мы поместим hosting.rs в каталог src, компилятор будет думать, что код в hosting.rs это модуль hosting
, объявленный в корне крейта, а не объявленный как дочерний модуль front_of_house
. Правила компилятора для проверки какие файлы содержат код каких модулей предполагают, что каталоги и файлы точно соответствуют дереву модулей.
Альтернативные пути к файлам
До сих пор мы рассматривали наиболее идиоматические пути к файлам,
используемые компилятором Rust, но Rust также поддерживает и старый стиль пути к файлу. Для модуля с именем front_of_house
, объявленного в корневом модуле крейта, компилятор будет искать код модуля в:
src/front_of_house.rs (что мы рассматривали)
src/front_of_house/mod.rs (старый стиль, всё ещё поддерживаемый путь)
Для модуля с именем hosting
, который является подмодулем front_of_house
,
компилятор будет искать код модуля в:
src/front_of_house/hosting.rs (что мы рассматривали)
src/front_of_house/hosting/mod.rs (старый стиль, всё ещё поддерживаемый путь)
pub mod hosting; pub fn add_to_waitlist
() {}
Источник: https://files.student-it.ru/previewfile/264058