Один элемент — весь путь владения
Пример examples/bounded_vec.liza создаёт вектор вместимостью два элемента,
кладёт в него 42, извлекает это значение и освобождает ресурсы.
При успехе программа возвращает 0. В консоль она ничего не выводит.
Здесь всего один элемент, но виден весь путь: регион → вектор → составной результат → независимое значение → завершение владения.
Если match, Ok/Err или move пока незнакомы, начните с
основ языка — они разобраны вместе на небольших программах.
Полная программа
Код ниже подставляется из исполняемого примера в репозитории. Номера строк относятся к этому файлу. Выделены передачи владения и завершение ресурсов.
Запуск из корня репозитория:
Строки 1–2: функция, которая передаёт значение дальше
Строка 1 — комментарий. Он формулирует три свойства примера: фиксированное размещение, изменяемый инициализированный префикс и явное владение.
Строка 2 определяет generic-функцию:
T — параметр типа. Функция принимает значение типа T и возвращает значение
того же типа. move value передаёт его вызывающей стороне; использовать
прежнюю привязку value после перемещения нельзя.
Здесь нет требования копируемости: функция ничего не копирует. Поэтому она
подходит и для линейного результата pop. Эффект не указан — функция pure.
Её объявление само по себе ничего не выполняет: вызов будет в строке 15.
Строка 3 — пустая строка для чтения кода, без влияния на выполнение.
Строки 4–7: создаём регион и разбираем результат
Строка 4: fn main()->u32 : alloc — точка входа. Она возвращает код
завершения типа u32. Эффект alloc разрешает операции управления хранилищем.
io не нужен: программа ничего не читает из файлов и не печатает.
Строка 5: memory.create(4096) запрашивает регион с вместимостью 4096 байтов.
Результат — Result<memory.Region, MemoryError>. match выбирает ветку
по результату создания.
Строка 6: если получен Err(error), регион не создан. Программа сразу
возвращает 1; закрывать ещё нечего. Имя error связывает причину отказа,
но этот короткий пример её не выводит.
Строка 7: Ok(region) передаёт созданный регион в привязку region.
Теперь на нормальном выходе программа обязана закрыть его либо передать дальше.
В этом примере закрытие произойдёт в строке 24.
Строки 8–11: создаём пустой вектор
Строка 8: var result:u32=0 создаёт изменяемую привязку кода завершения.
Ноль означает успех. Последующие проверки могут присвоить ей другой код.
Это обычное целое значение, а не ресурс.
Строка 9: vec.with_capacity<u32>(region,2) резервирует в регионе место
для двух элементов u32 и возвращает Result<vec.Vec<u32>, MemoryError>.
Сразу после успеха:
| Свойство | Значение |
|---|---|
| Тип элемента | u32 |
| Вместимость | 2 |
| Длина | 0 |
| Доступные элементы | Нет |
Две ячейки хранилища ещё не являются двумя значениями u32.
Инициализатор не нужен: значения появятся только после успешного push.
Аргумент region передаётся без move. Вектор не забирает регион себе,
а становится зависимым от его хранилища. Пока эта зависимость существует,
регион нельзя закрыть или переместить.
Строка 10: при отказе выделения записываем result=2.
Здесь намеренно нет раннего return: регион уже создан, поэтому выполнение
пройдёт к его закрытию в строке 24.
Строка 11: Ok(initial) создаёт владельца пустого вектора initial.
Ветка заканчивается только после завершения владения вектором.
Строки 12–13: push возвращает и владельца, и статус
Строка 12:
Сначала вычисляются аргументы. move initial передаёт владение в операцию;
прежняя привязка initial становится недоступной. Литерал 42 получает тип
u32 из типа элемента вектора.
push возвращает внутреннее линейное произведение двух компонентов:
first— вектор после операции;status—Result<vec.Done, vec.Error>.
Деструктуризация вычисляет правую часть один раз и потребляет результат целиком.
Это не два независимых перемещения полей. Вектор возвращается при любом
статусе: при Full он сохранил бы прежнее содержимое и длину.
В данном примере вектор пуст и имеет вместимость 2, поэтому добавление успешно:
first содержит [42], его длина равна 1.
Строка 13: match status проверяет успех. В Ok(done) делать ничего
не нужно: done — пустое значение vec.Done. В Err(error) записывается код 3.
Это проверка контракта; для только что созданного пустого вектора ошибка Full
не ожидается.
Строки 14–15: pop и передача линейного результата через generic
Строка 14:
first больше недоступен. pop извлекает последний элемент и возвращает
произведение пустого вектора и Some(42). Пока оно целиком связано
с именем pending: обязанность завершить владение вектором находится внутри
этого результата, она не исчезла.
Копировать pending или просто забыть о нём нельзя. Даже если извлечённый
элемент — обычное число, весь составной результат остаётся линейным.
Строка 15:
Вывод типов подставляет в identity<T> внутренний тип результата pop.
Функция получает произведение через move и целиком возвращает его дальше.
Затем деструктуризация создаёт две новые привязки:
empty: vec.Vec<u32>— владелец пустого вектора, всё ещё зависимый от региона;item: Option<u32>— независимое значениеSome(42).
pending после перемещения недоступен. Вызов generic-функции не стёр
ни обязанность освободить вектор, ни его происхождение из region.
Этот вызов нужен, чтобы показать перенос владения через функцию. Если такого посредника не требуется, строки 14–15 можно записать короче:
Строки 16–19: проверяем извлечённое значение
Строка 16: match item разбирает Option<u32>.
Здесь не нужен move: Option<u32> копируем, в отличие от произведения,
которое мы только что распаковали.
Строка 17: ветка Some(value) проверяет, что извлечено именно 42.
Если значение другое, записывается код 4.
Строка 18: None означал бы, что извлечь было нечего. После успешного
добавления это не ожидается, поэтому записывается код 5.
Строка 19 закрывает match.
Извлечённое число принадлежит программе независимо от региона: оно могло бы
использоваться и после закрытия хранилища. При этом байты старого элемента
могут оставаться в слоте вектора. После pop этот слот уже не является
доступным элементом; следующий push запишет туда новое значение.
Строка 20: длина уменьшилась, вместимость осталась
pop изменил длину с 1 на 0, но не уменьшил вместимость и не переместил
хранилище. Обе функции только читают свойства владельца; передавать его
через move не нужно.
|| вычисляется с коротким замыканием: если проверка длины уже обнаружила
ошибку, проверка вместимости не требуется. При нарушении любого условия
программа записывает код 6.
Строки 21–28: сначала вектор, затем регион
Строка 21: vec.release(move empty) завершает логическое владение вектором
и снимает зависимость от региона. Саму память эта операция не освобождает
и вместимость региону не возвращает. Результат 0 сохраняется в released;
это обычное число, его разрешено не использовать.
Строки 22–23 закрывают ветку успешного создания вектора и её match.
Теперь оба пути сходятся: либо выделение вектора не удалось, либо его владелец
уже завершён. Ни на одном пути нет живого вектора, зависящего от региона.
Строка 24: memory.close(move region) освобождает хранилище региона.
Этот порядок проверяет компилятор: закрытие до release, в том числе при
живом pending, было бы статической ошибкой. Возвращаемый 0 записывается
в обычную привязку closed.
Строка 25: return result возвращает накопленный код завершения.
На успешном пути он так и остался равным 0.
Строки 26–28 закрывают ветку Ok(region), внешний match и функцию main.
Как меняются владельцы на успешном пути
| После строки | Доступный владелец | Длина / вместимость | Извлечённое значение |
|---|---|---|---|
| 11 | initial |
0 / 2 | — |
| 12 | first |
1 / 2 | — |
| 14 | Вектор внутри pending |
0 / 2 | Some(42) внутри pending |
| 15 | empty |
0 / 2 | Независимый item = Some(42) |
| 21 | Владение вектором завершено | — | item не зависит от региона |
| 24 | Регион закрыт | — | Независимое значение остаётся корректным |
Имена меняются, но в этом примере хранилище вектора всё время одно.
identity, push и pop не создают новый регион и не увеличивают вместимость.
Это свойство данного API, а не обещание, что всякое move произвольного
крупного значения всегда имеет стоимость O(1).
Что означают коды завершения
| Код | Причина |
|---|---|
0 |
Все операции и проверки успешны |
1 |
Не удалось создать регион |
2 |
Не удалось выделить вектор в регионе |
3 |
push вернул ошибку |
4 |
Извлечённое значение отличается от 42 |
5 |
pop вернул None |
6 |
Не совпали ожидаемые длина или вместимость |
Переменная result сохраняет последнюю обнаруженную проверками ошибку,
а не обязательно первую. Это небольшой проверочный пример, не универсальная
схема диагностики приложения.
Ошибки выделения здесь передаются через Result. При нарушении вычислительного
контракта возможен неперехватываемый trap; вызов release или close на таком
пути язык не гарантирует. В показанном успешном выполнении trap не возникает.
Дальше: полный контракт Vec, регионы и память и правила generics.