Модули в Rust: как разбить код на несколько файлов и не сойти с ума

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Когда один файл — это уже тесно

Вы пишете на Rust. Ваш проект растёт. Ещё вчера main.rs вмещал всё — функции, структуры, пару конфигов. А сегодня вы тратите пять минут, чтобы найти нужную функцию среди тысячи строк. Знакомо?

Рано или поздно каждый разработчик Rust сталкивается с необходимостью разбить код на логические части. Казалось бы, что может быть проще? Создал папку, разложил файлы, импортировал — и готово. Но модульная система Rust славится своей строгостью. Одна неверная запись в mod.rs — и компилятор выдаёт ошибку, которую вы будете разгадывать полчаса.

В этой статье мы разберём топ ошибок при разделении модуля Rust на несколько файлов и, главное, покажем, как их избежать. Вы узнаете, как правильно организовать файловую структуру, когда использовать mod.rs, а когда — lib.rs, и почему pub use — ваш лучший друг.

Анатомия модульной системы Rust

Прежде чем нырять в ошибки, давайте вспомним основы. В Rust модуль — это единица организации кода. По умолчанию каждый файл — это модуль, но не всё так линейно.

Есть два подхода:

  • Одноимённый файл-модуль: файл utils.rs автоматически становится модулем utils, если вы объявите mod utils; в lib.rs или main.rs.
  • Директория-модуль: папка utils/ с файлом mod.rs внутри. В этом случае mod.rs — точка входа в модуль.

Звучит просто, но именно здесь кроется большинство граблей. Давайте разберём самые частые.

Топ-5 ошибок при разбиении модуля на несколько файлов

Ошибка №1: Забыли объявить подмодуль

Вы создали файл network/tcp.rs и пытаетесь его использовать. Пишете use network::tcp; — и получаете ошибку компиляции. Почему? Потому что Rust не сканирует папки автоматически. Вы должны явно объявить каждый подмодуль.

// network/mod.rs
// ❌ Ошибка: подмодуль tcp не объявлен
// use network::tcp; // не скомпилируется

// ✅ Правильно:
pub mod tcp; // объявляем подмодуль

Как избежать: Всегда проверяйте, что вы добавили pub mod имя_подмодуля; в родительский модуль. Это похоже на явный импорт — Rust требует чёткости.

Ошибка №2: Путаница между mod.rs и lib.rs

Новички часто помещают mod.rs в корень проекта, рядом с main.rs. Это работает, но только если вы создаёте библиотеку. Для исполняемого файла (main.rs) корневой модуль — это сам main.rs. А mod.rs — это файл внутри папки.

my_project/
├── main.rs        // корневой модуль для бинарника
├── lib.rs         // корневой модуль для библиотеки (если есть)
├── utils/
│   ├── mod.rs     // точка входа в модуль utils
│   └── helpers.rs

Как избежать: Запомните правило: mod.rs живёт только внутри папки-модуля. Если вам нужен корневой модуль для библиотеки — используйте lib.rs.

Ошибка №3: Забыли про pub в подмодулях

Вы объявили подмодуль, но всё равно не можете вызвать функцию. Скорее всего, вы забыли указать pub.

// network/mod.rs
pub mod tcp;

// network/tcp.rs
// ❌ Функция приватная по умолчанию
fn connect() { /* ... */ }

// ✅ Делаем её публичной
pub fn connect() { /* ... */ }

В Rust всё приватно по умолчанию. Это не баг, а фича: вы сами решаете, что открывать внешнему миру. Если функция нужна за пределами модуля — ставьте pub.

Как избежать: Используйте pub осознанно. Для внутренних утилит оставляйте приватный доступ. Для API — делайте публичным.

Ошибка №4: Игнорирование re-export (pub use)

Представьте: у вас есть модуль database с подмодулем models. Чтобы использовать структуру User, вам приходится писать use database::models::user::User;. Длинно и неудобно. А если структура переедет в другой файл — придётся менять все импорты.

Решение — re-export с помощью pub use.

// database/mod.rs
pub mod models;

// Re-export: делаем User доступным из database напрямую
pub use models::user::User;

// Теперь можно писать:
// use database::User; // вместо database::models::user::User

Это не только сокращает импорты, но и скрывает внутреннюю структуру модуля. Вы можете переименовать файлы внутри models, и внешний код не сломается — достаточно обновить pub use.

Как избежать: Сделайте pub use стандартной практикой для всех публичных типов. Это упрощает API и делает рефакторинг безболезненным.

Ошибка №5: Слишком глубокая вложенность

Вы создали структуру папок в 5 уровней: app/services/network/http/client.rs. Вроде логично, но теперь каждый импорт выглядит как эпопея. Кроме того, Rust ограничивает видимость: чтобы получить доступ к функции на 4-м уровне вложенности, нужно везде проставить pub.

// ❌ Слишком глубоко
use app::services::network::http::client::send_request;

// ✅ Лучше: выровнять модули
use app::http::client::send_request;

Как избежать: Придерживайтесь правила «не глубже 3 уровней». Если модуль становится слишком глубоким — подумайте, не пора ли вынести часть логики в отдельную библиотеку (crate).

Практический пример: разбиваем проект

Давайте соберём всё воедино. Допустим, вы пишете веб-сервер. Вот как может выглядеть правильная организация:

my_server/
├── main.rs
├── server/
│   ├── mod.rs
│   ├── listener.rs
│   └── handler.rs
├── config/
│   ├── mod.rs
│   └── settings.rs
└── utils/
    ├── mod.rs
    ├── logger.rs
    └── validator.rs

Файл main.rs:

mod server;
mod config;
mod utils;

use server::                

Блоги

Рекомендаций по книгам