Skip to content

Latest commit

 

History

History
148 lines (113 loc) · 8.43 KB

File metadata and controls

148 lines (113 loc) · 8.43 KB

Глава 14. Модульная система

В операционной системе мы раскладываем файлы по папкам. В Rust мы раскладываем код по Модулям. Модульная система позволяет группировать функции, структуры и трейты по смыслу, а также скрывать внутреннюю реализацию от посторонних глаз (инкапсуляция).

Представь, что мы пишем программу для управления рестораном.

1. Определение модулей. Приватность и публичность (pub)

Чтобы создать модуль, мы используем ключевое слово mod.

По умолчанию всё, что находится внутри модуля (функции, структуры, другие модули) — приватно. Это значит, что код снаружи не может это использовать. Это сделано специально: компилятор заставляет тебя явно разрешать доступ к тем вещам, которые другие части программы имеют право трогать, с помощью слова pub (public).

// Создаем модуль "Ресторан"
mod restaurant {
    // Внутренний модуль "Зал для гостей". Делаем его публичным.
    pub mod front_of_house {
        // Функция посадки гостей. Тоже должна быть публичной!
        pub fn add_to_waitlist() {
            println!("Гость добавлен в список ожидания.");
        }

        // А вот эта функция приватная. Мы не хотим, 
        // чтобы гости сами убирали со стола.
        fn clean_table() {
            println!("Стол убран.");
        }
    }
}

fn main() {
    // Чтобы вызвать функцию, мы прописываем полный путь к ней через двойное двоеточие ::
    restaurant::front_of_house::add_to_waitlist();

    // restaurant::front_of_house::clean_table(); 
    // ОШИБКА КОМПИЛЯЦИИ! Функция clean_table приватная. 
    // Мы не можем вызвать её из функции main.
}

2. Вложенные модули и ключевое слово super

Пути в Rust похожи на пути к файлам в твоем компьютере. Иногда функции, находящейся глубоко во вложенном модуле, нужно обратиться к функции из "родительского" модуля (на уровень выше).

Для этого используется ключевое слово super (аналог ../ в файловых системах).

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        // Используем super, чтобы подняться на уровень выше 
        // и вызвать функцию из соседнего модуля (или глобальной области)
        super::serve_order(); 
    }

    fn cook_order() {}
}

fn serve_order() {} // Функция находится на верхнем уровне (на одном уровне с back_of_house)

3. Оператор use (Создание ярлыков)

Писать длиннющие пути вроде restaurant::front_of_house::add_to_waitlist() каждый раз очень неудобно.

Мы можем "принести" путь в нашу текущую область видимости с помощью ключевого слова use. Это работает как создание ярлыка на рабочем столе.

mod restaurant {
    pub mod front_of_house {
        pub fn add_to_waitlist() {}
    }
}

// Создаем ярлык! Теперь компилятор знает, что если мы напишем 
// add_to_waitlist, мы имеем в виду именно эту функцию.
use crate::restaurant::front_of_house::add_to_waitlist;

fn main() {
    add_to_waitlist(); // Вызываем коротко и красиво
    add_to_waitlist();
}

(Слово crate в начале пути означает "корень нашего текущего проекта").

Правило хорошего тона: При импорте функций принято использовать use до родительского модуля, а саму функцию вызывать через этот модуль (например, use crate::restaurant::front_of_house;, а затем front_of_house::add_to_waitlist()). Так сразу понятно, что функция пришла из другого модуля, а не определена где-то рядом. А вот структуры и перечисления (Enums) принято импортировать полностью: use std::collections::HashMap;.

4. Разбиение проекта на файлы

До сих пор мы писали всё в одном файле. Как вынести модуль restaurant в отдельный файл? Это очень частый камень преткновения для новичков в Rust. Внимание на магию!

Если в src/main.rs ты напишешь:

mod restaurant; // Точка с запятой вместо фигурных скобок!

fn main() {
    // ...
}

Точка с запятой говорит компилятору: "Я объявляю модуль restaurant, но его содержимое находится не здесь. Пойди и найди файл restaurant.rs или папку restaurant/mod.rs".

Давай посмотрим, как будет выглядеть наша файловая структура:

my_project/
├── Cargo.toml
└── src/
    ├── main.rs         (Здесь написано: mod restaurant;)
    └── restaurant.rs   (А здесь лежит код модуля!)

Содержимое файла src/restaurant.rs:

// Внутри файла restaurant.rs нам НЕ НУЖНО писать `mod restaurant {}`.
// Файл САМ ПО СЕБЕ уже является этим модулем. Мы просто пишем его содержимое:

pub mod front_of_house {
    pub fn add_to_waitlist() {
        println!("Гость в очереди!");
    }
}

А если модуль слишком большой и мы хотим разбить уже его? Например, мы хотим вынести front_of_house в свой собственный файл.

Структура папок станет такой:

my_project/
├── Cargo.toml
└── src/
    ├── main.rs                  (Пишем: mod restaurant;)
    ├── restaurant.rs            (Пишем: pub mod front_of_house;)
    └── restaurant/              (Создаем папку с именем родительского модуля!)
        └── front_of_house.rs    (Здесь пишем: pub fn add_to_waitlist() { ... })

Компилятор очень умен. Он сам ходит по папкам, ориентируясь на ключевое слово mod в твоем коде, и "собирает" все файлы в одно большое дерево программы.


Резюме:

  • mod создает новый модуль (папку для кода). Если написать mod name;, компилятор пойдет искать файл name.rs.
  • По умолчанию всё приватно. Используй pub, чтобы сделать функции, структуры или модули доступными снаружи.
  • super позволяет подняться по дереву модулей на уровень вверх.
  • use создает короткие псевдонимы (ярлыки) для длинных путей, чтобы код был чистым и читаемым.