Типы-значения против ссылочных типов в 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

Сравнение с другими языками

ЯзыкСтековой тип-значение с именованными полями
Ruststruct Point { x: f64, y: f64 } — стек по умолчанию
Swiftstruct Point { let x: Double; let y: Double } — тип-значение
C#struct Point { public double X; public double Y; } — тип-значение
Gotype Point struct { X, Y float64 } — решает escape-анализ
Novatype Point(x f64, y f64)явно стек, синтаксис скобок

Выбор скобок в Nova (() vs {}) делает решение об аллокации явным в точке объявления типа — не нужно читать доки или помнить правила языка.