О версии: модули — возможность Topaz v5.2. Каждая однофайловая программа v5.1 остаётся корректной программой v5.2 с неизменным смыслом, когда служит точкой входа сборки — модульный синтаксис добавляет ноль новых зарезервированных ключевых слов.
importиexport— контекстные головные слова, распознаваемые только в начале списка элементов файла.
Программа на Topaz начинается с одного входного файла. Начиная с
v5.2 её можно разделить на файлы: каждый файл .tpz — ровно один
модуль, имя которого — путь относительно корня проекта.
Программа из двух файлов
project/
main.tpz точка входа
utils/
strings.tpz модуль utils.strings// utils/strings.tpz
export function shout(s: string) -> string {
return "{s}!"
}
export let greeting = "hello"// main.tpz
import utils.strings
let line = strings.shout(strings.greeting)
print(line) // "hello!"Сборка разрешает utils.strings в utils/strings.tpz под корнем
(каталог входного файла, если корень не задан сборке явно). Имена
файлов должны совпадать с путём модуля точно — разрешение
чувствительно к регистру и отвергает имена файлов, совпадающие с
точностью до нормализации Юникода или сведения регистра, поэтому
юнит, который собирается на одной машине, собирается одинаково на
любой файловой системе.
Импорты
В Topaz ровно две формы импорта.
Форма A — импорт пространства имён
import utils.strings
let s = strings.shout("hi")import utils.strings связывает одно имя: последний сегмент,
strings. Точечный путь — адрес, а не выражение: utils сам по себе
не связывается, и неявного родительского модуля нет.
Форма B — выборочный импорт
import utils.strings { shout, greeting }
let s = shout(greeting)Выборочный импорт связывает перечисленные экспортированные имена напрямую.
Псевдонимы через as
Обе формы принимают as, чтобы переименовать связываемое:
import utils.strings as str
import net.url { encode as encodeUrl }
let s = str.shout(encodeUrl("a b"))Псевдоним — единственное связываемое имя. Слоты не сочетаются:
import m as ns { x } — не синтаксис v5.2.
Правила импорта
- Импорты образуют пролог: каждый импорт стоит раньше всех остальных элементов верхнего уровня файла.
- Модуль может встречаться не более чем в одном импортирующем элементе на файл — дважды формой A, дважды формой B или вперемешку: всё это ошибки дублирующего импорта.
- Внутри одного списка импорта нельзя дважды выбрать одно исходное имя, и две позиции не могут дать одно локальное имя.
- Импорт модуля, который ничего не экспортирует, — ошибка: импортов «только ради побочных эффектов» в Topaz нет.
- Корни путей
stdиtopazзарезервированы —import std.io— статическая ошибка. Встроенная поверхность — прелюдия (поверхность v5.1 §22 без изменений), импорт ей не нужен.
Экспорты и видимость
Всё на верхнем уровне модуля приватно, пока не экспортировано.
Уровней видимости ровно два, а написание экспорта одно: встроенное
export перед объявлением.
export function area(w: float, h: float) -> float { return w * h }
export type Size = { w: float, h: float }
export let defaultSize: Size = { w: 1.0, h: 1.0 }
export const maxSide = 4096
function clamp(v: float) -> float { // приватный помощник
if v > 4096.0 { return 4096.0 }
return v
}export — обёртка с нулевой стоимостью времени выполнения:
объявление ведёт себя ровно так, как без экспорта. Правила:
- Экспортируемый
letсвязывает ровно один идентификатор. Экспорт с деструктуризацией, подстановочным или опровержимым паттерном — статическая ошибка. export let mut— статическая ошибка. Приватный для модуляlet mutзаконен; изменяемая ячейка привязки никогда не входит в публичную поверхность модуля (экспортированная неизменяемая привязка может при этом содержать значение, которое само изменяемо).- Импортированные привязки только для чтения: присваивание
импортированному имени или через пространство имён
(
strings.greeting = "x") — статическая ошибка. - Тип, названный в экспортированной сигнатуре, аннотации экспортированной привязки или теле экспортированного псевдонима, должен сам быть публично разрешимым — примитив или тип прелюдии, экспортированный псевдоним того же модуля либо квалифицированный пространством имён экспортированный тип импортированного модуля. Приватный псевдоним не может протечь через публичную поверхность.
Использование пространства имён
Привязка формы A — объект разрешения времени компиляции, а не значение. Она может стоять ровно в двух позициях — головой доступа к члену в выражении или головой квалифицированного типа:
// ui/theme.tpz
export type Style = { bold: bool }
export let defaultStyle: Style = { bold: false }// main.tpz
import ui.theme
let s: theme.Style = theme.defaultStyle // ns.Type и ns.member
let b = theme.defaultStyle.bold // дальше обычный доступ- Поиск в пространстве имён потребляет ровно одно имя члена; всё
после него — обычный доступ к членам разрешённого значения: в
theme.defaultStyle.boldпространство имён разрешаетdefaultStyle, а.boldчитает поле записи. - Член должен быть экспортированным именем. Экспортированные типы
используются в позиции типа, экспортированные значения и функции
— в позиции выражения:
let x = theme.Style— ошибка. - Само пространство имён нельзя передать, сохранить или вернуть:
let n = theme— ошибка. - Локальные и вложенные области видимости затеняют имена верхнего уровня модуля по обычным правилам; разрешение имён смотрит сначала локальные области, затем верхний уровень модуля, затем прелюдию.
Инициализация
Импортированные модули инициализируются жадно, ровно один раз, до
первого не-импортного элемента точки входа. Порядок детерминирован:
между модулями — порядок зависимостей (сначала зависимости; ничьи —
лексикографически), внутри модуля — сверху вниз. Элементы const —
этап компиляции, без шага времени выполнения.
Внутри импортированного модуля инициализатор может дотянуться только до уже инициализированных привязок — более ранних элементов того же модуля или чего-либо импортированного. Ссылки вперёд отвергаются статически, причём проверка заглядывает внутрь лямбд и функций-помощников:
// config.tpz — отвергается
export let cache = compute() // ошибка: достигает `limit`,
export let limit = 100 // объявленного после `cache`
function compute() -> int { return limit * 2 }Перестановка двух привязок устраняет ошибку. Благодаря этому правилу (плюс отказ от циклов и детерминированный порядок) ни один модуль никогда не наблюдает частично инициализированный модуль — «временной мёртвой зоны» во время выполнения нет.
Ещё два факта об импортируемых файлах:
- На их верхнем уровне допустимы только импорты, объявления и
привязки. Свободные инструкции, которые что-то делают, —
инструкции-выражения, присваивания,
while,defer,return,break,continue— в импортированном модуле являются статическими ошибками. Входной файл сохраняет всю свободу v5.1. Один и тот же файл может быть корректной точкой входа и некорректным импортом; диагностика показывает цепочку импортов от точки входа. - Фолт во время инициализации прерывает программу; перехватить его нельзя.
Циклы — ошибки
Каждый импорт создаёт ребро в графе импортов, и каждый цикл импортов — статическая ошибка, включая модуль, импортирующий сам себя, и независимо от того, «только ли типы» импортируются. Диагностика сообщает один канонический путь цикла на каждую группу взаимно импортирующих модулей, поэтому одна и та же ошибка сборки читается одинаково везде.
Чего в модулях v5.2 нет
Модульная система v5.2 намеренно мала:
-
Ни менеджера пакетов, ни манифестов — один корень, одна точка входа, пути под корнем. Файлы-манифесты не имеют языкового смысла.
-
Нет синтаксиса реэкспорта —
export import, списки экспорта (export { a, b }) и подстановочные экспорты отвергаются. Узкий API можно переслать вручную обычными экспортами:TOPAZimport ui.theme export let defaultStyle = theme.defaultStyle export type Style = theme.StyleКанонический стиль всё равно предпочитает импорт определяющего модуля напрямую.
-
Нет элементов
use—useраспознаётся и отвергается отдельной диагностикой; слово зарезервировано. -
Нет строковых путей —
import "utils/strings"отвергается; пути модулей — идентификаторы через точку.
Эти формы диагностируются как модульный синтаксис (вы получите модульную ошибку, а не сбивающую с толку ошибку базового синтаксиса), но частью языка они не являются.