Skip to content

Спецификация системы типов

Настоящий документ определяет спецификацию системы типов языка программирования YaoXiang, включая базовые типы, составные типы, generics и trait.


Глава 0: Теоретические основы

0.1 Изоморфизм Карри — Ховарда

Изоморфизм Карри — Ховарда (Curry-Howard correspondence) является теоретической основой системы типов YaoXiang. Он раскрывает глубокую корреспонденцию между системой типов языка программирования и математической логикой:

ЛогикаЯзык программирования
Суждение (P)Тип Type
Доказательство (p: P)Программа x: T = ...
Импликация (P \rightarrow Q)Функциональный тип (P) -> Q
Конъюнкция (P \wedge Q)Product-тип { a: P, b: Q }
Дизъюнкция (P \vee Q)Sum-тип { a(P) | b(Q) }
Универсальная квантификация (\forall x:T. P(x))Generics (T: Type) -> ...
Истина (\top)Void (Unit, имеет значение по умолчанию)
Ложь (\bot)Never (нуль конструкторов, ни одно значение не обитает)
Типовые вселенные (Type_n : Type_{n+1})Иерархия вселенных (защита от парадокса Рассела)
Case-анализPattern matching на уровне типов

Примечание: Pattern matching на уровне типов — это классификация случаев (case analysis), не математическая индукция. Для индукции требуются рекурсивные функции на уровне типов + проверка компилятором терминированности.

0.2 Типы как суждения, программы как доказательства

В YaoXiang эта корреспонденция является основным принципом проектирования:

  • Терминирующие вычисления на уровне типов соответствуют корректным конструктивным доказательствам. Типовые семейства YaoXiang (такие как case analysis + рекурсивные вызовы Add на Nat) по сути являются кодировкой математической индукции на уровне типов — при условии, что компилятор выполняет проверку терминированности.
  • Проверка типов — это верификация доказательства. Когда программа проходит проверку типов, это эквивалентно конструктивному доказательству логического суждения.

0.3 Влияние на дизайн языка

Изоморфизм Карри — Ховарда в YaoXiang проявляется следующим образом:

  1. Иерархия вселенных (RFC-010): Type₀ : Type₁ : Type₂ … избегает логических парадоксов (Type: Type) (парадокс Жирара)
  2. Типовые семейства (RFC-011): Case analysis + рекурсивные вызовы натуральных чисел Nat(Zero/Succ) соответствуют аксиомам Пеано — при условии проверки компилятором терминированности
  3. Условные типы (RFC-011): If: (C: Bool, T: Type, E: Type) -> Type соответствует case-дизъюнкции в логике
  4. Зависимые от значений типы (RFC-011): Vec: (n: Int) -> Type соответствует конечной квантификации "для каждого целого n существует тип"

Глава 1: Классификация типов

1.1 Типовые выражения

TypeExpr    ::= PrimitiveType
              | RecordType
              | InterfaceType
              | TupleType
              | FnType
              | GenericType
              | TypeRef
              | TypeUnion
              | TypeIntersection

Замечание о дизайне: Хотя RFC-010 предлагает унифицированную модель "всё является присваиванием" (name: type = value), на синтаксическом уровне типы и значения всё ещё нужно различать. В реализации компилятора Type и Expr — два независимых AST-перечисления (ast.rs:406 и ast.rs:25), а TypeExpr как BNF-заполнитель соответствует перечислению Type в реализации, представляющему "в этой позиции ожидается тип".


Глава 2: Базовые типы

2.1 Primitive type

ТипЛогическое соответствиеОписаниеРазмер по умолчанию
TypeМетатип0 байт
Never⊥ (ложь/пустой тип)Нуль конструкторов, никаких значений. Дивергентный/panic возвращаемый тип. Never <: T для любого T.0 байт
Void⊤ (истина/Unit)Имеет значение void по умолчанию, product-тип с нулевым количеством полей. x: Void = <по умолчанию> легально.0 байт
BoolБулево значение: true / false1 байт
IntЗнаковое целое8 байт
UintБеззнаковое целое8 байт
FloatЧисло с плавающей точкой8 байт
StringСтрока в кодировке UTF-8переменный
CharСимвол Unicode4 байта
BytesСырые байтыпеременный

Целые с битовой шириной: Int8, Int16, Int32, Int64, Int128 Float с битовой шириной: Float32, Float64

2.2 Never и Void: ⊥ и ⊤

Never и Void — логические примитивы системы типов, соответственно представляющие ложь (⊥) и истину (⊤).

Never (⊥, ложь/пустой тип) — три непреложных свойства:

  1. Нуль конструкторов: Никакой литерал или выражение не может произвести значение типа Never. x: Never = ... — нечего написать справа.
  2. Принцип взрыва: Never <: T выполняется для любого типа T. assert(false) возвращает Never, после чего код может пройти проверку типов (хотя никогда не выполнится).
  3. Маркер дивергенции: f: (...) -> Never означает, что f гарантированно не возвращается. Компилятор использует это для анализа недостижимого кода и слияния веток match.

Never — это встроенное имя типа (зарегистрированное в том же пространстве, что Int/Bool), не ключевое слово.

Void (⊤, истина/Unit) — ровно один обитатель (значение void по умолчанию). Void — это нейтральный элемент (幺元) product-типа с нулевым количеством полей. x: Void = <по умолчанию> легально, функции по умолчанию возвращают Void при отсутствии return.


Глава 3: Составные типы

3.1 Record type

Унифицированный синтаксис: Name: Type = { field1: Type1, field2: Type2, ... }

RecordType  ::= '{' FieldList? '}'
FieldList   ::= Field (',' Field)* ','?
Field       ::= Identifier ':' TypeExpr
            |  Identifier                 // Ограничение интерфейса
yaoxiang
// Простой record type
Point: Type = { x: Float, y: Float }

// Пустой record type
Empty: Type = {}

// Record type с generics
Pair: (T: Type) -> Type = { first: T, second: T }

// Record type, реализующий интерфейсы
Point: Type = {
    x: Float,
    y: Float,
    Drawable,
    Serializable
}

Правила:

  • Record type определяются фигурными скобками {}
  • После имени поля идёт двоеточие и тип
  • Имена интерфейсов записываются внутри тела типа для обозначения их реализации

Принадлежность пространству имён: Префикс Type.name (например, Point.draw) указывает, что функция принадлежит пространству имён Point. Он не вызывает никаких неявных binding. Чтобы синтаксис вызова . вроде p.draw() работал, требуется явный binding: Point.draw = draw[0]. Подробности см. в RFC-004 и RFC-010.

3.1.1 Значения полей по умолчанию

Поля типа могут иметь значения по умолчанию, которые можно указать при конструировании:

yaoxiang
// Поля со значениями по умолчанию — необязательны при конструировании
Point: Type = {
    x: Float = 0,
    y: Float = 0
}

// Использование
Point()           // -> Point(x=0, y=0)
Point(x=1)       // -> Point(x=1, y=0)
Point(x=1, y=2) // -> Point(x=1, y=2)

// Поля без значений по умолчанию — обязательны при конструировании
Point2: Type = {
    x: Float,
    y: Float
}

// Использование
Point2(x=1, y=2) // Правильно
Point2()          // Ошибка

Правила:

  • field: Type = expression -> есть значение по умолчанию, необязательно при конструировании
  • field: Type -> нет значения по умолчанию, обязательно при конструировании

3.1.2 Встроенные binding

Внутри определения типа можно напрямую привязать методы:

yaoxiang
// Способ 1: Привязка внешней функции
distance: (a: Point, b: Point) -> Float = { ... }
Point: Type = {
    x: Float = 0,
    y: Float = 0,
    distance = distance[0]    // Привязка к позиции 0
}
// Вызов: p1.distance(p2) -> distance(p1, p2)

// Способ 2: Анонимная функция + позиционная привязка
Point: Type = {
    x: Float = 0,
    y: Float = 0,
    distance: ((a: Point, b: Point) -> Float)[0] = ((a, b) => {
        dx = a.x - b.x
        dy = a.y - b.y
        return (dx * dx + dy * dy).sqrt()
    })
}
// Синтаксис: ((params) => body)[position]
// Вызов: p1.distance(p2) -> distance(p1, p2)

3.2 Interface type

InterfaceType ::= '{' FnField (',' FnField)* ','?
FnField       ::= Identifier ':' FnType
FnType        ::= '(' ParamTypes? ')' '->' TypeExpr

Синтаксис: Интерфейс — это record type, все поля которого являются функциональными типами

yaoxiang
// Определение интерфейса
Drawable: Type = {
    draw: (Surface) -> Void,
    bounding_box: () -> Rect
}

Serializable: Type = {
    serialize: () -> String
}

// Пустой интерфейс
EmptyInterface: Type = {}

Реализация интерфейса: Тип реализует интерфейс, перечисляя имена интерфейсов в конце определения

yaoxiang
// Тип, реализующий интерфейсы
Point: Type = {
    x: Float,
    y: Float,
    Drawable,        // Реализует интерфейс Drawable
    Serializable     // Реализует интерфейс Serializable
}

Прямое присваивание интерфейсу: Конкретный тип можно напрямую присвоить переменной типа интерфейса (структурное подтипование)

yaoxiang
// Прямое присваивание (конкретный тип известен на этапе компиляции -> вызов без накладных расходов)
d: Drawable = Circle(1)
d.draw(screen)        // После компиляции: прямой вызов circle_draw, без vtable

// Возврат из функции (конкретный тип неизвестен на этапе компиляции -> вызов через vtable)
d: Drawable = get_shape()
d.draw(screen)        // Поиск метода через vtable

// Интерфейс как параметр функции
process: (d: Drawable) -> Void = d.draw(screen)

Стратегия оптимизации компилятора:

СценарийРезультат выводаСпособ вызова
Прямое присваивание конкретного типаКонкретный тип определёнПрямой вызов (нулевые накладные расходы)
Возврат из функцииНеизвестенvtable
Гетерогенная коллекцияНесколько типовvtable

3.4 Tuple type

TupleType   ::= '(' TypeList? ')'
TypeList    ::= TypeExpr (',' TypeExpr)* ','?

3.5 Function type

FnType      ::= '(' ParamList? ')' '->' TypeExpr
ParamList   ::= TypeExpr (',' TypeExpr)*

Глава 4: Generics

4.1 Синтаксис параметров generics

Параметры generics являются частью функционального типа и унифицированно используют синтаксис ():

GenericType     ::= Identifier '(' TypeArgList ')'
TypeArgList     ::= TypeExpr (',' TypeExpr)* ','?
TypeBound       ::= Identifier
                 |  Identifier '+' Identifier ('+' Identifier)*

В определении generic-типа (T: Type) — это сигнатура параметров конструктора типа, -> Type — возвращаемый тип:

yaoxiang
List: (T: Type) -> Type = { ... }
Map: (K: Type, V: Type) -> Type = { ... }

В generic-функциях параметры типов также объявляются в сигнатуре, компилятор автоматически выводит их из фактических аргументов:

yaoxiang
map: (T: Type, R: Type) -> ((list: List(T), f: (T) -> R) -> List(R)) = ...

4.2 Определение generic-типов

yaoxiang
// Базовый generic-тип
Option: (T: Type) -> Type = {
    some: (T) -> Option(T),
    none: () -> Option(T)
}

Result: (T: Type, E: Type) -> Type = {
    ok: (T) -> Result(T, E),
    err: (E) -> Result(T, E)
}

List: (T: Type) -> Type = {
    data: Array(T),
    length: Int,
    push: (self: List(T), item: T) -> Void,   // self — это просто соглашение об именовании, не ключевое слово
    get: (self: List(T), index: Int) -> Option(T)
}

4.3 Type inference

yaoxiang
// Компилятор автоматически выводит параметры generics
numbers: List(Int) = List(1, 2, 3)  // Компилятор выводит List(Int)

Глава 5: Type constraint

5.1 Единичный constraint

ConstrainedType ::= '(' Identifier ':' TypeBound ')' TypeExpr
yaoxiang
// Определение интерфейсного типа (как ограничение)
Clone: Type = {
    clone: () -> Clone
}

// Использование ограничения
clone: (T: Clone)(value: T) -> T = value.clone()

5.2 Множественные constraint

yaoxiang
// Синтаксис множественных ограничений
combine: (T: Clone + Add)(a: T, b: T) -> T = {
    a.clone() + b
}

// Сортировка generic-контейнеров
sort: (T: Clone + PartialOrd)(list: List(T)) -> List(T) = {
    result = list.clone()
    quicksort(&mut result)
    return result
}

5.3 Ограничения функциональных типов

yaoxiang
// Ограничения высших порядков
call_twice: (T: Type, F: () -> T)(f: F) -> (T, T) = (f(), f())

compose: (A: Type, B: Type, C: Type, F: (A) -> B, G: (B) -> C)(a: A, f: F, g: G) -> C = g(f(a))

Глава 6: Associated type

6.1 Определение associated type

AssociatedType ::= Identifier ':' TypeExpr
yaoxiang
// Iterator trait (с использованием синтаксиса record type)
Iterator: (T: Type) -> Type = {
    Item: T,                    // Associated type
    next: () -> Option(T),
    has_next: () -> Bool
}

// Использование associated type
collect: (T: Type, I: Iterator(T))(iter: I) -> List(T) = {
    result = List(T)()
    while iter.has_next() {
        if let Some(item) = iter.next() {
            result.push(item)
        }
    }
    return result
}

6.2 Generic Associated Type (GAT)

yaoxiang
// Более сложные associated type
Container: (T: Type) -> Type = {
    Item: T,
    IteratorType: Iterator(T),  // Associated type тоже generic
    iter: () -> IteratorType
}

Глава 7: Compile-time generics

7.1 Параметры compile-time констант

LiteralType   ::= Identifier ':' Int          // Compile-time константа
CompileTimeFn ::= '(' Identifier ':' Int ')' '(' Identifier ')' '->' TypeExpr

Основной дизайн: Использование параметра generics (n: Int) + параметра значения (n: n) различает compile-time константы и runtime значения.

yaoxiang
// Факториал на этапе компиляции: параметр должен быть литералом, известным на этапе компиляции
factorial: (n: Int)(n: n) -> Int = {
    match n {
        0 => 1,
        _ => n * factorial(n - 1)
    }
}

// Массив compile-time констант
StaticArray: (T: Type, N: Int) -> Type = {
    data: Array(T, N),      // Массив с размером, известным на этапе компиляции
    length: N
}

// Способ использования
arr: StaticArray(Int, factorial(5))  // Компилятор вычисляет factorial(5) = 120 на этапе компиляции

7.2 Массивы compile-time констант

yaoxiang
// Использование для типов матриц
Matrix: (T: Type, Rows: Int, Cols: Int) -> Type = {
    data: Array(Array(T, Cols), Rows)
}

// Проверка размерностей на этапе компиляции
identity_matrix: (T: Add + Zero + One, N: Int)(size: N) -> Matrix(T, N, N) = {
    // ...
}

Глава 8: Условные типы

8.1 Условный тип If

IfType        ::= 'If' '(' BoolExpr ',' TypeExpr ',' TypeExpr ')'
yaoxiang
// If на уровне типов
If: (C: Bool, T: Type, E: Type) -> Type = match C {
    True => T,
    False => E
}

// Пример: ветвление на этапе компиляции
NonEmpty: (T: Type) -> Type = If(T != Void, T, Never)
// IsTrue — мост, Assert — уточнение типа (подробности в §8.3)
IsTrue: (b: Bool) -> Type = match b {
    true => Void,      // ⊤, программа продолжается
    false => Never,    // ⊥, дивергенция/ошибка компиляции
}
Assert: (cond: Bool) -> Type = IsTrue(cond)

8.2 Типовые семейства

yaoxiang
// Преобразование типов на этапе компиляции
AsString: (T: Type) -> Type = match T {
    Int => String,
    Float => String,
    Bool => String,
    _ => String
}

8.3 Assert уточняет тип и assert-утверждение

assert и Assert — две стороны одной и той же примитивной операции уточнения — автоматически выбираются механизмом dispatch по критерию "доступны ли свободные переменные предиката на этапе компиляции".

Основная сигнатура: assert: (cond: Bool, ?msg: String | Error) -> Assert(IsTrue(cond))

Правила dispatch:

КритерийРежимПоведение
Все свободные переменные известны на этапе компиляции (параметры generics, compile-time константы)CompileTimeПопадают в pipeline доказательства: true → стирается в Void, false → ошибка компиляции (Never не обитаем)
Существуют runtime свободные переменные (параметры функций, внешний ввод)RuntimeВставляется runtime проверка Bool, в множество flow-sensitive допущений Γ добавляется уточнённый факт

Flow-sensitive множество допущений Γ:

Компилятор поддерживает для каждой точки потока управления множество известных суждений:

yaoxiang
assert(x > 0)       // Γ = {x > 0}
y = x + 1           // Γ = {x > 0, y > 1}  ← SP распространение
mut x = x - 5       // Γ = {}  ← kill set для mut: старые допущения аннулируются

После присваивания переменной с mut все допущения, связанные с этой переменной, удаляются (kill set). При слиянии веток Γ берётся как пересечение допущений веток.


Глава 9: Объединение и пересечение типов

9.1 Объединение типов

TypeUnion     ::= TypeExpr '|' TypeExpr

9.2 Пересечение типов

TypeIntersection ::= TypeExpr '&' TypeExpr

Синтаксис: Пересечение типов A & B представляет тип, удовлетворяющий одновременно A и B

yaoxiang
// Композиция интерфейсов = пересечение типов
DrawableSerializable: Type = Drawable & Serializable

// Использование типа пересечения
process: (T: Drawable & Serializable)(item: T, screen: Surface) -> String = {
    item.draw(screen)
    return item.serialize()
}

Глава 10: Перегрузка и специализация функций

10.1 Перегрузка функций

yaoxiang
// Базовая специализация: использование перегрузки функций (компилятор выбирает автоматически)
sum: (arr: Array(Int)) -> Int = {
    return native_sum_int(arr.data, arr.length)
}

sum: (arr: Array(Float)) -> Float = {
    return simd_sum_float(arr.data, arr.length)
}

// Универсальная реализация
sum: (T: Add)(arr: Array(T)) -> T = {
    result = Zero::zero()
    for item in arr {
        result = result + item
    }
    return result
}

10.2 Специализация под платформу

yaoxiang
// Перечисление типов платформ (стандартная библиотека)
Platform: Type = { X86_64: () -> Platform, AArch64: () -> Platform, RISC_V: () -> Platform, ARM: () -> Platform, X86: () -> Platform }

// P — предопределённое имя параметра generics, представляющее текущую компилируемую платформу
sum: (P: X86_64)(arr: Array(Float)) -> Float = {
    return avx2_sum(arr.data, arr.length)
}

sum: (P: AArch64)(arr: Array(Float)) -> Float = {
    return neon_sum(arr.data, arr.length)
}

Глава 11: Атрибуты типов

В YaoXiang существует только один атрибут типа, который необходимо различать: линейный vs копируемый. Выводится компилятором автоматически.

11.1 Move (передача ownership по умолчанию)

Все типы по умолчанию следуют семантике Move. Присваивание, передача параметров, возврат = передача ownership.

yaoxiang
p: Point = Point(1.0, 2.0)
q = p           // Move, p больше нельзя читать

11.2 Dup (мелкое копирование: копирование дескриптора, совместное использование данных)

Атрибут Dup используется для типов ссылок/токенов. Присваивание значению типа Dup = мелкое копирование — копируется дескриптор/токен, базовые данные совместно используются. Несколько владельцев указывают на одни и те же данные.

ТипАтрибутОписание
&TDupТокен чтения нулевого размера, копирование токена = несколько представлений одних данных
ref TDupRc/Arc копирование = +1 к счётчику ссылок, совместное использование heap-данных
&mut TLinearТокен записи нулевого размера, эксклюзивный, не копируемый
Все остальные типыMoveПередача ownership по умолчанию

Primitive value type (Int, Float, Bool, Char) — это специальная встроенная обработка компилятора: при присваивании автоматически происходит копирование значения, два значения полностью независимы. Это встроенное поведение компилятора, не относящееся к атрибуту типа Dup.

yaoxiang
// &T: Dup, можно свободно создавать алиасы
view: &Point = &p
view2 = view     // Dup: копирование токена, оба действительны
print(view.x)    // Можно использовать
print(view2.x)   // Можно использовать

// &mut T: Linear, не копируемый
mut_ref: &mut Point = &mut p
// r2 = mut_ref  // ❌ &mut T не является Dup, нельзя копировать

11.3 Clone (явное глубокое копирование) и связь с Dup

Clone — это интерфейс явного глубокого копирования. Любой тип может реализовать Clone, предоставив метод .clone().

yaoxiang
// Определение интерфейса Clone (стандартная библиотека)
Clone: Type = {
    clone: () -> Clone
}

// Использование
p: Point = Point(1.0, 2.0)
backup = p.clone()    // Глубокое копирование, p всё ещё можно использовать
p2 = p.clone()        // Можно клонировать многократно

Различия между Dup и Clone:

DupClone
СемантикаМелкое копирование: копирование дескриптора/токена, базовые данные общиеГлубокое копирование: создаётся полная независимая копия
Способ вызоваНеявный (автоматический при присваивании/передаче параметров)Явный (.clone())
Влияние измененийВлияют друг на друга (общие базовые данные)Не влияют друг на друга (независимые копии)
Применимые типыТокены &T, ref TЛюбые типы, реализующие интерфейс Clone
СтоимостьНулевые накладные расходы (токен — тип нулевого размера)Зависит от типа

Dup не влечёт Clone, Clone не влечёт Dup — это два ортогональных понятия:

yaoxiang
// Тип Dup: копирование токена, базовые данные общие
view: &Point = &p
view2 = view        // Dup: копирование токена, оба указывают на один и тот же p
print(view.x)       // Можно использовать
print(view2.x)      // Можно использовать, видите одни и те же данные

// Primitive value type: компилятор автоматически копирует значение (не Dup)
x: Int = 42
y = x               // Копирование значения, x и y полностью независимы
print(x)            // Можно использовать

// Clone: явное глубокое копирование, создание независимой копии
p: Point = Point(1.0, 2.0)
q = p.clone()       // Clone: глубокое копирование, p всё ещё можно использовать
r = p               // Move: передача ownership, потому что Point не Dup и не primitive

Замысел дизайна:

  • Dup используется для типов токенов/ссылок, решая проблему "несколько представлений одних и тех же данных"
  • Clone используется в сценариях, требующих независимых копий, явный вызов делает стоимость видимой
  • Primitive value type (Int/Float/Bool/Char) — это встроенное в компилятор поведение, не относящееся к Dup
  • Большинство пользовательских типов по умолчанию Move, обеспечивая высокую производительность без копирования

Глава 12: Типы borrowing токенов

12.1 Основные концепции

&T и &mut T — это типы токенов компиляции нулевого размера. Они не являются "ссылками", а представляют "типовые доказательства прав доступа".

&T      →  Нулевой размер, замораживает исходные данные (запрещает получение WriteToken в это время),
          при заморозке несколько read-only безопасно → Dup (копируемый)
&mut T  →  Нулевой размер, эксклюзивный read-write (запрещает любые другие токены),
          при эксклюзивном доступе копирование бессмысленно → Linear (не Dup)

Ключевые характеристики:

  • Токены — это обычные типы, следующие тем же правилам области видимости, что и все остальные типы
  • Не требуют аннотаций времени жизни 'a
  • Не требуют специализированного borrow checker — атрибут типа (Dup/Linear) естественно выводит разрешения
  • Полностью исчезают после компиляции, нулевые накладные расходы на runtime

12.2 Базовое использование

yaoxiang
// Сторона метода: объявление типа параметра определяет необходимые разрешения
Point.print: (self: &Point) -> Void = {
    print(self.x)               // Токен &Point предоставляет право на чтение
    print(self.y)
}

Point.shift: (self: &mut Point, dx: Float, dy: Float) -> Void = {
    self.x = self.x + dx        // Токен &mut Point предоставляет право на запись
    self.y = self.y + dy
}

// Сторона вызова: компилятор автоматически выбирает borrowing или Move
p = Point(1.0, 2.0)
p.print()                       // Компилятор автоматически создаёт токен &Point
p.shift(1.0, 1.0)               // Компилятор автоматически создаёт токен &mut Point
p.print()                       // OK, предыдущий токен был освобождён после вызова shift

// Сосуществование нескольких токенов &T — тип Dup позволяет свободно копировать
distance: (a: &Point, b: &Point) -> Float = {
    sqrt((a.x - b.x)**2 + (a.y - b.y)**2)
}
d = distance(p, p2)

12.3 Область действия токенов и их распространение

Токены — обычные типы, поэтому поддерживают все операции обычных типов:

Возврат токенов — токен распространяется вместе с возвращаемым значением:

yaoxiang
// ✅ Дочерний токен и родительский токен возвращаются вместе
Point.get_x: (self: &Point) -> (&Float, &Point) = {
    return (&self.x, self)
}

p = Point(1.0, 2.0)
(px_ref, p) = p.get_x()        // Токен возвращается вызывающему
print(px_ref)                    // OK, токен всё ещё в области видимости

Хранение в структуре — структуры могут содержать поля-токены:

yaoxiang
// ✅ Структура содержит токен как поле
Window: Type = {
    target: Point,
    view: &Point,              // Поле-токен — держит read-only представление target
}

Захват замыканием — замыкание захватывает токен как любое значение:

yaoxiang
// ✅ Замыкание захватывает токен &Float (тип Dup, свободно копируется в замыкание)
filter_by_threshold: (items: List(Point), threshold: &Float) -> List(Point) = {
    items.filter(|p| p.x > threshold)
}

12.4 Автоматический выбор borrowing

Компилятор на стороне вызова автоматически выбирает по следующему приоритету:

1. Если фактический аргумент ещё используется в дальнейшем → создаётся токен (&T или &mut T, по сигнатуре метода)
2. Если фактический аргумент больше не используется → Move
3. Приоритет сопоставления: &T < &mut T < Move
yaoxiang
p = Point(1.0, 2.0)
p.print()          // Параметр print имеет тип &Point → компилятор создаёт токен &Point
p.shift(1.0, 1.0)  // Параметр shift имеет тип &mut Point → компилятор создаёт токен &mut Point
p2 = p             // Больше не используется → Move

12.5 Обнаружение конфликтов токенов

Компилятор выполняет flow-sensitive анализ активности значений токенов, отслеживая состояние каждого токена (активен/перемещён):

yaoxiang
// ❌ &mut и производные &T не могут быть активны одновременно
bad_alias: (p: &mut Point) -> Void = {
    p.x = 10.0                   // ✅ Нормальное использование WriteToken
    print(p.y)
}

// ✅ После окончания области действия токена он автоматически освобождается
good_seq: (p: &mut Point) -> Void = {
    {
        // Внутренняя область видимости
        print(p.x)               // Используется &mut Point
    }
    // Конец внутренней области видимости
    p.x = 10.0                   // ✅ WriteToken всё ещё доступен
}

// ❌ Для одного фактического аргумента нельзя одновременно создавать токен &mut и другие токены
alias_bad: (a: &mut Point, b: &Point) -> Void = { ... }
p = Point(1.0, 2.0)
alias_bad(p, p)                  // ❌ p одновременно получает токены &mut и &

12.6 Внутренняя реализация компилятора: механизм brand

Пользователь никогда не сталкивается с brand. Компилятор внутренне назначает каждому токену компиляционно уникальный идентификатор:

Видимое пользователю         Внутреннее представление компилятора
────────────────────────────────────────────────────────────────
&Point         →  ReadToken(Point, #N)    // #N — компиляционно уникальное целое число
&mut Point     →  WriteToken(Point, #M)   // #M — компиляционно уникальное целое число

Назначение brand:

  • Защита от подделки: Токен можно получить только от владельца-капсулы, нельзя создать из воздуха
  • Отслеживание происхождения: Производные &Float от доступа к полю несут производный brand (#N.field_x), компилятор может отследить до родительского токена
  • Обнаружение конфликтов: WriteToken и производные ReadToken из одного источника не могут быть активны одновременно

Brand полностью исчезают после мономорфизации и инлайнга, в сгенерированном машинном коде их нет. Нулевые накладные расходы на runtime.

12.7 Sum-тип токенов Borrow

&BorrowToken ::= &T          // ReadToken (замораживает исходные данные → Dup безопасен)
               | &mut T      // WriteToken (эксклюзивный read-write → Linear)

12.8 Borrowing токены vs ref

&T / &mut Tref
Что делаетЗаглянуть/изменить на местеСовместное владение
Область действияПо области видимости токенаМеж областей видимости
СтоимостьНулевые накладные расходы (тип нулевого размера, исчезает после компиляции)Rc или Arc (выбирает компилятор)
ЭскапингВозможен (токен распространяется с возвращаемым значением/структурой/замыканием)Это его основное назначение
Меж задачамиНевозможно (токен не реализует межзадачную передачу)Возможно (компилятор автоматически выбирает Arc)
Обнаружение цикловНе затрагиваетсяТихая внутри задачи, lint меж задачами

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

A.1 Определения типов

// === Record type (фигурные скобки) ===

// Record type
Point: Type = { x: Float, y: Float }

// Record type с вариантами (с использованием функциональных полей)
Result: (T: Type, E: Type) -> Type = { ok: (T) -> Result(T, E), err: (E) -> Result(T, E) }

// === Interface type (фигурные скобки, все поля функциональные) ===

// Определение интерфейса
Serializable: Type = { serialize: () -> String }

// Тип, реализующий интерфейс
Point: Type = {
    x: Float,
    y: Float,
    Serializable    // Реализует интерфейс Serializable
}

// === Function type ===

Adder: Type = (Int, Int) -> Int

A.2 Синтаксис generics

// Generic-тип
List: (T: Type) -> Type = { data: Array(T), length: Int }
Result: (T: Type, E: Type) -> Type = { ok: (T) -> Result(T, E), err: (E) -> Result(T, E) }

// Generic-функция
map: (T: Type, R: Type)(list: List(T), f: (T) -> R) -> List(R) = { ... }

// Type constraint
clone: (T: Clone)(value: T) -> T = value.clone()
combine: (T: Clone + Add)(a: T, b: T) -> T = body

// Associated type
Iterator: (T: Type) -> Type = { Item: T, next: () -> Option(T) }

// Compile-time generics
factorial: (n: Int)(n: n) -> Int = { ... }
StaticArray: (T: Type, N: Int) -> Type = { data: Array(T, N), length: N }

// Условный тип
If: (C: Bool, T: Type, E: Type) -> Type = match C { True => T, False => E }

// Специализация функций
sum: (arr: Array(Int)) -> Int = { ... }
sum: (arr: Array(Float)) -> Float = { ... }

A.3 Краткий справочник атрибутов типов

// === Move (по умолчанию) ===
// Все типы по умолчанию Move. Присваивание, передача параметров, возврат = передача ownership

// === Primitive value type (встроенные в компилятор) ===
Int, Float,     // При присваивании автоматическое копирование значения, два значения полностью независимы
Bool, Char      // Не Dup, это встроенная обработка примитивов компилятором

// === Dup (мелкое копирование: копирование дескриптора, общие базовые данные) ===
&T              // Токен чтения нулевого размера, копирование токена = несколько представлений одних данных
ref T           // Rc/Arc копирование = +1 к счётчику ссылок, совместное использование heap-данных

// === Linear ===
&mut T          // Токен записи нулевого размера, Linear (эксклюзивный, не копируемый)

// === Clone (явное глубокое копирование) ===
value.clone()   // Создаёт независимую копию, изменения не влияют на оригинал

A.4 Краткий справочник borrowing токенов

// === Borrowing токены ===
&T              // Токен чтения компиляции нулевого размера, замораживает исходные данные → Dup (копируемый)
&mut T          // Токен записи компиляции нулевого размера, эксклюзивный read-write → Linear (не копируемый)

// Автоматический выбор на стороне вызова
// 1. Фактический аргумент ещё используется → создаётся токен
// 2. Фактический аргумент больше не используется → Move
// 3. Приоритет сопоставления: &T < &mut T < Move

// Распространение токенов
// ✅ Можно возвращать, хранить в структуре, захватывать замыканием
// ❌ Нельзя передавать между задачами (токен не реализует межзадачную передачу)