Спецификация системы типов
Настоящий документ определяет спецификацию системы типов языка программирования 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 проявляется следующим образом:
- Иерархия вселенных (RFC-010):
Type₀ : Type₁ : Type₂ …избегает логических парадоксов (Type: Type) (парадокс Жирара) - Типовые семейства (RFC-011): Case analysis + рекурсивные вызовы натуральных чисел
Nat(Zero/Succ)соответствуют аксиомам Пеано — при условии проверки компилятором терминированности - Условные типы (RFC-011):
If: (C: Bool, T: Type, E: Type) -> Typeсоответствует case-дизъюнкции в логике - Зависимые от значений типы (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 / false | 1 байт |
Int | — | Знаковое целое | 8 байт |
Uint | — | Беззнаковое целое | 8 байт |
Float | — | Число с плавающей точкой | 8 байт |
String | — | Строка в кодировке UTF-8 | переменный |
Char | — | Символ Unicode | 4 байта |
Bytes | — | Сырые байты | переменный |
Целые с битовой шириной: Int8, Int16, Int32, Int64, Int128 Float с битовой шириной: Float32, Float64
2.2 Never и Void: ⊥ и ⊤
Never и Void — логические примитивы системы типов, соответственно представляющие ложь (⊥) и истину (⊤).
Never (⊥, ложь/пустой тип) — три непреложных свойства:
- Нуль конструкторов: Никакой литерал или выражение не может произвести значение типа
Never.x: Never = ...— нечего написать справа. - Принцип взрыва:
Never <: Tвыполняется для любого типаT.assert(false)возвращаетNever, после чего код может пройти проверку типов (хотя никогда не выполнится). - Маркер дивергенции:
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 // Ограничение интерфейса// Простой 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 Значения полей по умолчанию
Поля типа могут иметь значения по умолчанию, которые можно указать при конструировании:
// Поля со значениями по умолчанию — необязательны при конструировании
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
Внутри определения типа можно напрямую привязать методы:
// Способ 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, все поля которого являются функциональными типами
// Определение интерфейса
Drawable: Type = {
draw: (Surface) -> Void,
bounding_box: () -> Rect
}
Serializable: Type = {
serialize: () -> String
}
// Пустой интерфейс
EmptyInterface: Type = {}Реализация интерфейса: Тип реализует интерфейс, перечисляя имена интерфейсов в конце определения
// Тип, реализующий интерфейсы
Point: Type = {
x: Float,
y: Float,
Drawable, // Реализует интерфейс Drawable
Serializable // Реализует интерфейс Serializable
}Прямое присваивание интерфейсу: Конкретный тип можно напрямую присвоить переменной типа интерфейса (структурное подтипование)
// Прямое присваивание (конкретный тип известен на этапе компиляции -> вызов без накладных расходов)
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 — возвращаемый тип:
List: (T: Type) -> Type = { ... }
Map: (K: Type, V: Type) -> Type = { ... }В generic-функциях параметры типов также объявляются в сигнатуре, компилятор автоматически выводит их из фактических аргументов:
map: (T: Type, R: Type) -> ((list: List(T), f: (T) -> R) -> List(R)) = ...4.2 Определение generic-типов
// Базовый 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
// Компилятор автоматически выводит параметры generics
numbers: List(Int) = List(1, 2, 3) // Компилятор выводит List(Int)Глава 5: Type constraint
5.1 Единичный constraint
ConstrainedType ::= '(' Identifier ':' TypeBound ')' TypeExpr// Определение интерфейсного типа (как ограничение)
Clone: Type = {
clone: () -> Clone
}
// Использование ограничения
clone: (T: Clone)(value: T) -> T = value.clone()5.2 Множественные constraint
// Синтаксис множественных ограничений
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 Ограничения функциональных типов
// Ограничения высших порядков
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// 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)
// Более сложные 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 значения.
// Факториал на этапе компиляции: параметр должен быть литералом, известным на этапе компиляции
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 констант
// Использование для типов матриц
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 ')'// 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 Типовые семейства
// Преобразование типов на этапе компиляции
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 множество допущений Γ:
Компилятор поддерживает для каждой точки потока управления множество известных суждений:
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 '|' TypeExpr9.2 Пересечение типов
TypeIntersection ::= TypeExpr '&' TypeExprСинтаксис: Пересечение типов A & B представляет тип, удовлетворяющий одновременно A и B
// Композиция интерфейсов = пересечение типов
DrawableSerializable: Type = Drawable & Serializable
// Использование типа пересечения
process: (T: Drawable & Serializable)(item: T, screen: Surface) -> String = {
item.draw(screen)
return item.serialize()
}Глава 10: Перегрузка и специализация функций
10.1 Перегрузка функций
// Базовая специализация: использование перегрузки функций (компилятор выбирает автоматически)
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 Специализация под платформу
// Перечисление типов платформ (стандартная библиотека)
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.
p: Point = Point(1.0, 2.0)
q = p // Move, p больше нельзя читать11.2 Dup (мелкое копирование: копирование дескриптора, совместное использование данных)
Атрибут Dup используется для типов ссылок/токенов. Присваивание значению типа Dup = мелкое копирование — копируется дескриптор/токен, базовые данные совместно используются. Несколько владельцев указывают на одни и те же данные.
| Тип | Атрибут | Описание |
|---|---|---|
&T | Dup | Токен чтения нулевого размера, копирование токена = несколько представлений одних данных |
ref T | Dup | Rc/Arc копирование = +1 к счётчику ссылок, совместное использование heap-данных |
&mut T | Linear | Токен записи нулевого размера, эксклюзивный, не копируемый |
| Все остальные типы | Move | Передача ownership по умолчанию |
Primitive value type (Int, Float, Bool, Char) — это специальная встроенная обработка компилятора: при присваивании автоматически происходит копирование значения, два значения полностью независимы. Это встроенное поведение компилятора, не относящееся к атрибуту типа Dup.
// &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().
// Определение интерфейса Clone (стандартная библиотека)
Clone: Type = {
clone: () -> Clone
}
// Использование
p: Point = Point(1.0, 2.0)
backup = p.clone() // Глубокое копирование, p всё ещё можно использовать
p2 = p.clone() // Можно клонировать многократноРазличия между Dup и Clone:
| Dup | Clone | |
|---|---|---|
| Семантика | Мелкое копирование: копирование дескриптора/токена, базовые данные общие | Глубокое копирование: создаётся полная независимая копия |
| Способ вызова | Неявный (автоматический при присваивании/передаче параметров) | Явный (.clone()) |
| Влияние изменений | Влияют друг на друга (общие базовые данные) | Не влияют друг на друга (независимые копии) |
| Применимые типы | Токены &T, ref T | Любые типы, реализующие интерфейс Clone |
| Стоимость | Нулевые накладные расходы (токен — тип нулевого размера) | Зависит от типа |
Dup не влечёт Clone, Clone не влечёт Dup — это два ортогональных понятия:
// Тип 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 Базовое использование
// Сторона метода: объявление типа параметра определяет необходимые разрешения
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 Область действия токенов и их распространение
Токены — обычные типы, поэтому поддерживают все операции обычных типов:
Возврат токенов — токен распространяется вместе с возвращаемым значением:
// ✅ Дочерний токен и родительский токен возвращаются вместе
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, токен всё ещё в области видимостиХранение в структуре — структуры могут содержать поля-токены:
// ✅ Структура содержит токен как поле
Window: Type = {
target: Point,
view: &Point, // Поле-токен — держит read-only представление target
}Захват замыканием — замыкание захватывает токен как любое значение:
// ✅ Замыкание захватывает токен &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 < Movep = Point(1.0, 2.0)
p.print() // Параметр print имеет тип &Point → компилятор создаёт токен &Point
p.shift(1.0, 1.0) // Параметр shift имеет тип &mut Point → компилятор создаёт токен &mut Point
p2 = p // Больше не используется → Move12.5 Обнаружение конфликтов токенов
Компилятор выполняет flow-sensitive анализ активности значений токенов, отслеживая состояние каждого токена (активен/перемещён):
// ❌ &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 T | ref | |
|---|---|---|
| Что делает | Заглянуть/изменить на месте | Совместное владение |
| Область действия | По области видимости токена | Меж областей видимости |
| Стоимость | Нулевые накладные расходы (тип нулевого размера, исчезает после компиляции) | 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) -> IntA.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
// Распространение токенов
// ✅ Можно возвращать, хранить в структуре, захватывать замыканием
// ❌ Нельзя передавать между задачами (токен не реализует межзадачную передачу)