Спецификация системы модулей
В данном документе определяется спецификация системы модулей языка программирования 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 (уже moved) : считается "не найдена допустимая привязка", переобъявление в текущей области
→ Найден x (неизменяемый, жив): E2010 Невозможно переприсвоить
→ Не найден : новое объявление в текущей области (единственный путь объявления)
mut x = value:
→ x уже существует в текущей области : E2002 Повторное определение
→ x существует во внешней области : E2013 Затенение запрещено (явное новое объявление не может совпадать по имени с внешним)
→ Нет конфликтов : новое изменяемое объявление- В одной области видимости: любое имя может быть объявлено только один раз (E2002)
- Во внутренней области без
mut: приоритетный поиск во внешней, присваивание или ошибка - Во внутренней области с
mut: явное новое объявление, запрещено совпадение с внешним (E2013)
В одной области видимости
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 слота)Основной механизм: После потребления старого значения привязка становится недействительной, имя возвращается в состояние "можно объявить". Это не затенение — старая привязка больше не существует.
// Конвейерная обработка данных: каждый шаг потребляет старое значение, создаёт новое
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-переменных, указывая, что имя находится в неинициализированном состоянии
// Чтение после 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
// Внешняя неизменяемая, внутреннее присваивание → нельзя переприсвоить неизменяемую переменную
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 внешний аккумулятор можно модифицировать в теле цикла
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 | Чтение уже перемещённой переменной |
Глава 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) => { ... }