Спецификация модульной системы
Данный документ определяет спецификацию модульной системы языка программирования YaoXiang, включая определение модулей, импорт/экспорт и область видимости.
Глава 1: Определение модулей
1.1 Основы модулей
В качестве границ модулей используются файлы. Каждый файл .yx является модулем.
// Имя файла является именем модуля
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }1.2 Правила именования модулей
- Имя модуля определяется именем файла
- Расширение файла
.yxне участвует в имени модуля - Имена модулей используют нотацию PascalCase
Глава 2: Импорт модулей
2.1 Синтаксис импорта
Import ::= 'use' ModuleRef ImportSpec?
ImportSpec ::= ('{' ImportItems '}') ('as' AliasList)?
| 'as' AliasList
ImportItems ::= Identifier (',' Identifier)* ','?
AliasList ::= Identifier (',' Identifier)*2.2 Способы импорта
| Синтаксис | Описание | Пример |
|---|---|---|
use path; | Импорт модуля, доступ через последнюю часть | use std.io; -> io.print |
use path.{a, b}; | Импорт указанных элементов | use std.io.{print}; -> print |
use path as alias; | Импорт с переименованием | use std.io as io; -> io.print |
use path.{i1, i2} as a, b; | Импорт указанных элементов с переименованием | use std.io.{print, read} as p, r; -> p, r |
2.3 Примеры импорта
// Импорт всего модуля
use std.io
io.print("Hello")
// Импорт указанных элементов
use std.io.{print, read}
print("Hello")
// Импорт с переименованием
use std.io as io_module
io_module.print("Hello")
// Импорт указанных элементов с переименованием
use std.io.{print, read} as p, r
p("Hello")Глава 3: Экспорт модулей
3.1 Ключевое слово pub
Для объявления экспортируемых элементов используется ключевое слово pub:
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }
// Приватный элемент (не экспортируется)
internal_value: Int = 423.2 Правила экспорта
- По умолчанию все элементы являются приватными
- Элементы, объявленные с
pub, доступны из других модулей - Приватные элементы доступны только внутри текущего модуля
3.3 Автоматическое связывание pub
Для функций, объявленных с pub, компилятор автоматически выполняет привязку к типам, определённым в том же файле:
// Объявление с pub, компилятор автоматически выполняет привязку
pub distance: (p1: Point, p2: Point) -> Float = {
dx = p1.x - p2.x
dy = p1.y - p2.y
(dx * dx + dy * dy).sqrt()
}
// Компилятор автоматически выводит:
// 1. Point определён в текущем файле
// 2. Параметры функции содержат Point
// 3. Выполняет Point.distance = distance[0]
// Вызов
d = distance(p1, p2) // Функциональный стиль
d2 = p1.distance(p2) // Синтаксический сахар ООПГлава 4: Область видимости
4.1 Область видимости модуля
Каждый модуль имеет собственную область видимости, элементы внутри модуля по умолчанию недоступны извне.
4.2 Вложенные области видимости
// Блочная область видимости
{
x = 10
// x виден внутри данной области
}
// x не виден за пределами этой области
// Область видимости функции
add: (a: Int, b: Int) -> Int = {
result = a + b
return result
}
// result не виден за пределами функции4.3 Объявление переменных и затенение
В YaoXiang нет ключевого слова let. Является ли x = value объявлением или присваиванием? Следует одному принципу:
Приоритет присваивания. Объявление выполняется лишь единожды, а присваивание — сотни раз. Пусть частая операция идёт по кратчайшему пути.
x = value:
Поиск x по цепочке областей видимости наружу
→ Найдено mut x : присваивание, OK (через токен &mut)
→ Найдено x (неизменяемое, живо) : E2010 невозможно повторно присвоить
→ Не найдено : новое объявление в текущей области (единственный путь объявления)
mut x = value:
→ В текущей области уже есть x : E2002 повторное определение
→ Во внешней области есть x : E2013 запрет затенения (явное новое объявление не может иметь то же имя)
→ Конфликта нет : новое изменяемое объявление- В одной области: любое имя может быть объявлено только один раз (E2002)
- Во внутренней без
mut: приоритет — поиск во внешней области, присваивание или ошибка - Во внутренней с
mut: явное новое объявление, запрещено совпадение имён с внешней областью (E2013)
Блок — это настоящая область видимости: предпосылкой «поиска по цепочке областей» является то, что каждый блок
{}действительно создаёт свой уровень — имена, объявленные во внутренней области, не утекают наружу (см. §2.15).
В одной области
x = 10
x = 20 // E2010: 'x' неизменяемое, невозможно повторно присвоить
mut y = 10
y = 20 // OK: та же привязка, повторное присваивание
mut y = 30 // E2002: 'y' уже определено в данной области (явное новое объявление с тем же именем)
z = 10
mut z = 20 // E2002: 'z' уже определено в данной области (mut не может перекрыть существующее объявление)Обратите внимание, что x = 20 выдаёт E2010 (невозможно повторно присвоить), а не E2002 (повторное определение): x = value без mut по семантике является присваиванием (поиск по цепочке областей), а не «повторным объявлением x». Только mut x = value является явным новым объявлением, и только тогда при совпадении имён выдаётся E2002.
Повторная привязка после Move
Если неизменяемая переменная владеет ресурсом, и её значение было move (поглощено), исходная привязка переходит в состояние moved — имя по-прежнему занимает слот в области видимости, но значение более недоступно.
moved не является входными данными для «определения объявления». Способом заново получить это имя является явное повторное объявление:
// Конвейерный поток данных: каждый шаг поглощает старое значение и порождает новое
mut data = fetch() // Явное изменяемое объявление
mut data = transform(data) // E2002: в данной области уже есть dataПочему не используется «уже moved → можно повторно объявить» (редакция от 2026-09-19):
move — это свойство потока данных, зависящее от пути — после
if c { move p }переменнаяpна одном пути уже move, на другом — ещё нет, это «возможно уже move», а не булево значение истина/ложь. Маркер на уровне привязки структурно не способен выразить слияние ветвей, и использование его в качестве переключателя «можно ли заново объявить имя» приведёт к ошибочным диагностикам (move в одной ветви будет воспринято как полностью move).Настоящий анализ move находится в
layers/ownership.rs: строится CFG тела функции, используется решёткаAlive < Moved < Droppedдля потока данных, в точке слияния ветвей берётся max (консервативно), и в точке проверки чтения выдаётся E2014/E2018. Он отвечает на вопрос «можно ли здесь читать», тогда как на вопрос «можно ли заново объявить это имя» независимо отвечает правило приоритета присваивания из §4.3.У них разные обязанности, и они не должны быть связаны. По этой причине прежний раздел «уже moved ветвь» никогда не был достижим: он зависел от маркера, который никогда не записывается.
Повторная привязка выполняется через явное объявление с mut:
// Эквивалентная явная запись
mut data1 = fetch()
data2 = transform(data1) // data1 поглощён move, нельзя использовать снова
mut data3 = filter(data2)Разделение семантики:
| Операция | Значение | Механизм | Синтаксис |
|---|---|---|---|
| Повторная привязка | Старое значение исчезает, новое рождается | move + новое объявление | mut x = f(x) (новое имя или mut) |
| Изменение на месте | Изменение значения в той же ячейке памяти | присваивание mut | mut x = v; x = w |
Ограничения:
- Только значения, владеющие ресурсом, могут быть поглощены move. Ссылки (
&T,&mut T) копируются, а не перемещаются - Проверка move выполняется на этапе компиляции (CFG + поток данных), чтение переменной в состоянии moved в любом выражении выдаёт E2014
- IDE может отображать серую подсказку на moved-переменных, указывающую, что имя находится в неинициализированном состоянии
- Объявление должно иметь начальное значение:
LetStmt ::= ('mut')? Identifier (':' TypeExpr)? '=' Expr(§3.2) — чисто аннотационное объявление вродеx: Intне соответствует грамматике, выдаётся E0012
// Чтение после move → ошибка
data = fetch()
result = process(data) // data поглощён move
print(data) // E2014: 'data' уже перемещён move, нельзя использовать
// Ссылки не вызывают move
ref_data = &value
copy1 = ref_data // Копирование ссылки, ref_data всё ещё доступен
copy2 = ref_data // OK
// Через области видимости: состояние moved проходит насквозь
data = fetch()
{
data = transform(data) // move внешней data → повторная привязка (новое объявление во внутренней области)
print(data) // OK: используется внутренняя data
}
print(data) // E2014: внешняя data уже перемещена moveЧерез области видимости
// Внешняя неизменяемая, присваивание во внутренней → неизменяемую переменную нельзя переприсвоить
x = 10
{
x = 20 // E2010: 'x' неизменяемое, нельзя повторно присвоить
}
{
mut x = 20 // E2013: нельзя затенить существующую переменную 'x' (явное объявление новой привязки)
}
// Внешняя mut, присваивание во внутренней → изменение той же привязки
mut y = 10
{
y = 20 // OK: та же привязка, изменение через токен &mut
}
print(y) // 20
// Внешняя mut, во внутренней нельзя объявить с тем же именем
mut z = 10
{
z = 30 // OK: та же привязка
}
{
mut z = 30 // E2013: нельзя затенить существующую переменную 'z'
}
// Многоуровневое вложение: mut проходит через все уровни
mut a = 0
{
{
a = 10 // OK
}
}
print(a) // 10
// Неизменяемое проходит через все уровни и тоже не может быть переприсвоено
b = 0
{
{
b = 10 // E2010: 'b' неизменяемое, нельзя повторно присвоить
}
}Цикл for
// Переменная цикла на каждой итерации — это новая привязка, а не изменение
for i in 1..5 {
print(i) // OK: на каждой итерации связывается новое значение
i = 10 // E2010: неизменяемая переменная цикла, нельзя повторно присвоить
}
for mut i in 1..5 {
i = 10 // OK: изменяемая переменная цикла
}
// Переменная цикла не может затенять внешнюю
i = 0
for i in 1..5 { // E2013: нельзя затенить существующую переменную 'i'
}
// Изменяемый внешний аккумулятор можно изменять внутри цикла
mut sum = 0
for i in 1..5 {
sum = sum + i // OK: та же привязка, изменение через токен &mut
}
print(sum) // 15
// Неизменяемый внешний нельзя изменять внутри цикла
sum2 = 0
for i in 1..5 {
sum2 = sum2 + i // E2010: 'sum2' неизменяемое, нельзя повторно присвоить
}Связанные коды ошибок
| Код ошибки | Сообщение | Сценарий срабатывания |
|---|---|---|
| E2002 | '{name}' is already defined in this scope | Повторное объявление в одной области (независимо от mut) |
| E2010 | Cannot assign to immutable variable '{name}' | При присваивании во внутренней области без mut внешняя переменная неизменяема и не была moved |
| E2013 | Cannot shadow existing variable '{name}' | Явное объявление во внутренней области (mut x или x: Type) с тем же именем, что и внешняя |
| E2014 | '{name}' has been moved and cannot be used | Чтение переменной, которая уже была moved |
Глава 5: Организация модулей
5.1 Структура каталогов
src/
├── main.yx // Главный модуль
├── math/
│ ├── index.yx // Точка входа математического модуля
│ ├── vector.yx // Модуль векторов
│ └── matrix.yx // Модуль матриц
└── utils/
├── index.yx // Точка входа модуля утилит
└── string.yx // Утилиты для строк5.2 Точка входа модуля
Файл index.yx в каталоге служит точкой входа модуля:
// math/index.yx
use math.vector
use math.matrix
pub Vector = vector.Vector
pub Matrix = matrix.Matrix5.3 Относительный импорт
// В math/vector.yx
use math.matrix // Абсолютный импорт
use .matrix // Относительный импорт (тот же каталог)Приложение: Краткий справочник по синтаксису модулей
A.1 Модуль = файл
// ИмяФайла.yx — это и есть имя модуля
Import ::= 'use' ModuleRefA.2 Импорт и экспорт
// Импорт
use std.io
use std.io.{print, read}
use std.io as io
// Экспорт
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }