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

Явный рост Vec · 0.0.21

Реализовано в 0.0.21. Вектор может явно увеличить вместимость, сохраняя происхождение хранилища и возвращая владельца даже при отказе.

vec.reserve увеличивает вместимость существующего вектора в его исходном регионе. push сохраняет прежний контракт: сам не растёт и возвращает Full, если места нет. Изменения синтаксиса языка не требуются.

(next, status) = vec.reserve(move current, region, capacity)
  • current имеет тип vec.Vec<T>, где T копируем.
  • region — доступная именованная привязка исходного региона; передаётся без move.
  • capacity: u32 — желаемая полная вместимость, а не число дополнительных элементов.
  • next безусловно получает владельца вектора.
  • status имеет тип Result<vec.Done, MemoryError>.
  • Вызов требует эффекта alloc.

Поведение

Условие Результат
capacity <= current.capacity Ok, вектор и размещение не меняются; выделений и копирования нет
Запрошен рост и места достаточно Ok, вместимость становится ровно запрошенной; длина и значения сохраняются
Места недостаточно Err(MemoryError.Exhausted), вектор и использованная вместимость региона не меняются
Размер размещения непредставим Err(MemoryError.SizeOverflow), состояние не меняется

Регион уже создан: reserve не обращается к системному heap и сам не порождает OutOfMemory. Используется общий тип MemoryError, поэтому match над ним по-прежнему должен учитывать все варианты.

Все аргументы вычисляются слева направо до вызова, даже если запрос окажется no-op или завершится отказом. Эффекты и traps аргументов сохраняются. При trap cleanup не гарантируется, как и для остальных операций языка. Если новая вместимость вычисляется из vec.capacity(current), вычислите её до вызова: после первого аргумента move current прежнее имя уже недоступно.

Хранилище и стоимость

При росте выделяется новое размещение на capacity элементов и копируется только инициализированный префикс [0,len). Неиспользуемая часть новой вместимости не инициализируется значениями T. Проверки размеров, выравнивания и доступного места выполняются до изменения состояния региона.

Предыдущее размещение остаётся занятым до memory.close: регион не поддерживает индивидуальное освобождение. release нового вектора также не возвращает вместимость. Последовательность частых небольших увеличений может расходовать значительно больше памяти, чем последнее размещение. Стратегию роста выбирает программа; скрытого удвоения вместимости нет.

Рост имеет стоимость копирования len × sizeof(T) байтов, с учётом размера и представления T. Запрос без роста не копирует содержимое. После роста push/pop/get/set сохраняют прежние контракты без выделения хранилища.

Владение и заимствования

Компилятор проверяет совпадение происхождения региона и вектора. Два разных региона одного типа не взаимозаменяемы. Проверка действует даже для no-op: семантика вызова не зависит от догадки о значении аргумента вместимости.

Живой readonly view запрещает reserve, включая no-op, поскольку вызов потребляет владельца. После завершения области заимствования можно увеличить вместимость и создать новый view. Ранее извлечённые независимые значения get/pop остаются корректными.

Происхождение next совпадает с происхождением current, но физическое размещение может измениться. Нераспакованный результат reserve продолжает удерживать зависимость от региона. Передача результата через generic-функции и объединения CFG не снимают эту зависимость.

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

Границы версии

Нет автоматического роста, уменьшения вместимости, переноса в другой регион, индивидуального освобождения старых размещений, mutable views или ресурсных элементов. Модель владения и линейного результата из 0.0.20 остаётся прежней.

Пример: от одного элемента к четырём местам

python3 -m liza run examples/vec_reserve.liza
examples/vec_reserve.liza
// Growth is explicit. Old storage stays reserved until the region closes.
fn main() -> u32 : alloc {
    match memory.create(4096) {
        Err(error) => { return 1 }
        Ok(region) => {
            var result: u32 = 0
            match vec.with_capacity<u32>(region, 1) {
                Err(error) => { result = 2 }
                Ok(initial) => {
                    (full, pushed) = vec.push(move initial, 42)
                    match pushed { Ok(done) => {} Err(error) => { result = 3 } }
                    (larger, reserved) = vec.reserve(move full, region, 4)
                    match reserved {
                        Err(error) => {
                            released = vec.release(move larger)
                            result = 4
                        }
                        Ok(done) => {
                            (next, status) = vec.push(move larger, 7)
                            match status { Ok(push_done) => {} Err(error) => { result = 5 } }
                            {
                                values = vec.view(next)
                                if values.len != 2 || values[0] != 42 || values[1] != 7 { result = 6 }
                            }
                            if vec.capacity(next) != 4 { result = 7 }
                            released = vec.release(move next)
                        }
                    }
                }
            }
            closed = memory.close(move region)
            return result
        }
    }
}

Сначала вектор заполнен одним числом 42. reserve запрашивает четыре места и сохраняет это число. При отказе ветка Err завершает возвращённого владельца; при успехе добавляется 7 и создаётся новый view. Регион закрывается после завершения владельца на обоих путях. Успешное выполнение возвращает 0.

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

Для вместимостей 64, 4096 и 16384 и runtime-входов при O0/O2 проверено: успешный рост вектора из одного u64 копирует ровно 8 байтов независимо от старой вместимости. No-op не копирует данные и не расходует регион. В каждом сценарии остаётся одно выделение системной памяти — создание региона.

Отдельно проверяются нетронутый хвост нового размещения, рост пустого вектора, выравнивание, переполнение размера и успешный повторный запрос после отказа. Это проверка конкретных трактов, а не обещание постоянного времени любого роста: объём копирования увеличивается вместе с длиной и размером элемента.

Где применять монотонный регион

Монотонный регион не является универсальной заменой heap-allocator. Он подходит для временных буферов, пакетов и вычислений с ограниченным временем жизни данных. Повторный рост долгоживущей коллекции накапливает занятые размещения: вместимости 4 → 8 → 16 → 32 для u64 требуют суммарно 480 байтов без учёта выравнивания, хотя последнее размещение занимает 256. Индивидуальный возврат блоков потребует отдельного контракта хранения; release и reserve его не вводят.