Перейти к содержанию

Линейные результаты и bounded Vec · 0.0.20

Реализовано в 0.0.20. Линейный результат и bounded Vec<T> сохраняют владение, зависимости от региона и лексические readonly-заимствования.

Запустить законченный пример:

python3 -m liza run examples/bounded_vec.liza

Построчный разбор этого примера объясняет каждую передачу владения, обработку ошибок и порядок освобождения ресурсов.

Этап состоит из двух последовательных частей:

  1. Универсальный линейный составной результат, потребляющая деструктуризация, перенос владения и зависимостей хранилища через функции и CFG.
  2. Bounded vec.Vec<T> для копируемых элементов, без роста и релокации.

Линейное произведение

Внутренний тип Product<A,B> объединяет два значения с независимыми обязательствами владения. Он не является специальным результатом вектора. Публичный конструктор и доступный пользователю generic-тип Product в этой версии не вводятся. Тип результата выводится из вызова.

(next, item) = vec.pop(move current)

Правая часть вычисляется ровно один раз. Затем результат целиком потребляется, а оба компонента передаются двум различным новым привязкам. Существующие привязки не переобъявляются и не затеняются этой конструкцией. Для цикла используется отдельное присваивание изменяемому владельцу:

(next, status) = vec.push(move current, value)
current = move next

Для ранее связанного результата сохраняется явная передача ресурса:

result = vec.pop(move current)
(next, item) = move result
// result больше недоступен

Произведение линейно независимо от свойств компонентов. copy result, неявное копирование, повторное извлечение и потеря результата запрещены. Деструктуризация не разрешает частичное перемещение и не состоит из двух независимых move result.field. После неё доступность, ресурсные обязанности и происхождение передаются каждому компоненту по его собственному типу. Ресурсный компонент нельзя молча отбросить; на нормальном выходе его нужно завершить или передать дальше.

Семантическая операция:

%result = call vec.pop(%current)
%next, %item = unpack.consume %result

Потребление целого результата атомарно на уровне семантики. Это не обещание аппаратной атомарности или синхронизации потоков. Сама операция не выделяет память и не вводит runtime-ошибки: некорректная форма и нарушение владения диагностируются до исполнения. Эффекты и traps правой части сохраняются.

Функции, варианты и граница первой реализации

Целая передача существующего произведения через функцию разрешена, в том числе через уже существующую generic-функцию:

fn identity<T>(value: T) -> T {
    return move value
}

result = vec.pop(move current)
forwarded = identity(move result)
(next, item) = move forwarded

Это не разрешение вручную конструировать произведения, изменять их форму, извлекать отдельные поля из непотреблённого результата или хранить произведения в пользовательских агрегатах. Вложенная деструктуризация также отложена. После целого разбора компоненты можно возвращать независимо, если обязанности по остальным компонентам выполнены.

Компонент в модели IR может быть независимым значением, move-only ресурсом, значением с зависимостью от региона или вариантом, условно содержащим ресурс. unpack.consume переносит эти сведения без отдельной системы ownership для Vec. Например, зависимость в компоненте Result<Buffer<T>,E> остаётся условной: Ok владеет буфером, Err не содержит этой зависимости. Извлечение компонента не означает извлечение его payload или завершение его владения.

Ограничения консервативного анализа происхождения из 0.0.19 сохраняются. Произведение не должно позволять обойти запрет преждевременного закрытия региона через временные аргументы, вызовы, слияния ветвей или обратные рёбра циклов.

Vec: вместимость и инициализированный префикс

vec.Vec<T> — move-only владелец последовательности в регионе. Вместимость задаётся при создании и не меняется; логическая длина изменяется.

0 <= len <= capacity
[0, len)        — инициализированные значения T
[len, capacity) — недоступное хранилище, не значения T

Создание не требует начального значения T и не выполняет семантическую нулевую инициализацию всей вместимости. До успешного push элементов нет. Чтение, get, set и view ограничиваются длиной, а не вместимостью.

Названия типов и операций принадлежат библиотечному пространству vec. Связанные с типом методы не вводятся.

Операция Результат Контракт
vec.with_capacity<T>(region, capacity: u32) Result<vec.Vec<T>, MemoryError> Эффект alloc, длина 0, новая зависимость от региона
vec.push(move current, value: T) Product<vec.Vec<T>, Result<vec.Done, vec.Error>> При успехе добавляет элемент; при Full сохраняет состояние
vec.pop(move current) Product<vec.Vec<T>, Option<T>> Извлекает последний элемент; пустой вектор возвращает None
vec.get(current, index: u32) Option<T> Возвращает независимое значение при index < len, иначе None
vec.set(move current, index: u32, value: T) Product<vec.Vec<T>, Result<vec.Done, vec.Error>> Заменяет существующий элемент; при OutOfBounds сохраняет состояние
vec.len(current) u32 Логическая длина
vec.capacity(current) u32 Фиксированная вместимость
vec.view(current) View<T> Readonly-доступ только к инициализированному префиксу
vec.release(move current) u32, всегда 0 Завершает зависимость, не возвращая вместимость региону

vec.Done — пустое значение успеха. vec.Error содержит Full и OutOfBounds. Произведение в таблице обозначает внутренний тип, а не доступную пользователю аннотацию. Владение вектором возвращается безусловно, отдельно от статуса операции. Оборачивать весь результат push в Result<Product<...>, Error> для этого API не требуется: ошибка не должна терять владельца.

Порядок вычисления, ошибки и повторное использование слота

Все аргументы вычисляются слева направо до начала библиотечной операции:

(next, status) = vec.push(move current, expensive())

Если expensive() вызывает trap, push не начинается. Перемещение current уже произошло; нормальное выполнение не продолжается, cleanup не гарантируется. Проверка заполненности также не отменяет вычисление аргументов.

При Full содержимое, длина, вместимость и размещение возвращённого вектора не меняются. Нет частично добавленного элемента, видимого после ошибки. Успешный push инициализирует следующий слот и увеличивает длину.

Успешный pop возвращает независимое значение T и уменьшает длину. Освобождённый слот не обязан обнуляться. Его байты больше не обозначают доступное значение; следующий успешный push записывает туда новое значение. get удалённого индекса возвращает None. Извлечённое значение может пережить освобождение вектора и закрытие региона, включая случай крупного составного T.

Для будущих ресурсных элементов уменьшения длины будет недостаточно: pop должен передавать владение элементом. Такие элементы в 0.0.20 запрещены; универсальное произведение лишь сохраняет возможность выразить этот контракт позже.

Заимствования и стоимость

Заимствуемые аргументы — доступные именованные привязки. Действующий readonly view консервативно запрещает push, pop, set, перемещение и release вектора до конца лексической области. Полученный позже view видит новую длину. Регион нельзя переместить или закрыть при живом векторе или потенциально владеющем результате его создания/операции.

Выделение требует проверки размера, выравнивания и вместимости региона; при ошибке состояние региона сохраняется. После создания push, pop, get, set, len, capacity, view и release не выделяют новое хранилище. Размещение не меняется. Стоимость копирования одного T зависит от его размера; независимый результат get или pop может требовать снимка одного элемента. Это не должно превращаться в копирование всей вместимости вектора.

Критерии приёмки

Область Обязательная проверка
Произведение Одна оценка RHS, целое потребление, два новых имени, запрет повторного извлечения и потери ресурса
Компоненты Независимые значения, ресурсы и условные зависимости внутри вариантов в общей модели IR
Функции / CFG Передача целого результата через generic-функцию; обязанности на return, break, continue и слияниях
Инициализация Доступен только [0,len); при создании не требуется произвольное начальное T
Пустые случаи Вместимость 0, Full при push, None при pop/get, неизменность состояния
Повторное использование pop → get удалённого индекса → новый push; старый извлечённый снимок независим
Ошибки Full и OutOfBounds возвращают прежний вектор; эффекты аргументов и обязательные traps сохраняются
Views Запрет изменений и завершения владельца при живом заимствовании; правильная длина нового view
Стоимость Runtime-входы; отсутствие malloc и полных копий хранилища в выбранном цикле push/pop/set; отдельная проверка крупных элементов
Платформы Linux, macOS, Windows; эквивалентные значения, эффекты и traps при O0/O2

Автоматический рост, релокация, remove, mutable views, частичный move, коллекции ресурсов, пользовательские аллокаторы, строки и Map не входят в этот этап.

Проверенная стоимость

В выбранном цикле set → pop → push с runtime-входами и вместимостью 64, 4096 и 16384 элементов LLVM IR и машинный код при O0/O2 не содержат аллокаций или полного копирования хранилища. На регион приходится одно выделение и одно освобождение; отказ выделения проверяется отдельно. Создание пустого вектора оставляет байты неиспользуемой вместимости нетронутыми.

Это проверка конкретного тракта с u64, а не обещание одинаковой стоимости для всех T. Отдельные проверки крупных элементов подтверждают независимость результатов get и pop после перезаписи слота и закрытия региона.