Визначення модулів для контролю області видимості та приватності

В цьому розділі ми поговоримо про модулі та інші частини модульної системи, а саме: про шляхи, що дозволяють іменувати елементи; про ключове слово use, яке додає шлях в область видимості; та про ключове слово pub, що робить елементи публічними. Ми також розглянемо ключове слово as, зовнішні пакети та оператор glob.

Ми почнемо з переліку правил, до яких вам було б зручно повертатися в якості довідки в майбутньому при організації коду. Потім ми детально пояснимо кожне з правил.

Шпаргалка по модулям

В цьому місці ми дамо короткий огляд того, як модулі, шляхи, ключові слова use та pub працюють в компіляторі, та як більшість розробників організовують свій код. В цьому розділі ми також розберемо приклади кожного з цих правил. Цей розділ буде прекрасним місцем, куди варто звертатися для нагадування про те, як працюють модулі.

  • Починайте з кореня крейту: Компілюючи крейт, компілятор спочатку дивиться в кореневий файл крейту в пошуках коду для компіляції. Зазвичай це src/lib.rs для бібліотечного крейту або src/main.rs для бінарного.
  • Оголошення модулів: Ви можете оголошувати нові модулі в кореневому файлі крейту. Скажімо, ви хочете оголосити модуль "garden" як mod garden;. Компілятор шукатиме код даного модуля в наступних місцях:
    • Локально в цьому файлі всередині фігурних дужок, які заміняють крапку з комою після mod garden
    • У файлі src/garden.rs
    • У файлі src/garden/mod.rs
  • Оголошення підмодулів: Ви можете оголошувати підмодулі в будь якому файлі, не лише в корені крейту. Наприклад, ви можете оголосити mod vegetables; в src/garden.rs. Компілятор шукатиме код підмодуля в теці з іменем батьківського модуля в наступних місцях:
    • Локально в цьому файлі, одразу після mod vegetables, всередині фігурних дужок замість крапки з комою
    • У файлі src/garden/vegetables.rs
    • У файлі src/garden/vegetables/mod.rs
  • Шляхи до коду в модулях: Після того як модуль став частиною вашого крейту, ви можете звертатися до його коду з будь-якого місця даного крейту за допомогою шляху до коду, якщо дозволяють правила приватності. Наприклад, тип Asparagus в модулі garden vegetables буде знайдений за шляхом crate::garden::vegetables::Asparagus.
  • Приватність або публічність: Код всередині модуля є приватним від його батьківських модулів за замовчуванням. Аби зробити модуль публічним, оголосіть його за допомогою pub mod замість mod. Аби зробити елементи всередині публічного модуля публічними також, використовуйте pub перед їх оголошенням.
  • Ключове слово use: Всередині області видимості ключове слово use створює псевдоніми для елементів аби прибрати необхідність повторювати довгі шляхи. В будь якій області видимості, де необхідно звертатися до crate::garden::vegetables::Asparagus ви можете створити псевдонім use crate::garden::vegetables::Asparagus; і після цього просто писати Asparagus для використання цього типу в даній області видимості.

Аби продемонструвати ці правила, створимо бінарний крейт backyard. Тека крейту, яка також називається backyard, містить такі файли та теки:

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs

Кореневий файл крейту в цьому випадку це src/main.rs. Його вміст:

Файл: src/main.rs

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {:?}!", plant);
}

Рядок pub mod garden; каже компілятору підключити код, який знайдений в src/garden.rs:

Файл: src/garden.rs

pub mod vegetables;

Тут pub mod vegetables; означає, що код в src/garden/vegetables.rs також буде підключений. Цей код:

#[derive(Debug)]
pub struct Asparagus {}

Тепер давайте розглянемо ці правила детальніше і продемонструємо їх в роботі!

Групування повʼязаного коду в модулі

Модулі дозволяють організувати код в крейті для читабельності та простоти повторного використання. Модулі також дозволяють контролювати приватність елементів, оскільки код всередині модуля є приватним за замовчуванням. Приватні елементи являють собою внутрішні деталі реалізації, недоступні для використання ззовні. Ми можемо зробити модулі і елементи всередині них публічними, що дозволить сторонньому коду використовувати їх і залежати від них.

В якості прикладу давайте напишемо бібліотечний крейт, що реалізує функціонал ресторану. Ми визначимо сигнатури функцій, проте залишимо їх вміст пустим для того, щоб сконцентруватися на організації коду, а не на деталях імплементації ресторану.

В ресторанній справі вирізняють такі частини ресторану як внутрішня кухня (back of house) та зал (front of house). Зал це те, де сидять відвідувачі. Тут знаходяться місця для клієнтів, офіціанти приймають замовлення і оплату, а бармени роблять напої. Внутрішня кухня - це те, де шеф-кухарі і повари працюють на кухні, посудомийники миють посуд, а менеджери виконують адміністративну роботу.

Для того аби структурувати наш крейт правильним чином, можемо організувати його функції у вкладених модулях. Створіть нову бібліотеку з іменем restaurant, виконавши cargo new restaurant --lib; тоді наберіть код з Лістинга 7-1 в src/lib.rs аби визначити деякі модулі та сигнатури функцій. Далі йде секція для зали:

Файл: src/lib.rs

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}

        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}

        fn serve_order() {}

        fn take_payment() {}
    }
}

Блок коду 7-1: Модуль front_of_house, що містить інші модулі, які своєю чергою містять функції

Ми визначаємо модуль за допомогою ключового слова mod, після якого йде назва модуля (в цьому випадку front_of_house). Тіло модуля розміщається всередині фігурних дужок. Модулі можуть містити інші модулі, як в нашому випадку це зроблено з модулями hosting та serving. Також в модулях можуть знаходитися визначення інших елементів, таких як структури, переліки, константи, трейти, і - як у Блоці коду 7-1 - функції.

Використовуючи модулі, ми можемо групувати повʼязані визначення між собою і показувати, чому саме вони повʼязані. Програмісти, що використовують цей код, можуть орієнтуватись в коді на рівні функцій замість того, аби бути змушеними читати всі визначення в коді. Це робить задачу пошуку необхідних елементів набагато простішою. Додаючи новий функціонал до коду, програмісти знають де розмістити певний код аби підтримувати порядок і організацію в програмі.

Раніше ми згадували, що src/main.rs та src/lib.rsназиваються коренями крейту. Причина такого іменування в тому, що вміст будь-якого з цих двох файлів утворює модуль з іменем crate в корені структури модуля крейту, яка також відома як дерево модулів.

Блок коду 7-2 демонструє дерево модулів для структури з Блоку коду 7-1.

crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment

Блок коду 7-2: дерево модулів для коду в Блоці коду 7-1

Це дерево показує, як одні модулі вкладені в інші. Наприклад, hostingвкладений в front_of_house. Дерево також показує, що деякі модулі є братами (siblings) один для одного, що означає, що вони визначені в одному модулі. hosting та serving є братами, визначеними всередині front_of_house. Якщо модуль A міститься всередині модуля B, ми кажемо, що модуль A є нащадком (child) модуля B і що модуль B є батьком (parent) модуля A. Зверніть увагу, що батьком усього дерева модулів є неявний модуль з назвою crate.

Дерево модулів може нагадувати вам дерево тек і файлів файлової системи на вашому компʼютері. Це дуже влучне порівняння! Ви можете використовувати модулі для організації коду точно так само, як ви використовуєте теки у файловій системі. І так само, як у випадку з файлами в теці, нам потрібен спосіб пошуку необхідних модулів.