В чём принципиальное отличие Result::or_else от unwrap_or_else при реализации резервного пути?
Result::or_else запускает резервную операцию только при Err и сохраняет её результат как Result, поэтому резервный путь может завершиться новой ошибкой. unwrap_or_else также обрабатывает Err, но обязан вернуть успешное значение T, превращая ошибочный исход в значение и теряя форму Result.
Result в Rust предназначен для явного представления двух исходов операции: успеха и ошибки. Такой подход позволяет вызывающему коду решить, нужно ли повторить операцию, выбрать запасной источник, преобразовать ошибку или завершить обработку.
Комбинаторы or_else и unwrap_or_else разделяют два разных намерения. Первый строит альтернативную операцию, которая всё ещё может завершиться ошибкой, а второй предоставляет окончательное значение по умолчанию.
Предположим, приложение сначала читает основной конфигурационный файл, а при сбое пытается использовать резервный. Если резервный файл тоже недоступен, эту ошибку нельзя замаскировать произвольным пустым значением без изменения семантики программы.
Выбор unwrap_or_else в такой ситуации может скрыть неисправность: вызывающий код получит строку и не узнает, что оба источника не сработали. or_else сохраняет возможность передать окончательный сбой выше по стеку, но сам по себе не сохраняет первую ошибку, если резервная операция тоже завершилась ошибкой.
or_else принимает исходную ошибку и возвращает новый Result. Замыкание вызывается лениво только для варианта Err; при Ok резервная операция не выполняется. Поэтому метод подходит для повторной попытки, переключения на другой источник или условного восстановления.
unwrap_or_else принимает ошибку, но замыкание должно вернуть именно успешное значение T. После его выполнения результатом становится T, а информация о первоначальной ошибке больше не доступна через возвращаемый тип.
Минимальный пример резервного чтения:
Если основной файл прочитан успешно, резервный путь не запускается. Если не удалось прочитать оба файла, наружу выйдет ошибка второй операции. Это важное ограничение: для диагностики обеих причин нужен собственный тип ошибки, содержащий исходную и резервную ошибки, либо явная обработка результатов.
Типы методов отражают их назначение: or_else сохраняет оболочку Result, хотя тип ошибки может измениться, а unwrap_or_else снимает оболочку и возвращает значение успеха. Оба метода ленивы, поэтому дорогостоящий резервный путь не выполняется без необходимости.
Сервис загружает шаблон сначала из локального файла, затем из удалённого хранилища. Возможны три решения.
Первое — вызвать unwrap_or_else и вернуть встроенный шаблон. Плюс этого варианта — сервис продолжает работу, минус — ошибка доступности обоих источников может остаться незаметной, а встроенный шаблон может быть устаревшим.
Второе — применить or_else и вернуть ошибку удалённого хранилища, если локальное чтение не удалось. Это сохраняет отказ как отказ операции, но теряет сведения о том, почему не сработал первый источник.
Третье — использовать or_else для попытки резервного источника, а затем преобразовать обе причины в собственный тип вроде ошибки загрузки шаблона с полями для локальной и удалённой ошибок. Для критичной конфигурации выбран третий вариант: вызывающий код получает структурированную диагностику, а политика аварийного завершения остаётся явной. В результате резервный путь не маскирует неисправность и не смешивает её с успешным значением по умолчанию.
or_else, если исходный Result содержит Ok?Нет. or_else — ленивый комбинатор: при Ok он возвращает исходное успешное значение и не вызывает переданное замыкание. Это позволяет безопасно описывать дорогие или имеющие побочные эффекты резервные операции.
or_else повторить ту же операцию?Да, но повтор не происходит автоматически. Замыкание должно явно вызвать операцию снова, обычно с ограничением числа попыток и проверкой класса ошибки. Бесконтрольный повтор может усилить нагрузку или скрыть постоянную ошибку, поэтому для временных и постоянных сбоев нужна разная политика.
or_else не всегда сохраняет исходную ошибку?Потому что его результатом становится тот Result, который возвращает замыкание. Если замыкание запускает резервную операцию и она возвращает Err, исходная ошибка заменяется новой. Когда важны обе причины, их нужно объединить в пользовательский тип ошибки или обработать явно до выбора резервного результата.