Vec[T] — нативный динамический массив Nova

Vec[T] — generic-растущий массив, реализованный целиком на Nova поверх аллокации сырых указателей (RawMem.alloc). Доступен как std.collections.vec_owned.Vec.

Когда использовать Vec[T]

Используй []T (встроенный срез) по умолчанию. Переходи на Vec[T], когда:

  • Тип элемента Tvalue-structOption[U], именованный кортеж или value-record — чьё in-memory представление шире 8 байт (int64-слота, используемого внутренней erasure-моделью []T).
  • Нужно типизированное хранилище, где каждый элемент лежит в своём реальном C-типе без боксинга.

Для примитивов и heap-record типов []T и Vec[T] на практике ведут себя идентично; для краткости предпочитай []T.

Быстрый старт

import std.collections.vec_owned.{Vec}

fn main() -> () {
    // Build a vector from a literal element list (D259: `of`, not `from([…])`)
    mut v = Vec[int].of(10, 20, 30)
    assert(v.len() == 3)

    // Push and pop
    v.push(40)
    assert(v.pop() == Some(40))

    // Index access
    assert(v.get(0) == Some(10))
    assert(v.get(99) == None)

    // Iteration
    for x in v {
        println(x)    // 10, 20, 30
    }

    // Value-struct elements work correctly
    mut opts = Vec[Option[int]].new()
    opts.push(Some(1))
    opts.push(None)
    opts.push(Some(3))
    assert(opts.get(1) == Some(None))
}

Конструкция

ВызовРезультат
Vec[T].new()Пустой вектор, cap = 0, без аллокации
Vec[T].with_capacity(n)Пустой вектор, преаллоцировано n слотов под элементы
Vec[T].of(a, b, c)Вектор из литерального списка элементов (variadic) — одна аллокация
existing.clone()Конвертация (deep-copy) существующей коллекции / []T

of vs .clone() — что когда использовать (Plan 153.1 / D259)

Vec[T].from(coll) RETRACTED (2026-07-20, Plan 200 П16) — это был ровно coll.clone() (глубокая, поэлементная копия через Clone, D230), так что выделенный статический конструктор был избыточен. Используй .clone() напрямую.

Две различные роли — не путай их:

  • Построение из литерального списка элементов → Vec[T].of(a, b, c) (variadic). Как vec![a, b, c] в Rust. Берёт элементы напрямую: одна аллокация.
  • Конвертация существующей коллекции → existing.clone(). Как iter.clone() в Rust — глубокая, независимая копия того, что у тебя уже есть.
ro a = Vec[int].of(1, 2, 3)        // ✅ literal list → of (1 allocation)
ro b = Vec[int].new()              // ✅ empty
ro c = other_vec.clone()           // ✅ convert (deep-copy) an existing collection

Почему литерал никогда не идёт по конверсионному пути. Согласно D239 массив-литерал [1, 2, 3] уже является Vec[int] — одна аллокация на самом литерале. Оборачивание его во второй вызов конструктора (старый антипаттерн from([1, 2, 3])) скопировало бы его во второй буфер, суммарно две аллокации, против одной у Vec[int].of(1, 2, 3). Прибереги .clone() для конвертации коллекции, которая у тебя уже есть.

Когда тип элемента уже зафиксирован контекстом, конструктор даже не нужен — голый литерал [a, b, c] и есть Vec[T] (D239). of — только для inline-аннотации типа (позиция возврата, generic-контекст); .clone() — только для конвертации коллекции, которую ты уже держишь.

Справочник методов

Размер и ёмкость

МетодСигнатураОписание
len@len() -> intЧисло живых элементов
cap@cap() -> intЧисло аллоцированных слотов
is_empty@is_empty() -> boolИстина, когда len == 0
reservemut @reserve(additional int) -> ()Гарантировать место под additional элементов
shrink_to_fitmut @shrink_to_fit() -> ()Уменьшить ёмкость ровно до len
shrink_tomut @shrink_to(min_cap int) -> ()Уменьшить ёмкость до max(len, min_cap)

Добавление и удаление элементов

МетодСигнатураОписание
pushmut @push(v T) -> ()Добавить в конец; растёт ×2 при необходимости
popmut @pop() -> Option[T]Удалить и вернуть последний элемент
insertmut @insert(i int, v T) -> ()Вставить по индексу i, сдвигая вправо; panic при i > len
removemut @remove(i int) -> TУдалить по i, сдвигая влево; panic вне границ
swap_removemut @swap_remove(i int) -> TO(1)-удаление: обмен с последним, затем pop; не сохраняет порядок
clearmut @clear() -> ()Установить len = 0, буфер сохраняется
truncatemut @truncate(n int) -> ()Укоротить до n элементов; no-op при n >= len

Доступ

МетодСигнатураОписание
get@get(i int) -> Option[T]Элемент по индексу, с проверкой границ
get_mutmut @get_mut(i int) -> Option[*mut T]Сырой указатель на слот; валиден до следующего realloc
first@first() -> Option[T]Первый элемент
last@last() -> Option[T]Последний элемент

Массовые операции

МетодСигнатураОписание
extendmut @extend(items []T) -> ()Добавить все элементы из среза []T
appendmut @append(mut other Vec[T]) -> ()Перенести всё из other в конец; other становится пустым
retainmut @retain(pred fn(T) -> bool) -> ()Оставить только элементы, где pred возвращает true; O(n)
reversemut @reverse() -> ()Развернуть живые элементы на месте

Срезы и представления (Plan 153.4 / D262)

Zero-copy []T-представления того же типа, разделяющие родительский буфер (cap == len); типа Slice нет. Мутация с realloc на представлении отвязывает его (модель Go, GC-safe); владеющая копия — clone()/to_vec(). Ленивые срезовые итераторы chunks/chunks_exact/rchunks/windows живут в явно импортируемом ленивом модуле (import std.collections.vec_lazy) — они отдают []T-представления по одному без внешней аллокации Vec (slice::chunks/ windows в Rust). См. vec-internals.md → Slices & views.

МетодСигнатураОписание
as_slice@as_slice() -> []TRead-only zero-copy целое представление (НЕ копия)
as_slice (mut)mut @as_slice() -> mut []TЗаписывающее целое представление (recv-mut overload, как mut @as_ptr)
split_at@split_at(i int) -> ([]T, []T)Два смежных представления по i; requires 0 <= i <= len (OOB panic)
split_first@split_first() -> Option[(T, []T)]Первый элемент + хвостовое представление; пустой → None
split_last@split_last() -> Option[(T, []T)]Последний элемент + головное представление; пустой → None
first_n@first_n(n int) -> []TПрефиксное представление; кламп (n > len → всё, n <= 0 → пусто)
last_n@last_n(n int) -> []TСуффиксное представление; кламп (как first_n)

Ленивые срезовые итераторы (import std.collections.vec_lazy; каждый -> BoxIter[[]T], requires n > 0):

МетодСигнатураОписание
chunks@chunks(n int) -> BoxIter[[]T]Непересекающиеся куски по n; последний кусок короткий
chunks_exact@chunks_exact(n int) -> BoxIter[[]T]Только полные куски по n; короткий хвост отбрасывается
rchunks@rchunks(n int) -> BoxIter[[]T]Непересекающиеся куски с конца (отдаются сзади-наперёд); ведущий кусок короткий
windows@windows(n int) -> BoxIter[[]T]Пересекающиеся представления ширины n (n-1 общих); n > len → пусто

Конверсия

МетодСигнатураОписание
iter@iter() -> VecIter[T]Итератор-курсор по индексу

Протоколы

ПротоколМетодПримечания
Iterable[T]@iter() / VecIter[T].@next()Синтаксис for x in v
Equal@equal(other Vec[T]) -> boolПоэлементно, через сравнение as_slice
Clone@clone() -> Vec[T]Аллоцирует новый буфер, копирует все элементы
Display@display(mut sb StringBuilder) -> ()Формат: Vec[e0, e1, ..., eN-1]
Debug@debug(mut sb StringBuilder) -> ()Тот же формат, для ${v:?}

Примеры

Рост и итерация

mut v = Vec[int].new()
for i in 0..10 { v.push(i) }
assert(v.len() == 10)
for x in v { print("${x} ") }    // 0 1 2 3 ... 9

Вставка и удаление

mut v = Vec[int].of(1, 2, 4, 5)
v.insert(2, 3)                     // [1, 2, 3, 4, 5]
assert(v.remove(0) == 1)           // [2, 3, 4, 5]
assert(v.swap_remove(0) == 2)      // [5, 3, 4] (order disrupted)

Фильтр через retain

mut v = Vec[int].of(1, 2, 3, 4, 5, 6)
v.retain(|x| x % 2 == 0)
assert(v.as_slice() == [2, 4, 6])

Срезы и представления (zero-copy)

ro v = Vec[int].of(1, 2, 3, 4, 5)

// split_at: two views of the same buffer (contract 0 <= i <= len)
ro (l, r) = v.split_at(2)
assert(l.len() == 2 && r.len() == 3 && l[0] == 1 && r[0] == 3)

// first_n / last_n clamp ("take up to N")
assert(v.first_n(3).len() == 3)
assert(v.first_n(99).len() == 5)        // clamped to len
assert(v.last_n(2).equal(Vec[int].of(4, 5)))

// mut @as_slice writes through to the parent (until detach)
mut w = Vec[int].of(1, 2, 3)
mut s = w.as_slice()
s[0] = 99
assert(w[0] == 99)

// detach-on-resize: pushing onto a cap==len view reallocs; parent untouched
mut head = w.first_n(2)
head.push(7)                            // detaches into a fresh buffer
assert(w.equal(Vec[int].of(99, 2, 3)))  // parent unchanged

Value-struct элементы

// Option[int] is a value-struct. []Option[int] would erase it.
// Vec[Option[int]] stores each NovaOpt_nova_int struct inline.
mut v = Vec[Option[int]].new()
v.push(Some(42))
v.push(None)
assert(v.get(0) == Some(Some(42)))
assert(v.get(1) == Some(None))

Управление ёмкостью

mut v = Vec[int].with_capacity(100)
assert(v.cap() >= 100)
for i in 0..50 { v.push(i) }
v.shrink_to_fit()
assert(v.cap() == 50)

Клон и равенство

ro a = Vec[int].of(1, 2, 3)
mut b = a.clone()
b.push(4)
assert(a.len() == 3)          // original unchanged
assert(b.len() == 4)
assert(a.equals(Vec[int].of(1, 2, 3)))

Небезопасный get_mut

get_mut возвращает сырой мутабельный указатель для обновления на месте без копирования:

mut v = Vec[int].of(10, 20, 30)
if Some(p) = v.get_mut(1) {
    unsafe { *p = 99 }
}
assert(v.get(1) == Some(99))

Примечание: указатель инвалидируется любым последующим push, insert, reserve или другим realloc-способным вызовом.

Сравнение с []T

[]TVec[T]
Выбор по умолчаниюДаНет
Примитивные элементыПолная типизацияПолная типизация
Record-элементыУказатель-в-слотеУказатель-в-слоте
Элементы Option[U]int64-erasure (сломано)Inline-struct (правильно)
Элементы именованных кортежейint64-erasure (сломано)Inline-struct (правильно)
Value-record элементыint64-erasure (сломано)Inline-struct (правильно)
Итерация for x inВстроеннаяЧерез VecIter
Компилятор-магияДа (NOVA_ARRAY_DECL)Нет (чистый Nova)
Синтаксис литерала [1,2,3]Да ([1,2,3] это Vec по D239)Да — [1,2,3], или Vec[T].of(1,2,3) для inline-типа (НЕ from([…]), D259)

Заметки о производительности

  • push: амортизированный O(1). Начальная ёмкость 8; удваивается на каждом realloc.
  • get / get_mut: O(1) арифметика указателей.
  • insert / remove: O(n) сдвиг элементов — предпочитай swap_remove, когда порядок не важен.
  • as_slice: O(n) копия — избегай на горячих путях; итерируй напрямую через for x in v.
  • Аллокация буфера использует RawMem.alloc (Boehm GC-tracked, обнулённая, выравнивание 8 байт). GC консервативно сканирует буфер, так что указатели на элементы внутри буфера держат свои цели живыми.

Ссылки на спеку

  • D231 — API аллокатора RawMem.
  • D232 — формальная спека Vec[T].
  • D216 §6 — codegen арифметики указателей.
  • Q-vec-vs-slice — гайд принятия решения.