Результат: вы представите ожидаемое отсутствие и восстанавливаемую ошибку значениями, гарантируете очистку области и отличите оба случая от нарушения контракта во время выполнения.
Что нужно знать: завершите Данные и управление и научитесь прослеживать значение через if, match и for.
Сделайте неверный ввод ожидаемым результатом
Создайте failures-resources.tpz:
record StudyTask {
title: string,
minutes: int,
}
function parseMinutes(text: string) -> Result<int, string> {
match toInt(text) {
case Some(value) if value > 0 => Ok(value)
case _ => Err("minutes must be a positive integer")
}
}
function makeTask(title: string, minuteText: string) -> Result<StudyTask, string> {
let minutes = parseMinutes(minuteText)?
Ok(StudyTask { title: title, minutes: minutes })
}
function inspect(minuteText: string) -> () {
print("input: {minuteText}")
defer { print("inspection finished") }
match makeTask("Build application", minuteText) {
case Ok(task) => print("ready: {task.title}, {task.minutes} minutes")
case Err(message) => print("cannot add task: {message}")
}
}
inspect("25")
inspect("later")Проверьте и запустите файл:
topaz check failures-resources.tpz
topaz run failures-resources.tpz
Неверный текст обрабатывается как данные, поэтому процесс завершается нормально:
input: 25
ready: Build application, 25 minutes
inspection finished
input: later
cannot add task: minutes must be a positive integer
inspection finishedtoInt возвращает Option: Some(value) означает, что целое число присутствует, а оставшийся случай покрывает отсутствие. parseMinutes превращает отсутствие и неположительное число в Result с понятным сообщением.
makeTask тоже возвращает Result. Постфиксный ? извлекает значение из Ok, а совместимый Err немедленно возвращает из текущей функции. Поэтому неверная задача не создаётся. inspect явно обрабатывает оба исхода.
defer принадлежит области, которая начала проверку. Успешный и неуспешный ввод проходят разные ветви, но строка завершения появляется после обоих. В функции, владеющей настоящим ресурсом, обязательную очистку точно так же помещают в область владельца.
Изучите нарушенный контракт
Теперь создайте intentional-fault.tpz:
let tasks = ["Run first program", "Build application"]
print(tasks[2])Команда topaz run intentional-fault.tpz завершается ненулевым кодом:
error[TPZ4001]: index 2 is out of bounds for an array of length 2
--> intentional-fault.tpz:2:7
|
2 | print(tasks[2])
| ^^^^^^^^
Это fault, а не восстанавливаемый Err: программа нарушила границы массива. Диагностика называет контракт и место в исходнике, не придумывая отсутствующее значение.
Выбор: отсутствие, восстановление или fault?
Используйте Option, когда отсутствие значения — обычный исход. Используйте Result, когда вызывающий код способен отреагировать на осмысленную ошибку, и ?, чтобы передать её через другой совместимый Result. Нарушение контракта выражается как fault. Независимо от этого выбора применяйте defer, когда область обязана выполнить очистку при любом выходе.
Попробуйте
Добавьте в рабочую программу inspect("0"). Какая ветвь сработает и появится ли строка из defer?
Показать ответ
Ноль попадёт в запасную ветвь и напечатает cannot add task: minutes must be a positive integer. Следующей строкой всё равно будет inspection finished, потому что defer выполняется при выходе из области inspect.
Готовы двигаться дальше, если
Вы можете объяснить, почему неверный текст становится Err, почему ? безопасен внутри makeTask, почему строка очистки появляется дважды и почему выход за границы — это fault, а не ожидаемый ввод.
Точные правила приведены в разделах Null, Option, Result и ошибки и Defer и ресурсы. Переходите к Первому приложению.