Шаг 9. Модули и пакеты
Зачем#
Один файл — хорошо для эксперимента, но программа растёт. OxL делит код на пакеты (единица сборки и версионирования) и
модули (единица видимости). Граница модуля — это граница инкапсуляции: то, что не объявлено pub, снаружи не видно, и
изменение внутренностей модуля не ломает чужой код.
- Пакет — каталог с файлом
oxl.toml. - Модуль — каталог внутри
src/; все.oxlфайлы одного каталога образуют один модуль и разделяют имена. - Порядок объявлений в модуле не важен; циклические зависимости между модулями — ошибка.
Создаём проект#
oxl new geo && cd geoПолучится пакет такого вида; добавьте в него модуль shapes и тест:
geo/
├── oxl.toml
├── src/
│ ├── main.oxl # корневой модуль, здесь fn main
│ └── shapes/
│ └── shapes.oxl # модуль shapes
└── tests/
└── shapes_test.oxloxl.toml — манифест пакета (III.2):
[package]
name = "geo"
version = "0.1.0"
edition = "2027"src/shapes/shapes.oxl:
pub(api) data Circle { pub(api) r: f64 }
pub(api) fn area(c: &Circle) -> f64 pure => 3.0 * c.r * c.r
fn secret() -> i32 pure => 42src/main.oxl:
use self.shapes { Circle, area }
fn main() io {
let c = Circle{ r: 2.0 }
print("area {area(&c)}")
}tests/shapes_test.oxl (тесты видят пакет через super):
use super.shapes { Circle, area }
test "area of unit circle" {
assert_eq(area(&Circle{ r: 1.0 }), 3.0)
}$ oxl run
area 12.0
$ oxl test
test geo.tests::area of unit circle ... ok(Этот проект проверяется при подготовке документации: oxl run и oxl test дают приведённый вывод.)
Разбор#
use self.shapes { Circle, area }— импорт из модуля своего пакета. Первый сегмент пути —std(стандартная библиотека),self(этот пакет),super(родительский модуль, используется в тестах) или имя зависимости изoxl.toml.- Видимость: без модификатора — внутри модуля;
pub— внутри пакета;pub(api)— всем, кто зависит от пакета (это публичный API:oxl publishпроверяет совместимость по SemVer, §2.3). Поэтомуfn secret()снаружи недоступна. - Поля
dataимеют собственную видимость: безpub(api)у поляrлитералCircle{ r: 2.0 }вне модуля не скомпилируется (visibility). - Прелюдия
std.core(List,Map,print,sleep, …) импортируется автоматически; остальное —use std.io { File }. - Неиспользованный импорт — предупреждение; конфликт имён двух импортов — ошибка, если имя используется.
Команды проекта: oxl run -- аргументы, oxl test, oxl fmt, oxl check; зависимости добавляются в [dependencies]
манифеста, а oxl.lock фиксирует версии.
Типичная ошибка: неверный импорт#
use std.nothing { Thing }
fn main() io {
print("hi")
}unresolved-import
unresolved-import: в std нет такого модуля. Список модулей — в справочнике стандартной
библиотеки; для своих модулей проверьте, что каталог лежит в src/ и что первый сегмент пути — self.
Что запомнить#
- Пакет =
oxl.toml; модуль = каталог;pub— в пакете,pub(api)— наружу. - Пути:
std…,self…,super…, имя зависимости. - Тесты лежат в
tests/или рядом с кодом.
Дальше: тесты.