mod
и use
Пути и видимость: pub
, crate
Работа с внешними зависимостями
Иерархия модулей
Примеры: разделение кода на модули
Практические советы
Упражнение: Создать проект с несколькими модулями
Заключение
Добро пожаловать в тринадцатую лекцию курса по Rust! Сегодня мы подробно разберём одну из ключевых концепций языка программирования Rust — его модульную систему. Модули в Rust позволяют организовать код, управлять видимостью и упрощают масштабирование проектов. Мы начнём с основ, постепенно углубимся в детали и закончим практическим заданием. Эта лекция будет самодостаточной, так что даже если вы новичок, вы сможете всё понять. Поехали!
Rust — это язык, который стремится к безопасности, читаемости и масштабируемости. Когда ваш проект растёт, код становится сложнее, и без правильной организации его будет трудно поддерживать. Модульная система в Rust решает эту проблему, позволяя:
Модули в Rust — это не просто способ упаковать код, но и инструмент для инкапсуляции, что делает их похожими на классы или пространства имён в других языках, только с уникальными особенностями.
mod
и use
mod
В Rust модули объявляются с помощью ключевого слова mod
. Оно говорит компилятору: "Здесь начинается новый модуль". Модуль может быть определён:
Пример inline-модуля:
mod my_module {
fn say_hello() {
println!("Привет из модуля!");
}
}
Здесь мы объявили модуль my_module
, внутри которого есть функция say_hello
. Но если вы попробуете вызвать my_module::say_hello()
прямо сейчас, получите ошибку. Почему? Потому что функция по умолчанию приватная, и к ней нет доступа снаружи модуля. Об этом чуть позже.
use
Чтобы использовать элементы из модуля (функции, структуры и т.д.), применяется use
. Оно "импортирует" нужные имена в текущую область видимости.
Пример:
mod my_module {
pub fn say_hello() { // Добавили pub, чтобы функция стала публичной
println!("Привет из модуля!");
}
}
fn main() {
use my_module::say_hello; // Импортируем функцию
say_hello(); // Теперь можно вызывать её напрямую
}
С use
вы можете сократить путь до элемента, чтобы не писать каждый раз полный путь вроде my_module::say_hello()
.
pub
, crate
Пути (paths) — это способ указать, где находится тот или иной элемент в иерархии модулей. Пути бывают:
crate
(корень вашего проекта) или имени внешней зависимости.self
(текущий модуль) или super
(родительский модуль).Пример:
mod outer {
pub mod inner {
pub fn hello() {
println!("Привет из inner!");
}
}
}
fn main() {
outer::inner::hello(); // Абсолютный путь от корня
}
pub
По умолчанию все элементы в Rust (функции, структуры, модули) являются приватными. Чтобы сделать их доступными извне, используется ключевое слово pub
. Без него никто за пределами модуля не сможет использовать ваш код.
Пример:
mod my_module {
fn private_fn() { // Приватная функция
println!("Я приватная!");
}
pub fn public_fn() { // Публичная функция
println!("Я публичная!");
private_fn(); // Приватную функцию можно вызывать внутри модуля
}
}
fn main() {
my_module::public_f
n(); // Работает
// my_module::private_fn(); // Ошибка: private_fn приватная
}
crate
crate
обозначает корень вашего проекта. Оно полезно для абсолютных путей, особенно в больших проектах.
Пример:
mod outer {
pub mod inner {
pub fn say() {
println!("Говорю из inner!");
}
}
}
fn main() {
crate::outer::inner::say(); // Абсолютный путь через crate
}
Rust использует менеджер пакетов Cargo для управления зависимостями. Чтобы подключить внешнюю библиотеку (или "крейт"), нужно:
Cargo.toml
.use
.Пример: подключим крейт rand
для генерации случайных чисел.
Cargo.toml
[dependencies]
rand = "0.8.5"
use rand::Rng;
fn main() {
let random_number = rand::thread_rng().gen_range(1..101); // Случайное число от 1 до 100
println!("Случайное число: {}", random_number);
}
После этого выполните cargo run
, и Cargo автоматически загрузит и подключит rand
.
Совет: Если вы не знаете, какой крейт использовать, загляните на crates.io — это официальный репозиторий библиотек для Rust.
Когда проект становится большим, держать весь код в одном файле неудобно. Rust позволяет вынести модули в отдельные файлы и организовать их в иерархию.
my_project/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs (опционально, если это библиотека)
└── outer.rs
В языке программирования Rust файл lib.rs используется, если ваш проект представляет собой библиотеку (library), а не просто исполняемое приложение (binary).
Основное различие между библиотекой и приложением
cargo run
. В таком случае точкой входа обычно является файл main.rs
, где определена функция main()
.lib.rs
.main.rs
mod outer; // Объявляем модуль outer, который находится в outer.rs
fn main() {
outer::say_hello();
}
outer.rs
pub fn say_hello() {
println!("Привет из outer!");
}
Если у вас есть модуль outer
, а внутри него ещё один модуль inner
, структура может выглядеть так:
src/
├── main.rs
└── outer/
├── mod.rs // Описание модуля outer
└── inner.rs // Вложенный модуль inner
src/outer/mod.rs
pub mod inner; // Объявляем вложенный модуль inner
pub fn outer_fn() {
println!("Это outer_fn!");
}
src/outer/inner.rs
pub fn inner_fn() {
println!("Это inner_fn!");
}
src/main.rs
mod outer;
fn main() {
outer::outer_fn();
outer::inner::inner_fn();
}
Rust автоматически ищет файлы по имени модуля или использует mod.rs
для определения структуры.
Давайте создадим небольшой проект, чтобы закрепить знания.
simple_game/
├── Cargo.toml
└── src/
├── main.rs
├── player.rs
└── game.rs
Cargo.toml
[package]
name = "simple_game"
version = "0.1.0"
edition = "2021"
src/player.rs
pub struct Player {
pub name: String,
pub score: i32,
}
impl Player {
pub fn new(name: &str) -> Player {
Player {
name: String::from(name),
score: 0,
}
}
pub fn add_score(&mut self, points: i32) {
self.score += points;
}
}
src/game.rs
use crate::player::Player; // Импортируем Player из модуля player
pub fn play_game(player: &mut Player) {
player.add_score(10);
println!("{} набрал {} очков!", player.name, player.score);
}
src/main.rs
mod player; // Объявляем модуль player
mod game; // Объявляем модуль game
use player::Player;
use game::play_game;
fn main() {
let mut player = Player::new("Алексей");
play_game(&mut player);
}
Запустите cargo run
, и вы увидите:
Алексей набрал 10 очков!
Этот пример показывает, как модули разделяют логику (игрок и игра) и как они взаимодействуют через публичные интерфейсы.
network
, utils
, models
).pub
) только то, что действительно нужно извне. Это улучшает инкапсуляцию.///
к публичным элементам — это автоматически попадёт в документацию (генерируется через cargo doc
).use
, но не злоупотребляйте — код должен оставаться читаемым.Создайте проект bookstore
, который моделирует книжный магазин. У вас должны быть:
book
с структурой Book
и методами.store
для управления книгами.main.rs
для демонстрации работы.bookstore/
├── Cargo.toml
└── src/
├── main.rs
├── book.rs
└── store.rs
Cargo.toml
[package]
name = "bookstore"
version = "0.1.0"
edition = "2021"
src/book.rs
pub struct Book {
pub title: String,
pub price: f64,
}
impl Book {
pub fn new(title: &str, price: f64) -> Book {
Book {
title: String::from(title),
price,
}
}
}
src/store.rs
use crate::book::Book;
pub struct Store {
books: Vec<Book>,
}
impl Store {
pub fn new() -> Store {
Store { books: Vec::new() }
}
pub fn add_book(&mut self, book: Book) {
self.books.push(book);
}
pub fn total_price(&self) -> f64 {
self.books.iter().map(|book| book.price).sum()
}
}
src/main.rs
mod book;
mod store;
use book::Book;
use store::Store;
fn main() {
let mut store = Store::new();
store.add_book(Book::new("Война и мир", 15.99));
store.add_book(Book::new("1984", 9.99));
println!("Общая стоимость книг: ${:.2}", store.total_price());
}
Запустите cargo run
. Вывод должен быть:
Общая стоимость книг: $25.98
Модульная система Rust — это мощный инструмент для структурирования кода. Мы изучили, как объявлять модули с mod
, импортировать их с use
, управлять видимостью через pub
, работать с путями и внешними зависимостями, а также организовывать иерархию модулей. Теперь вы можете уверенно разделять код на логические части и поддерживать его читаемость.
Практикуйтесь, создавайте свои проекты и экспериментируйте с модулями! В следующей лекции мы продолжим углубляться в возможности Rust.
Если у вас остались вопросы, перечитайте примеры или попробуйте изменить упражнение — добавьте, например, метод для удаления книг из магазина. Удачи!