Modules Structure
External link: https://doc.rust-lang.org/stable/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
Правила работы с модулями
- Crate Root. При компиляции crate, компилятор ищет корень: src/lib.rs для библиотеки, либо src/main.rs для запускаемого файла (binary);
- Модули. При декларировании модуля в crate root, например,
mod test
, компилятор будет искать его код в одном из мест:- Сразу после объявления в том же файле (inline);
- В файле src/test.rs;
- В файл src/test/mod.rs - старый стиль, поддерживается, но не рекомендуется.
- Подмодули. В любом файле, кроме crate root, можно объявить подмодуль, например,
mod automa
. Компилятор будет искать код подмодуля в одном из мест:- Сразу после объявления в том же файле (inline);
- В файле src/test/automa.rs;
- В файле src/test/automa/mod.rs - *старый стиль, поддерживается, но не рекомендуется.
- Путь до кода. Когда модуль часть crate, можно обращаться к его коду из любого места этой crate в соответствии с правилами privacy и указывая путь до кода. Например, путь до типа robot в подмодуле automa будет
crate::test::automa::robot
; - Private/public. Код модуля скрыт от родительских модулей по умолчанию. Для его публикации нужно объявлять его с
pub mod
вместоmod
; - Use. Ключевое слово
use
нужно для сокращения написания пути до кода:use crate::test::automa::robot
позволяет далее писать простоrobot
для обращения к данным этого типа.
Нужно лишь единожды внести внешний файл с помощью mod
в дереве модулей, чтобы он стал частью проекта, и чтобы другие файлы могли на него ссылаться. В каждом файле с помощью mod
не надо ссылаться, mod
- это НЕ include
из Python и других языков программирования.
Пример структуры
my_crate
├── Cargo.lock
├── Cargo.toml
└── src
├── test
│ └── automa.rs
├── test.rs
└── main.rs
Вложенные модули
Для примера, возьмём библиотеку, обеспечивающую работу ресторана. Ресторан делится на части front - обслуживание посетителей, и back - кухня, мойка, бухгалтерия.
mod front {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
Пример обращения к функции вложенного модуля
Объявление модуля публичным с помощью pub
не делает его функции публичными. Нужно указывать публичность каждой функции по отдельности:
mod front_of_house {
pub mod hosting { // модуль публичен, чтобы к нему обращаться
pub fn add_to_waitlist() {} // функция явно публична
// несмотря на публичность модуля, к функции обратиться нельзя
// если она непублична
}
}
pub fn eat_at_restaurant() {
// Абсолютный путь через корень - ключевое слово crate
crate::front_of_house::hosting::add_to_waitlist();
// Относительный путь
front_of_house::hosting::add_to_waitlist();
}
Обращние к функции выше уровнем
Относительный вызов функции можно сделать через super
(аналог “..” в файловой системе):
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order(); // вызов функции в родительском модуле
}
fn cook_order() {}
}
Обращение к структурам и перечислениям
Поля структур приватны по умолчанию. Обозначение структуры публичной с pub
не делает её поля публичными - каждое поле нужно делать публичным по отдельности.
mod back_of_house {
pub struct Breakfast { // структура обозначена как публичная
pub toast: String, // поле обозначено публичным
seasonal_fruit: String, // поле осталось приватным
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
} } } }
pub fn eat_at_restaurant() {
// Обращение к функции. Без функции к структуре с приватным полем
// не получится обратиться:
let mut meal = back_of_house::Breakfast::summer("Rye");
// запись в публичное поле:
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// если раскомменировать строку далее, будет ошибка компиляции:
// meal.seasonal_fruit = String::from("blueberries");
}
Поля перечислений публичны по умолчанию. Достатоно сделать само перечисление публичным pub enum
, чтобы видеть все его поля.
mod back_of_house {
pub enum Appetizer { // обозначаем перечисление публичным
Soup,
Salad,
} }
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
Обращение к объектам под другим именем
С помощью as
можно создать ярлык на объект в строке с use
. Особенно это удобно в случае, когда нужно обратиться к одинаковым по имени объектам в разных модулях:
use std::fmt::Result;
use std::io::Result as IoResult; // IoResult - ярлык на тип Result в модуле io
fn function1() -> Result {
// ... }
fn function2() -> IoResult<()> {
// ... }
Ре-экспорт объектов
При обращении к объекту с помощью use
, сам ярлык этот становится приватным - через него могут обращаться только функции текущего scope. Для того, чтобы из других модулей функции могли тоже обратиться через этот ярлык, нужно сделать его публичным:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
} }
pub use crate::front_of_house::hosting; // ре-экспорт объекта
pub fn eat_at_restaurant() {
hosting::add_to_waitlist(); // обращение к функции через ярлык
}
Работа с внешними библиотеками
Внешние библиотеки включаются в файл Cargo.toml. Далее, публичные объекты из них заносятся в scope с помощью use
.
# Cargo.toml
rand = "0.8.5"
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
}
Если нужно внести несколько объектов из одной библиотеки, то можно сократить количество use
:
//use std::cmp::Ordering;
//use std::io;
use std::{cmp::Ordering, io}; // список объектов от общего корня
//use std::io;
//use std::io::Write;
use std::io{self, Write}; // включение самого общего корня в scope
use std::collections::*; // включение всех публичных объектов по пути
Следует быть осторожным с оператором glob - *
, так как про внесённые с его помощью объекты сложно сказать, где именно они были определены.