Підключення шляхів до області видимості за допомогою ключового слова 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();
}

Listing 7-11: Bringing a module into scope with use

Додання 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();
    }
}

Listing 7-12: A use statement only applies in the scope it’s in

Помилка компілятора показує, що даний ярлик більше не дійсний в модулі 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-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

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 не дозволяє зробити це. Лістинг 7-15 демонструє як підключити до області видимості два типи Result, що мають однакове імʼя, але різні батьківські модулі, та як до них звертатися.

Файл: src/lib.rs

use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
    Ok(())
}

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}

Блок коду 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

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}

Блок коду 7-16: Перейменування типу при його додаванні до області видимості з ключовим словом as

У другому операторі 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();
}

Блок коду 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.

Використання зовнішніх пакетів

У Розділі 2 ми написали гру у вгадування чисел, яка використовувала зовнішній пакет під назвою rand для отримання випадкових чисел. Для використання rand в нашому проекті ми додали наступний рядок до Cargo.toml:

Файл: Cargo.toml

rand = "0.8.3"

Adding rand as a dependency in Cargo.toml tells Cargo to download the rand package and any dependencies from crates.io and make rand available to our project.

Потім, для того щоб додати 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;
}

This is an absolute path starting with std, the name of the standard library crate.

Використаня вкладенних шляхів для зменшення величезних переліків 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!"),
    }
}

Блок коду 7-18: Вказання вкладених шляхів для додання до області видимості елементів з однаковими префіксами

У більших програмах додання багатьох елементів до області видимості з одного крейту або модулю за допомогою вкладених шляхів може значно скоротити кількість необхідних використань use!

Ми можемо використовувати вкладені шляхи будь-якого рівня вкладеності, що є корисним при комбінуванні двох виразів use, що мають спільну частину шляху. Наприклад, Блок коду 7-19 демонструє два оператори use: один додає до області видимості std::io і один, що додає std::io::Write.

Файл: src/lib.rs

use std::io;
use std::io::Write;

Блок коду 7-20: Комбінування шляхів з Блока коду 7-19 в одному операторі use

Цей рядок додає std::io та std::io::Write до області видимості.

Глобальний оператор (*)

Якщо ми хочемо додати до області видимості всі публічні елементи, визначені за певним шляхом, ми можемо вказати шлях, за яким йтиме глобальний оператор *:

#![allow(unused)]
fn main() {
use std::collections::*;
}

Цей оператор use додає до області видимості всі публічні елементи, визначені в std::collections. Будьте обережні, використовуючи глобальний оператор! Це може ускладнити сприйняття коду, оскільки стає важче визначити, які імена є в області видимості і де саме було визначено певне імʼя, що використовується у вашій програмі.

Лобальний оператор часто використовується при тестуванні для включення до області видимості всіх елементів з модуля tests. Ми поговоримо про це пізніше у секції “Як писати тести” розділу 11. Глобальний оператор також інколи використовується як частина патерну Прелюдія (prelude): див. документацію по стандартній бібліотеці для отримання додаткової інформації по цьому патерну.