Используйте defer, когда перед выходом из лексической области нужно
обязательно выполнить очистку. Если область владеет ровно одним File, более
узкая инструкция using объединяет получение ресурса, дочернюю область и
неявное закрытие.
Проверяем порядок очистки
Рядом с программой создайте текстовый файл defer-resources.txt с точным
содержимым:
readyСохраните следующий код как defer-resources.tpz:
function inspectNote(path: string) -> Result<(), string> {
using file = open(path)? {
defer { print("body cleanup") }
let note = file.read()?
print("note scalars: {note.scalars().length}")
}
print("resource scope closed")
Ok(())
}
match inspectNote("defer-resources.txt") {
case Ok(_) => print("done")
case Err(message) => print("error: {message}")
}Проверьте и запустите файл:
topaz check defer-resources.tpz
topaz run defer-resources.tpz
Строки появляются в следующем порядке:
note scalars: 6
body cleanup
resource scope closed
doneopen(path)? делает одну попытку получить файл. using регистрирует неявное
закрытие лишь после успешного получения. Действие defer, добавленное позже
в теле, выполняется первым, а к строке resource scope closed программа
переходит уже после неявного закрытия.
Выбираем владельца времени жизни
| Ситуация | Что использовать |
|---|---|
| Один вызов или блок очистки относится к текущей области | defer call или defer { ... } |
Один File принадлежит одному дочернему блоку | using file = open(path)? { ... } |
| Получение, чтение или запись могут штатно завершиться неудачей | Вернуть или разобрать Result; применять ? только в совместимой функции |
| Программа нарушила контракт среды выполнения | Считать это ошибкой выполнения, а не восстанавливаемой ветвью очистки |
Несколько действий defer в одной области выполняются в порядке, обратном
регистрации. Очистка также происходит, когда область пересекают return,
постфиксный ?, break или continue. Имя в using неизменяемо и видно
только внутри тела. Замыкание может сохранить значение File, но неявное
закрытие всё равно происходит при выходе из области using: сохранение
значения не продлевает жизнь ресурса.
Передача Result — не ошибка выполнения
Если open возвращает Err, очистка ещё не зарегистрирована. Если
file.read() возвращает Err, постфиксный ? пересекает действующую область
using, поэтому зарегистрированная очистка завершается до выхода ошибки из
inspectNote.
Для ошибки выполнения обещание намеренно уже: она прерывает вычисление, и язык
не гарантирует, что после этого будут выполнены все лексические defer или
закрытие using. Ошибка внутри самого отложенного действия подчиняется
политике записи и сбора ошибок среды выполнения. Она не становится значением,
которое можно перехватить, и не заменяет уже возвращаемый Err.
Частое исправление
Не пишите получение файла как File.open(...) и не приписывайте using
универсальный протокол ресурсов. Точная функция — open(path), а инструкция
using принимает одно простое неизменяемое имя и один File.
Явный вызов close() внутри тела using не отменяет зарегистрированную
попытку закрытия. Поведение после закрытия и при повторном закрытии не является
переносимым, поэтому обычный код должен оставлять одно закрытие одному
владельцу.
Точная граница
Пользовательские протоколы освобождения, одновременное получение нескольких ресурсов, передача владения, асинхронная очистка, очистка при отмене, гарантии для исключений среды и финализация на протяжении жизни модуля не входят в текущее поведение Топаза. Цель генерации обязана сохранить описанное поведение или явно отклонить программу. Этот запуск в интерпретаторе сам по себе не доказывает одинаковую поддержку всеми целями.
Вернитесь к учебному разбору на странице Ошибки и ресурсы, сравните отсутствие и восстанавливаемую ошибку на странице Null, Option, Result и ошибки, а точные операции с файлами найдите в разделе Файлы и ресурсы.