Skip to content

Спецификация модульной системы ​

Данный документ определяет спецификацию модульной системы языка программирования 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 Примеры импорта ​

yaoxiang
// Импорт всего модуля
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:

yaoxiang
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }

// Приватный элемент (не экспортируется)
internal_value: Int = 42

3.2 Правила экспорта ​

  • По умолчанию все элементы являются приватными
  • Элементы, объявленные с pub, доступны из других модулей
  • Приватные элементы доступны только внутри текущего модуля

3.3 Автоматическое связывание pub ​

Для функций, объявленных с pub, компилятор автоматически выполняет привязку к типам, определённым в том же файле:

yaoxiang
// Объявление с 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 Вложенные области видимости ​

yaoxiang
// Блочная область видимости
{
    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).

В одной области ​

yaoxiang
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 не является входными данными для «определения объявления». Способом заново получить это имя является явное повторное объявление:

yaoxiang
// Конвейерный поток данных: каждый шаг поглощает старое значение и порождает новое
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:

yaoxiang
// Эквивалентная явная запись
mut data1 = fetch()
data2 = transform(data1)  // data1 поглощён move, нельзя использовать снова
mut data3 = filter(data2)

Разделение семантики:

ОперацияЗначениеМеханизмСинтаксис
Повторная привязкаСтарое значение исчезает, новое рождаетсяmove + новое объявлениеmut x = f(x) (новое имя или mut)
Изменение на местеИзменение значения в той же ячейке памятиприсваивание mutmut x = v; x = w

Ограничения:

  • Только значения, владеющие ресурсом, могут быть поглощены move. Ссылки (&T, &mut T) копируются, а не перемещаются
  • Проверка move выполняется на этапе компиляции (CFG + поток данных), чтение переменной в состоянии moved в любом выражении выдаёт E2014
  • IDE может отображать серую подсказку на moved-переменных, указывающую, что имя находится в неинициализированном состоянии
  • Объявление должно иметь начальное значение: LetStmt ::= ('mut')? Identifier (':' TypeExpr)? '=' Expr (§3.2) — чисто аннотационное объявление вроде x: Int не соответствует грамматике, выдаётся E0012
yaoxiang
// Чтение после 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

Через области видимости ​

yaoxiang
// Внешняя неизменяемая, присваивание во внутренней → неизменяемую переменную нельзя переприсвоить
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 ​

yaoxiang
// Переменная цикла на каждой итерации — это новая привязка, а не изменение
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)
E2010Cannot assign to immutable variable '{name}'При присваивании во внутренней области без mut внешняя переменная неизменяема и не была moved
E2013Cannot 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 в каталоге служит точкой входа модуля:

yaoxiang
// math/index.yx
use math.vector
use math.matrix

pub Vector = vector.Vector
pub Matrix = matrix.Matrix

5.3 Относительный импорт ​

yaoxiang
// В math/vector.yx
use math.matrix  // Абсолютный импорт
use .matrix      // Относительный импорт (тот же каталог)

Приложение: Краткий справочник по синтаксису модулей ​

A.1 Модуль = файл ​

// ИмяФайла.yx — это и есть имя модуля
Import ::= 'use' ModuleRef

A.2 Импорт и экспорт ​

yaoxiang
// Импорт
use std.io
use std.io.{print, read}
use std.io as io

// Экспорт
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }