Какой идиоматический способ вернуть ошибку из функции без результата в случае успеха?

В Rust я считаю, что идиоматический способ справиться с восстанавливаемыми ошибками - использовать Result. Например, эта функция явно идиоматична:

fn do_work() -> Result<u64, WorkError> {...}

Конечно, есть также функции, которые имеют одно, очевидное, состояние отказа и, следовательно, вместо этого используют тип Option. Идиоматическим примером может быть следующее:

fn do_work() -> Option<u64>

Все это прямо рассматривается в документации. Однако я смущен тем случаем, когда функция может терпеть неудачу, но при этом не имеет значимой ценности. Сравните следующие две функции:

fn do_work() -> Option<WorkError>
// vs
fn do_work() -> Result<(), WorkError>

Я просто не уверен, какой из них более идиоматичен или используется чаще в коде рвения реального мира. Мой ресурс для таких вопросов, как книга Rust, но я не думаю, что это описано в разделе Обработка ошибок". Мне также не повезло с другими документами Rust.

Конечно, это кажется довольно субъективным, но я ищу авторитетные источники, которые либо заявляют, что форма идиоматична, либо почему одна форма превосходит (или уступает) другой. (Мне также любопытно, как соглашение сравнивается с другими языками, которые в значительной степени используют "ошибки как значения", такие как Go и Haskell.)

Ответ 1

Используйте fn do_work() -> Result<(), WorkError>.

Result<(), WorkError> означает, что вы хотите, чтобы работа была выполнена, но она может выйти из строя.

Option<WorkError> означает, что вы хотите получить сообщение об ошибке, но оно может отсутствовать.

Вероятно, вы хотите, чтобы работа была выполнена, но не для получения ошибки при написании do_work(), поэтому Result<(), WorkError> - лучший выбор.

Я ожидал бы, что Option<WorkError> будет использоваться только в таких случаях, как fn get_last_work_error() -> Option<WorkError>.

Ответ 2

Rust "довольно сильно набрал" (и, пожалуйста, пожалуйста, не вызывайте меня, как я измеряю, насколько сильно набирается язык...). Я имею в виду это в том смысле, что Rust обычно дает вам инструменты, позволяющие типам "говорить" для вас и документировать ваш код, поэтому он идиоматично использовать эту функцию для записи читаемого кода.

Другими словами, вопрос, который вы задаете, должен быть более "какой тип лучше всего показывает, что делает функция для любого, кто читает свою подпись?"

Для Result<(), Workerror> вы можете видеть прямо из документов

Результат - это тип, который представляет либо успех (Ok), либо отказ (Err)

Итак, специализированный для вашего случая, это означает, что ваша функция ничего не возвращает, если она успешна (представлена ​​ Ok<()>) или WorkError, если есть ошибка (Err<WorkError>). Это очень прямое представление в коде того, как вы описали функцию в своем вопросе.

Сравните это с Option<WorkError> или Option<()>

Тип Option представляет собой необязательное значение: каждый параметр имеет значение Some или содержит значение или None, а не

В вашем случае Option<WorkError> будет говорить читателю "эта функция должна возвращать WorkError, но ничего не может вернуть". Вы можете документировать, что случай "вернуть ничего" означает, что функция была фактически успешной, но которая не очень очевидна только для типов.

Option<()> говорит: "эта функция ничего не может вернуть или не имеет никакого значимого возврата", что может быть разумной вещью, если WorkError не содержит никакой другой информации (типа ошибки или сообщения об ошибке), и это практически способ сказать "произошла ошибка". В этом случае простая bool несет ту же информацию... В противном случае Result позволяет вам возвращать дополнительную информацию, связанную с ошибкой.