Сравните unwrap_or и unwrap_or_else для Option: когда вычисляется значение по умолчанию и как это влияет на поведение программы?
unwrap_or вычисляет аргумент по умолчанию до вызова метода, даже если Option содержит Some. unwrap_or_else принимает замыкание и выполняет его только при None, поэтому подходит для дорогих вычислений, побочных эффектов и ленивого создания значения.
В Rust отсутствие значения моделируется типом Option, а не неявным null. После проверки или преобразования Option часто требуется выбрать запасное значение, поэтому стандартная библиотека предоставляет варианты с eager- и lazy-вычислением.
Такое разделение решает практическую проблему: простое значение по умолчанию можно вычислить сразу, а сложную операцию не следует выполнять без необходимости. При этом поведение остаётся явным и контролируемым компилятором.
Если запасное значение создаётся дорого, безусловное вычисление приводит к лишним затратам даже при наличии Some. Если вычисление имеет побочный эффект, он произойдёт независимо от результата Option, что может привести к неожиданным обращениям к файлам, журналированию, выделениям памяти или ошибкам внутри самой операции.
Неверный выбор также ухудшает читаемость: код может выглядеть как ленивый fallback, хотя фактически запасная ветка выполняется всегда. Это особенно существенно в горячих участках и при вызовах функций с заметной стоимостью.
unwrap_or(default) получает уже вычисленное значение default. Сначала вычисляются аргументы вызова, затем вызывается метод; поэтому выражение по умолчанию выполняется и для Some, и для None.
unwrap_or_else(|| create_default()) получает замыкание типа FnOnce. Для Some(value) метод возвращает значение и не вызывает замыкание. Для None замыкание вызывается ровно один раз, а его результат становится запасным значением.
В первом случае сообщение печатается, хотя значение уже есть. Во втором случае замыкание не вызывается. Само создание замыкания обычно дёшево, но значения, захватываемые им по move, могут быть перемещены в замыкание уже при его создании; ленивым является выполнение тела, а не обязательно все операции захвата.
Оба метода потребляют Option и возвращают значение типа T, поэтому они не сохраняют информацию о том, был ли результат исходным или запасным. Если это различие важно, следует сопоставить варианты через match или использовать другой дизайн возвращаемого типа.
Сервис получает необязательную конфигурацию тайм-аута. Запасной тайм-аут строится из переменных окружения и разбора строки. Вариант с unwrap_or проще, но разбирает конфигурацию даже тогда, когда тайм-аут уже передан вызывающим кодом; кроме того, ошибка в подготовке fallback может проявиться в ненужной ветке.
Явный match даёт полный контроль и позволяет отдельно обработать ошибку разбора, но увеличивает объём кода. unwrap_or_else сохраняет компактность и запускает создание fallback только при отсутствии конфигурации; поэтому выбран этот вариант, если fallback действительно не может завершиться ошибкой или ошибка заранее преобразована в допустимое значение.
Результат — отсутствие лишних вычислений на обычном пути и предсказуемое выполнение побочных операций. Если построение fallback может завершиться ошибкой, unwrap_or_else не заменяет Result: ошибку нужно моделировать явно, например через комбинацию Option и Result или предварительное преобразование.
unwrap_or_else, что fallback никогда не вычисляется для Some?Да, тело замыкания для Some не вызывается. Однако выражения, необходимые для формирования захваченного состояния, могут вычисляться при создании замыкания, поэтому ленивым считается именно вызов тела замыкания. Например, значение, перемещаемое в замыкание через move, должно быть подготовлено до вызова метода.
unwrap_or может быть нежелателен даже при небольшом запасном значении?Проблема не ограничивается производительностью. Выражение по умолчанию может иметь побочный эффект, обращаться к внешнему ресурсу или завершиться паникой; всё это произойдёт даже при Some. unwrap_or_else локализует такие действия в ветке None, делая поведение соответствующим смыслу fallback.
unwrap_or_else сохранить ошибку построения запасного значения?Не напрямую: замыкание должно вернуть T, а не ошибку, которую метод автоматически передаст вызывающему коду. Если построение fallback возвращает Result<T, E>, обычно используют преобразование Option<T> в Result<T, E> с явной обработкой отсутствия или сначала строят Option<Result<T, E>>, после чего меняют порядок оболочек через transpose. Выбор зависит от того, является ли отсутствие значения штатным случаем или ошибкой.