Введення Шляхів до Області Видимості з Ключовим Словом use
Необхідність переписувати шляхи для виклику функцій може здатися незручною та повторюваною. У Блоці коду 7-7 незалежно від того, чи ми вказували абсолютний чи відносний шлях до функції add_to_waitlist
, для того, щоб її викликати, ми кожного разу мали також вказувати front_of_house
та hosting
. На щастя, існує спосіб спростити цей процес: достатньо один раз створити ярлик (shortcut) для шляху за допомогою ключового слова 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
та шляху до області видимості схоже на створення символічного посилання (symbolic link) у файловій системі. При додаванні 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`
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` due to 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
до області видимості.
Узагальнений Імпорт
Якщо ми хочемо додати до області видимості всі публічні елементи, визначені за певним шляхом, то ми можемо вказати шлях, за яким йтиме символ узагальнення *
:
#![allow(unused)] fn main() { use std::collections::*; }
Цей оператор use
додає до області видимості всі публічні елементи, визначені в std::collections
. Будьте обережні, використовуючи глобальний оператор! Це може ускладнити сприйняття коду, оскільки стає важче визначити, які імена є в області видимості і де саме було визначено певне імʼя, що використовується у вашій програмі.
Лобальний оператор часто використовується при тестуванні для включення до області видимості всіх елементів з модуля tests
. Ми поговоримо про це пізніше у секції “Як писати тести” розділу 11. Глобальний оператор також інколи використовується як частина патерну Прелюдія (prelude): див. документацію по стандартній бібліотеці
для отримання додаткової інформації по цьому патерну.