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

Null, Option, Result и ошибки

Выбор представления для обычного отсутствия, nullable-данных, восстанавливаемой ошибки и нарушения контракта времени выполнения.

Эта страница описывает Топаз v5.17. Прежде чем считать её актуальной, сверьте поле version в /version.json со значением 5.17.

Обращайтесь к этой странице, когда значение может отсутствовать или операция может завершиться неудачно. Сначала спросите не «какой оператор короче?», а «может ли вызывающая сторона продолжить работу?».

Выбирайте по смыслу

СитуацияЧто использовать
Отсутствие значения — обычный исход внутри ТопазаOption<T> с Some(value) или None
Внешние данные явно содержат nullable-полеT | null
Вызывающая сторона может обработать осмысленную ошибкуResult<T, E> с Ok(value) или Err(error)
Программа нарушила контракт времени выполненияСбой (fault): вычисление прерывается и не создаёт значение

Для обычного отсутствия в API Топаза предпочитайте Option. Оставляйте null для данных и границ интеграции, где он действительно присутствует. Используйте Result, когда вызывающей стороне нужны причина ошибки или выбор способа восстановления.

Превратите неверный ввод в восстанавливаемый результат

Эта полная программа превращает отсутствие, возвращённое toInt, в Result с понятным сообщением:

TOPAZ
record УчебнаяЗадача {
    название: string,
    минуты: int,
}

function разобратьМинуты(текст: string) -> Result<int, string> {
    match toInt(текст) {
        case Some(значение) if значение > 0 => Ok(значение)
        case _ => Err("minutes must be a positive integer")
    }
}

function создатьЗадачу(название: string, текстМинут: string) -> Result<УчебнаяЗадача, string> {
    let минуты = разобратьМинуты(текстМинут)?
    Ok(УчебнаяЗадача { название: название, минуты: минуты })
}

function проверить(текстМинут: string) -> () {
    print("input: {текстМинут}")
    defer { print("inspection finished") }
    match создатьЗадачу("Build application", текстМинут) {
        case Ok(задача) => print("ready: {задача.название}, {задача.минуты} minutes")
        case Err(сообщение) => print("cannot add task: {сообщение}")
    }
}

проверить("25")
проверить("later")

Проверьте и запустите программу:

BASH
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 finished

toInt возвращает Option<int>, потому что у текста, который не является целым числом, нет разобранного значения. разобратьМинуты решает, что это отсутствие важно вызывающей стороне, и превращает его в Err. В создатьЗадачу постфиксный ? извлекает значение из Ok, а при Err немедленно возвращает ошибку из совместимой функции с результатом Result. Последний match явно обрабатывает оба исхода.

Работайте с необязательными и nullable-значениями

Необязательный доступ и значение по умолчанию сохраняют различие моделей:

  • Если maybeUser: Option<{ name: string }>, то результат maybeUser?.name также остаётся Option, а maybeUser?.name ?? "guest" даёт string.
  • Если configPath: string | null, выражение configPath ?? "default.toml" использует запасное значение только при null.
  • target ??= value — инструкция, которая присваивает значение только пустой цели. Для цели Option<int> требуется Some(42), а не просто 42: Топаз не добавляет Some автоматически.

Операторы ?. и ?? вычисляют левую часть ровно один раз. ?. также не вычисляет аргументы метода, если получатель пуст. Каждый оператор снимает или уплощает один необязательный слой и не превращает Option в null.

Исправление частой ошибки: для значения, которое не является необязательным или nullable, используйте обычную точку .. Если тип сочетает Option<T> и null, явно разберите случаи через match, а не наслаивайте сокращения.

Сбой не является значением ошибки

Эта программа обращается к несуществующему элементу массива:

TOPAZ
let задачи = ["Run first program", "Build application"]
print(задачи[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(задачи[2])
  |       ^^^^^^^^^

Выход за границы — это сбой (fault): он прерывает вычисление и не создаёт ни None, ни Err. Постфиксный ?, оператор ??, необязательная цепочка и concurrent не перехватывают и не преобразуют сбои. Некоторые неверные операции с константами проверка отклоняет ещё до запуска.

Точные границы

  • Постфиксный ? разрешён только в функции или замыкании, чей возвращаемый тип может нести то же значение ошибки.
  • Общий try, исключения, перехват сбоев, ключевые слова panic и автоматические повторы не являются текущими формами Топаза.
  • assert(...) доступна только в явно выбранном коде test-profile; это не инструкция проверки для обычной программы.
  • Очистку при обычном выходе из области выполняет defer. Граница очистки при сбое уже, поэтому сбой нельзя считать восстанавливаемым Result.

Продолжите с пошаговым уроком Ошибки и ресурсы, точными правилами Defer и ресурсы и границей сбоев на странице Поведение среды выполнения.