Является ли String допустимым Тип ошибки, когда она может быть немедленно сообщена в stdout?

75
9

Недавно я реализовал основную механику игры в шахматы и использовал тип Result<T, E> для методов сбора человеческого ввода, поскольку он может быть недействительным. Однако я не уверен, какой тип я должен выбрать для возможной ошибки (E).

Я понял, что внедрение новых типов считается хорошей практикой при создании библиотеки. Однако, когда Result может быть обработан немедленно, а сообщение Err сообщаемое в stdout, не проще просто вернуть Result<T, String> или Result<T, &str> (или Result<T, Cow<str>> если оба может возникнуть)?

Рассмотрим следующий случай:

pub fn play() {
let mut game = Game::new();

loop {
match game.turn() {
Ok(()) => { game.turn += 1 }
Err(e) => println!("{}", e)
}
}
}

Игра воспроизводится на терминале, и любые ошибки ввода могут быть немедленно сообщены. Есть ли добавленная стоимость для введения пользовательского типа ошибки в этом случае?

спросил(а) 2021-01-19T17:55:03+03:00 2 месяца, 3 недели назад
1
Решение
114

Это довольно широкий вопрос, и нет четкого "правильного" или "неправильного" ответа.

В вашем примере важно отметить, что строки имеют очень мало доступную семантическую информацию. Конечно, вы могли бы извлечь всю семантическую информацию, проанализировав строку, но это действительно неправильный подход. Поэтому большинство более крупных библиотек или приложений используют типы ошибок, которые содержат большую семантическую информацию, что позволяет легко обрабатывать ошибки.

В вашем случае строки, вероятно, прекрасны, если вы все равно напечатаете их. Но есть аккуратный маленький трюк, чтобы сделать по крайней мере функции сигнатур немного более надежными в будущем: return Box<Error>.

Характеристика Error - хорошая абстракция ошибок. Практически каждый тип ошибки реализует эту черту. С помощью функции try!() И Into можно легко справляться с большинством ошибок. Кроме того: существует несколько типов преобразования impl для строк и Box<Error>. Это позволяет возвращать строки как ошибки:

fn foo() -> Result<(), Box<Error>> {
try!(std::fs::File::open("not-here")); // io::Error
try!(Err("oh noooo!")); // &str
try!(Err("I broke it :<".to_owned())); // String
Err("nop".into())
}

См. Рабочую демонстрацию.

Изменение: обратите внимание, что Box<Error> несет меньшую семантическую информацию, чем другой конкретный тип ошибки, такой как io::Error. Так что не всегда рекомендуется возвращать Box<Error> ! Это просто лучший подход в вашей ситуации :)

Редактирование 2: Недавно я много читал о моделях обработки ошибок, которые немного изменили мое мнение. Я все еще думаю, что этот ответ в значительной степени верен. Тем не менее, я думаю, что это далеко не так просто, как я сформулировал его здесь. Поэтому просто имейте в виду, что этот ответ не подходит в качестве общего руководства вообще!

ответил(а) 2021-01-19T17:55:03+03:00 2 месяца, 3 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема