Типы-значения против ссылочных типов в Nova
Plan 120 (D215, 2026-05-31): явное руководство по аллокации: стек или куча. См. также: spec/decisions/02-types.md → D215.
Правило скобок
Nova использует синтаксис скобок для кодирования семантики аллокации:
| Синтаксис | Категория | Аллокация | Семантика |
|---|---|---|---|
type X(T1, T2) | позиционный кортеж | стек | значение (копия при передаче) |
type X(name T, ...) | именованный кортеж | стек | значение (копия при передаче) |
type X { field T } | запись | куча (управляемая GC) | ссылка (указатель при передаче) |
type X | A | B | тип-сумма | куча | ссылка |
int, bool, f64, char, u8 | примитивы | регистр | значение |
Выбор между () и {} не произволен: он явно сообщает ожидания по
производительности и времени жизни и людям, и ИИ.
Типы-значения: кортежи на стеке
Позиционные кортежи
type Point(f64, f64) // .0 / .1 access
ro p = Point(1.0, 2.0)
assert(p.0 == 1.0)
Именованные кортежи (Plan 120, D215)
type Vec3(x f64, y f64, z f64) // .x / .y / .z access
type Color(r u8, g u8, b u8, a u8)
ro v = Vec3(x: 1.0, y: 2.0, z: 3.0)
ro c = Color(r: 255, g: 0, b: 128, a: 255)
assert(v.x == 1.0)
assert(c.r == 255)
Именованные кортежи идентичны позиционным по производительности — оба суть C-структуры на стеке, без аллокации в куче и без учёта GC.
Методы на типах-значениях
fn Vec3 @dot(other Vec3) -> f64 =>
@.x * other.x + @.y * other.y + @.z * other.z
fn Vec3 @scale(s f64) -> Vec3 =>
Vec3(x: @.x * s, y: @.y * s, z: @.z * s)
ro v1 = Vec3(x: 1.0, y: 0.0, z: 0.0)
ro v2 = Vec3(x: 0.0, y: 1.0, z: 0.0)
assert(v1.dot(v2) == 0.0) // perpendicular vectors
Ссылочные типы: записи в куче
type User {
id u64
name str
email str
}
ro u = User { id: 1, name: "alice", email: "alice@example.com" }
// u is a pointer to managed heap; GC-tracked
Что когда использовать
| Сценарий | Тип | Почему |
|---|---|---|
| Математика на горячем пути (Vec3, Matrix, Quaternion) | именованный кортеж | нулевой GC, предсказуемая производительность |
| Форматы пикселей/сэмплов (Color, AudioSample) | именованный кортеж | малый размер, дёшево копировать |
| Возврат нескольких значений через FFI | именованный кортеж | умещается в регистры |
| Состояние итератора (idx, end, step) | именованный кортеж | локальное время жизни, без кучи |
| Геометрические примитивы (Point, Rect, AABB) | именованный кортеж | семантика значений |
| Доменные сущности (User, Order, Account) | запись | идентичность, совместное использование между файберами |
| Крупные агрегаты со многими полями | запись | копирование дорого |
| Типы, разделяемые между модулями / персистентные | запись | ссылочная семантика естественна |
Общее правило: маленькое + дешёвое в копировании + семантика значений → кортеж; крупное / несущее идентичность / разделяемое → запись.
Ошибки перекрёстного доступа
Nova жёстко соблюдает границу между именованными и позиционными кортежами:
type Named(x f64, y f64)
type Positional(f64, f64)
fn Named @bad() -> f64 => @.0 // E_TUPLE_POSITIONAL_ACCESS_ON_NAMED
fn Positional @bad() -> f64 => @.x // E_TUPLE_NAMED_ACCESS_ON_POSITIONAL
Сравнение с другими языками
| Язык | Стековой тип-значение с именованными полями |
|---|---|
| Rust | struct Point { x: f64, y: f64 } — стек по умолчанию |
| Swift | struct Point { let x: Double; let y: Double } — тип-значение |
| C# | struct Point { public double X; public double Y; } — тип-значение |
| Go | type Point struct { X, Y float64 } — решает escape-анализ |
| Nova | type Point(x f64, y f64) — явно стек, синтаксис скобок |
Выбор скобок в Nova (() vs {}) делает решение об аллокации явным в точке
объявления типа — не нужно читать доки или помнить правила языка.