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 (уже moved)   : считается "не найдена допустимая привязка", переобъявление в текущей области
    → Найден x (неизменяемый, жив): E2010 Невозможно переприсвоить
    → Не найден             : новое объявление в текущей области (единственный путь объявления)

mut x = value:
    → x уже существует в текущей области : E2002 Повторное определение
    → x существует во внешней области    : E2013 Затенение запрещено (явное новое объявление не может совпадать по имени с внешним)
    → Нет конфликтов                      : новое изменяемое объявление
  • В одной области видимости: любое имя может быть объявлено только один раз (E2002)
  • Во внутренней области без mut: приоритетный поиск во внешней, присваивание или ошибка
  • Во внутренней области с mut: явное новое объявление, запрещено совпадение с внешним (E2013)

В одной области видимости

yaoxiang
x = 10
x = 20              // E2002: 'x' уже определён в этой области видимости

mut y = 10
y = 20              // OK: та же привязка, переприсваивание
mut y = 30          // E2002: 'y' уже определён в этой области видимости

z = 10
mut z = 20          // E2002: 'z' уже определён в этой области видимости (mut не может перекрыть существующее объявление)

Перепривязка после Move

Если неизменяемая переменная владеет значением, и это значение было перемещено (move, потреблено), исходная привязка переходит в состояние moved — имя всё ещё занимает слот в области видимости, но значение недоступно. В этом случае x = value — это не модификация старой привязки, а переобъявление x в той же области видимости.

Приоритетный поиск для "moved" ветки присваивания:
  x существует в текущей области видимости, но находится в состоянии moved
    → Компилятор считает "не найдена допустимая привязка"
    → Переобъявление x в текущей области видимости (перекрытие старого moved слота)

Основной механизм: После потребления старого значения привязка становится недействительной, имя возвращается в состояние "можно объявить". Это не затенение — старая привязка больше не существует.

yaoxiang
// Конвейерная обработка данных: каждый шаг потребляет старое значение, создаёт новое
data = fetch()           // Неизменяемая, владеет значением
data = transform(data)   // move data → старое data недействительно, новое data перепривязано
data = filter(data)      // Аналогично
process(data)

// Эквивалентная явная запись (для сравнения):
data1 = fetch()
data2 = transform(data1)  // data1 перемещён, нельзя использовать
data3 = filter(data2)    // data2 перемещён, нельзя использовать
process(data3)

Семантическое различие:

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

Почему это отличается от затенения:

  • Зенение (Rust let x = ...): старая привязка всё ещё существует, просто скрыта новой
  • Перепривязка после move: старая привязка уже потреблена, имя возвращается в состояние неинициализированного, переобъявление — единственный выход

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

  • Только значения с владением могут быть перемещены. Ссылки (&T, &mut T) копируются, а не перемещаются
  • Проверка move выполняется во время компиляции; чтение переменной в состоянии moved в любом выражении вызывает E2014
  • IDE может показывать серую подсказку на moved-переменных, указывая, что имя находится в неинициализированном состоянии
yaoxiang
// Чтение после move → Ошибка
data = fetch()
result = process(data)   // data перемещён
print(data)              // E2014: 'data' был перемещён, нельзя использовать

// Ссылки не вызывают move
ref_data = &value
copy1 = ref_data         // Копирование ссылки, ref_data всё ещё доступен
copy2 = ref_data         // OK

// Пере scopes: состояние moved проникает через границы
data = fetch()
{
    data = transform(data)  // move внешнего data → перепривязка (новое объявление во внутренней области)
    print(data)             // OK: используется внутренний data
}
print(data)                 // E2014: внешний data был перемещён

Пере scopes

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 внешний аккумулятор можно модифицировать в теле цикла
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Чтение уже перемещённой переменной

Глава 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) => { ... }