Введение
Добро пожаловать в Bringing Paths Into Scope With the Use Keyword. Эта лабораторная работа является частью Rust Book. Вы можете практиковать свои навыки Rust в LabEx.
В этой лабораторной работе мы узнаем, как использовать ключевое слово use для создания ярлыков для вызова функций и модулей и bringing paths into scope.
Bringing Paths into Scope with the use Keyword
Надоело каждый раз писать полный путь для вызова функций, это кажется неудобным и повторяющимся. В Listing 7-7, независимо от того, выбирали мы абсолютный или относительный путь к функции add_to_waitlist, каждый раз, когда хотели вызвать add_to_waitlist, нам также приходилось указывать front_of_house и hosting. К счастью, есть способ упростить этот процесс: мы можем создать ярлык для пути с помощью ключевого слова use один раз, а затем использовать корочее имя везде в этом скоупе.
В Listing 7-11 мы подключаем модуль crate::front_of_house::hosting в скоуп функции eat_at_restaurant, чтобы нам пришлось указывать только hosting::add_to_waitlist, чтобы вызвать функцию add_to_waitlist в eat_at_restaurant.
Filename: 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();
}
Listing 7-11: Bringing a module into scope with use
Добавление use и пути в скоуп похоже на создание символической ссылки в файловой системе. Добавив use crate::front_of_house::hosting в корне пакета, hosting теперь является допустимым именем в этом скоупе, точно так же, будто бы модуль hosting был определен в корне пакета. Путей, подключаемых с помощью use, также проверяется приватность, как и любые другие пути.
Обратите внимание, что use создает ярлык только для конкретного скоупа, в котором оно встречается. Listing 7-12 перемещает функцию eat_at_restaurant в новый дочерний модуль под названием customer, который представляет собой другой скоуп, чем use-statement, поэтому тело функции не скомпилируется.
Filename: 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();
}
}
Listing 7-12: A use statement only applies in the scope it's in.
Сообщение об ошибке компилятора показывает, что ярлык больше не действует внутри модуля customer:
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`
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
Заметим, что также есть предупреждение, что use больше не используется в своем скоупе! Чтобы исправить эту проблему, перенесите use внутри модуля customer также, или ссылаться на ярлык в родительском модуле с помощью super::hosting внутри дочернего модуля customer.
Creating Idiomatic use Paths
В Listing 7-11 вы, возможно, спросили себя, почему мы указали use crate::front_of_house::hosting, а затем вызвали hosting::add_to_waitlist в eat_at_restaurant, вместо того, чтобы указать путь use до самой функции add_to_waitlist, чтобы достичь того же результата, как в Listing 7-13.
Filename: 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();
}
Listing 7-13: Bringing the add_to_waitlist function into scope with use, which is unidiomatic
Хотя и в Listing 7-11, и в Listing 7-13 выполняется одна и та же задача, Listing 7-11 представляет собой идиоматический способ подключить функцию с помощью use. Подключение родительского модуля функции с помощью use означает, что мы должны указывать родительский модуль при вызове функции. Указание родительского модуля при вызове функции делает ясно, что функция не определена локально, при этом минимизируя повторение полного пути. Код в Listing 7-13 неясен, где определена add_to_waitlist.
С другой стороны, при подключении структур, перечислений и других элементов с помощью use, идиоматично указывать полный путь. Listing 7-14 показывает идиоматический способ подключить структуру HashMap из стандартной библиотеки в скоуп бинарного пакета.
Filename: src/main.rs
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
Listing 7-14: Bringing HashMap into scope in an idiomatic way
За этой идиомой нет особой причины: это просто договорённость, которая сформировалась, и люди привыкли читать и писать код на Rust именно так.
Исключение от этой идиомы возникает, если мы подключаем два элемента с одинаковым именем с помощью use-статементов, потому что Rust не позволяет этого. Listing 7-15 показывает, как подключить два типа Result, которые имеют одинаковое имя, но разные родительские модули, и как ссылаться на них.
Filename: src/lib.rs
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
--snip--
}
fn function2() -> io::Result<()> {
--snip--
}
Listing 7-15: Bringing two types with the same name into the same scope requires using their parent modules.
Как вы можете видеть, использование родительских модулей позволяет различать два типа Result. Если бы мы вместо этого указали use std::fmt::Result и use std::io::Result, то в одном скоупе мы бы имели два типа Result, и Rust не знал, какой именно мы имеем в виду, когда используем Result.
Providing New Names with the as Keyword
Есть еще одно решение проблемы подключения двух типов с одинаковым именем в один и тот же скоуп с помощью use: после пути мы можем указать as и новое локальное имя, или алиас, для типа. Listing 7-16 показывает другой способ написать код из Listing 7-15, перейменовав один из двух типов Result с использованием as.
Filename: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
--snip--
}
fn function2() -> IoResult<()> {
--snip--
}
Listing 7-16: Renaming a type when it's brought into scope with the as keyword
Во втором use-статменте мы выбрали новое имя IoResult для типа std::io::Result, которое не будет конфликтовать с Result из std::fmt, который мы также подключили в скоуп. Listing 7-15 и Listing 7-16 считаются идиоматическими, поэтому решение остается за вами!
Re-exporting Names with pub use
Когда мы подключаем имя в скоуп с помощью ключевого слова use, имя, доступное в новом скоупе, является приватным. Чтобы позволить коду, вызывающему наш код, ссылаться на это имя так, будто бы оно было определено в скоупе этого кода, мы можем комбинировать pub и use. Эта техника называется re-exporting (переэкспортированием), потому что мы подключаем элемент в скоуп, но также делаем этот элемент доступным для других, чтобы они могли подключить его в свой скоуп.
Listing 7-17 показывает код из Listing 7-11 с use в корневом модуле, изменённым на pub use.
Filename: 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();
}
Listing 7-17: Making a name available for any code to use from a new scope with 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 и то, как он влияет на документацию вашего пакета в разделе "Exporting a Convenient Public API with pub use".
Using External Packages
В главе 2 мы написали проект игры в угадайку, который использовал внешнюю библиотеку rand для генерации случайных чисел. Чтобы использовать rand в нашем проекте, мы добавили эту строку в Cargo.toml:
Filename: Cargo.toml
rand = "0.8.5"
Добавление rand в качестве зависимости в Cargo.toml сообщает Cargo скачать пакет rand и любые зависимости с https://crates.io, и сделать rand доступным для нашего проекта.
Затем, чтобы подключить определения rand в скоуп нашего пакета, мы добавили строку use, начинающуюся с имени пакета, rand, и перечислили элементы, которые мы хотели подключить в скоуп. Напомним, что в разделе "Generating a Random Number" мы подключили трейт Rng в скоуп и вызвали функцию rand::thread_rng:
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
}
Участники сообщества Rust сделали многие пакеты доступными на https://crates.io, и подключение любого из них в ваш пакет включает в себя эти же шаги: перечисление их в файле Cargo.toml вашего пакета и использование use для подключения элементов из их пакетов в скоуп.
Обратите внимание, что стандартная библиотека std также является пакетом, внешним для нашего пакета. Поскольку стандартная библиотека распространяется вместе с языком Rust, нам не нужно изменять Cargo.toml, чтобы включить std. Но мы все же должны ссылаться на нее с помощью use, чтобы подключить элементы оттуда в скоуп нашего пакета. Например, для HashMap мы бы использовали эту строку:
use std::collections::HashMap;
Это абсолютный путь, начинающийся с std, имени стандартной библиотеки пакета.
Using Nested Paths to Clean Up Large use Lists
Если мы используем несколько элементов, определенных в одном и том же пакете или модуле, перечисление каждого элемента на отдельной строке может занимать много вертикального пространства в наших файлах. Например, эти два use-статмента, которые мы имели в игре в угадайку в Listing 2-4, подключают элементы из std в скоуп:
Filename: src/main.rs
--snip--
use std::cmp::Ordering;
use std::io;
--snip--
Вместо этого мы можем использовать вложенные пути, чтобы подключить те же элементы в скоуп в одну строку. Мы делаем это, указав общий участок пути, за которым следуют два двоеточия, а затем фигурные скобки вокруг списка частей путей, которые отличаются, как показано в Listing 7-18.
Filename: src/main.rs
--snip--
use std::{cmp::Ordering, io};
--snip--
Listing 7-18: Specifying a nested path to bring multiple items with the same prefix into scope
В больших программах подключение многих элементов в скоуп из одного и того же пакета или модуля с использованием вложенных путей может значительно сократить количество отдельных use-статментов!
Мы можем использовать вложенный путь на любом уровне в пути, что полезно при комбинировании двух use-статментов, которые имеют общий подпуть. Например, Listing 7-19 показывает два use-статмента: один, который подключает std::io в скоуп, и другой, который подключает std::io::Write в скоуп.
Filename: src/lib.rs
use std::io;
use std::io::Write;
Listing 7-19: Two use statements where one is a subpath of the other
Общий участок этих двух путей - это std::io, и это полный первый путь. Чтобы объединить эти два пути в один use-статмент, мы можем использовать self в вложенном пути, как показано в Listing 7-20.
Filename: src/lib.rs
use std::io::{self, Write};
Listing 7-20: Combining the paths in Listing 7-19 into one use statement
Эта строка подключает std::io и std::io::Write в скоуп.
The Glob Operator
Если мы хотим подключить все публичные элементы, определенные в пути, в скоуп, мы можем указать этот путь, за которым следует оператор * (глоб):
use std::collections::*;
Этот use-статмент подключает все публичные элементы, определенные в std::collections, в текущий скоуп. Будьте осторожны при использовании оператора глоб! Глоб может сделать сложнее определить, какие имена находятся в скоупе и где было определено имя, используемое в вашей программе.
Оператор глоб часто используется при тестировании, чтобы подключить все тестируемые элементы в модуль tests; мы поговорим об этом в разделе "How to Write Tests" (Как писать тесты). Оператор глоб также иногда используется в качестве части шаблона преамбулы; см. документацию по стандартной библиотеке для получения дополнительной информации о этом шаблоне.
Summary
Поздравляем! Вы завершили лабораторную работу по теме "Bringing Paths Into Scope With the Use Keyword" (Подключение путей в скоуп с использованием ключевого слова use). Вы можете практиковаться в более многих лабораторных работах в LabEx, чтобы улучшить свои навыки.