Avatar ·

Путаница с модулями в Rust: main.rs и lib.rs в одном проекте

Всем привет! Я новичок в Rust и никак не могу разобраться с модулями, когда в проекте одновременно есть `main.rs` и `lib.rs`. У меня есть такой проект: ``` my_project/ ├── Cargo.toml ├── src/ │ ├── main.rs │ └── lib.rs ``` В `lib.rs` я объявил модуль `utils`: ```rust // lib.rs mod utils; pub fn greet() { println!("Hello from lib!"); } ``` А в `utils.rs` у меня есть функция `add`: ```rust // utils.rs pub fn add(a: i32, b: i32) -> i32 { a + b } ``` В `main.rs` я пытаюсь вызвать функцию `greet` из `lib.rs` и функцию `add` из модуля `utils`: ```rust // main.rs fn main() { greet(); // ошибка println!("2 + 3 = {}", utils::add(2, 3)); // тоже ошибка } ``` Компилятор ругается: "cannot find function `greet` in this scope" и "unresolved import `utils`". Я думал, что если файлы лежат в одной папке `src`, то они автоматически видят друг друга. Но это не так. Подскажите, как правильно организовать модули в Rust, когда есть и `main.rs`, и `lib.rs`? И в чём вообще разница между ними? Спасибо! <<>>

Основная причина вашей путаницы

В Rust main.rs и lib.rs — это две разные корневые точки входа для модульной системы. Они не видят друг друга автоматически. Каждый из них является корнем своего собственного дерева модулей. main.rs — это корень для бинарного крейта (исполняемого файла), а lib.rs — корень для библиотечного крейта. Если вы хотите использовать код из lib.rs в main.rs, вам нужно явно подключить библиотеку как внешний крейт.

Решение: используйте ключевое слово extern crate или имя проекта

В современном Rust (начиная с edition 2018) вам не нужно писать extern crate в явном виде для крейтов из того же проекта. Достаточно использовать имя вашего проекта (оно указано в Cargo.toml в поле name).

Предположим, ваш Cargo.toml выглядит так:

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

Тогда в main.rs вы можете обращаться к публичным элементам из lib.rs через префикс my_project:::

// main.rs
fn main() {
    my_project::greet(); // вызываем функцию из lib.rs
    println!("2 + 3 = {}", my_project::utils::add(2, 3)); // вызываем функцию из модуля utils
}

Но это ещё не всё. Вам также нужно правильно объявить модуль utils в lib.rs с модификатором pub, чтобы он был доступен снаружи:

// lib.rs
pub mod utils; // обратите внимание на pub!

pub fn greet() {
    println!("Hello from lib!");
}

Теперь структура проекта будет работать. Компилируем и запускаем:

$ cargo run
Hello from lib!
2 + 3 = 5

Как это работает под капотом?

  • lib.rs — корень библиотечного крейта. Всё, что объявлено как pub в lib.rs или в его дочерних модулях (если они тоже pub), становится доступно внешним потребителям.
  • main.rs — корень бинарного крейта. Он может использовать библиотечный крейт, но не наоборот (библиотека не знает о существовании бинарника).
  • Когда вы пишете my_project::utils::add, Rust ищет крейт с именем my_project (вашу библиотеку) и затем внутри него — публичный модуль utils и функцию add.

Альтернатива: используйте use для удобства

Чтобы не писать длинные пути, можно импортировать нужные элементы:

// main.rs
use my_project::greet;
use my_project::utils::add;

fn main() {
    greet();
    println!("2 + 3 = {}", add(2, 3));
}

Важное замечание про файловую структуру

Если у вас в проекте есть только main.rs (без lib.rs), то все модули объявляются внутри main.rs. Например:

// main.rs
mod utils; // объявляем модуль, который лежит в src/utils.rs

fn main() {
    utils::add(2, 3);
}

Но если есть и main.rs, и lib.rs, то mod utils в main.rs и mod utils в lib.rs — это два разных модуля, которые могут даже не знать друг о друге. Будьте внимательны!

<<>>

Другой подход: сделайте main.rs тонкой обёрткой

В некоторых проектах принято, что main.rs содержит только минимальный код запуска, а вся логика вынесена в lib.rs. Это упрощает тестирование (библиотеку можно тестировать отдельно) и переиспользование кода.

Пример:

// main.rs
fn main() {
    my_project::run();
}
// lib.rs
pub mod utils;

pub fn run() {
    greet();
    println!("2 + 3 = {}", utils::add(2, 3));
}

fn greet() {
    println!("Hello from lib!");
}

Обратите внимание: функция greet здесь не объявлена как pub, потому что она используется только внутри библиотеки. Это хорошая практика — скрывать внутренние детали реализации.

Когда использовать lib.rs?

  • Если вы пишете библиотеку, которая будет подключаться в другие проекты.
  • Если у вас есть бинарник с несколькими исполняемыми файлами (например, src/bin/), то общая логика выносится в lib.rs.
  • Для модульного тестирования: код в lib.rs легче тестировать, чем код в main.rs.

Если ваш проект — просто одна программа без необходимости переиспользования, можно обойтись одним main.rs и объявлять модули внутри него. Но разделение на lib.rs и main.rs — это стандартная практика в Rust, к которой стоит привыкнуть.