Доступ к путям с помощью ключевого слова 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 создаёт ярлык только для конкретной области видимости, в которой находится это 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]: 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();
}
add_to_waitlist с помощью use, что неидиоматичноХотя и Листинг 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); }
HashMapНет сильной причины за этим идиомом: это просто соглашение, которое сложилось, и люди привыкли читать и писать код Rust таким образом.
Исключением из этого идиома является ситуация, когда мы делаем доступными два элемента с одинаковыми именами с помощью операторов use, потому что Rust не позволяет этого. Листинг 7-15 показывает, как сделать доступными два типа Result с одинаковыми именами, но из разных родительских модулей, и как на них ссылаться.
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.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
asВо втором операторе 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();
}
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 с помощью 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!("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 доступными:
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.
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.
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.
Глобальный оператор
Если мы хотим сделать все публичные элементы, определённые в пути, доступными, мы можем указать этот путь, за которым следует глобальный оператор *:
#![allow(unused)] fn main() { use std::collections::*; }
Этот оператор use делает все публичные элементы, определённые в std::collections, доступными в текущей области видимости. Будьте осторожны при использовании глобального оператора! Глобальный оператор может затруднить определение того, какие имена находятся в области видимости и где было определено используемое в вашей программе имя. Кроме того, если зависимость изменит свои определения, то то, что вы импортировали, также изменится, что может привести к ошибкам компиляции при обновлении зависимости, если зависимость добавит определение с тем же именем, что и ваше определение в той же области видимости, например.
Глобальный оператор часто используется при тестировании, чтобы сделать всё под тестом доступным в модуле tests; мы поговорим об этом в разделе «Как писать тесты» в Главе 11. Глобальный оператор также иногда используется как часть шаблона прелюдии: см. документацию стандартной библиотеки для получения дополнительной информации об этом шаблоне.