Структура программы

Defer и ресурсы

Выбор между defer и формой using для File, порядок очистки и граница ошибки выполнения.

Используйте defer, когда перед выходом из лексической области нужно обязательно выполнить очистку. Если область владеет ровно одним File, более узкая инструкция using объединяет получение ресурса, дочернюю область и неявное закрытие.

Проверяем порядок очистки

Рядом с программой создайте текстовый файл defer-resources.txt с точным содержимым:

ready

Сохраните следующий код как defer-resources.tpz:

TOPAZ
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}")
}

Проверьте и запустите файл:

BASH
topaz check defer-resources.tpz
topaz run defer-resources.tpz

Строки появляются в следующем порядке:

Вывод
note scalars: 6
body cleanup
resource scope closed
done

open(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 и ошибки, а точные операции с файлами найдите в разделе Файлы и ресурсы.