В операционной системе мы раскладываем файлы по папкам. В Rust мы раскладываем код по Модулям. Модульная система позволяет группировать функции, структуры и трейты по смыслу, а также скрывать внутреннюю реализацию от посторонних глаз (инкапсуляция).
Представь, что мы пишем программу для управления рестораном.
Чтобы создать модуль, мы используем ключевое слово 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.
}Пути в 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)Писать длиннющие пути вроде 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;.
До сих пор мы писали всё в одном файле. Как вынести модуль 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создает короткие псевдонимы (ярлыки) для длинных путей, чтобы код был чистым и читаемым.