Comments 15
Основных ящиках -_-
Anyhow и thiserror это произведения исскусства, которые делают нелегкую жизнь разработчика на расте намного лучше :)
Всегда есть способ определить ответственность, кто «виновен» в панике. Либо это вина функции, которая запаниковала, либо вина вызывающей стороны.
Либо ОС вернула код ошибки 2 и никто не знает почему. То ли это код кривой, то ли вызов.
Вот это для меня основная трудность, понять откуда пришла ошибка.
Может быть я не прав, но я избалован исключениями (Java, JavaScript), поэтому когда в Rust’е я пользуюсь фреймворком, который за меня возвращает Result с ошибкой, и я пытаюсь её привести к значению, то меня это постоянно раздражает. Я не вижу источника ошибки (чтобы почитать чужой код и понять причину), а вижу лишь место своей паники.
А я наоборот, никогда не использую expect(). Что-то эта функция ни в царский жезл, ни в красную армию. unwrap() я использую тогда, когда у меня объект завернут, но я уже точно знаю, что он есть. unwrap() что-то навроде assert(), отличается невыразимой армейской краткостью. Если же есть хоть малейшие сомнения, что объект там есть, то уже надо использовать разнообразные unwrap_or или map.
Поэтому expect() странен: мы заявляем, что совершенно уверены, что он никогда не сработает, и тут же пишем ему аргумент на случай, если он сработает. При том, что если сработает unwrap(), то разработчик всё равно получит всю инфу о проблеме, а зачем рассказывать юзеру технические детали большого облома, неясно. В отчёте от него вам всё равно будет указана строка в коде, unwrap() её выводит.
По этой причине, никогда не пишите два unwrap() на одной строке.
Важно избегать такого кода:
let cthulhu: Option<Cthulhu> = call_cthulhu();
if cthulhu.is_none() {
return;
}
// Много печальных приготовлений на 3 страницы
tickle(cthulhu.unwrap());
Сейчас с этим кодом всё в порядке. Но проверка option никак не связана с его unwrap() и в дальнейшем может или условие потеряться, или вторая половина кода куда-то переехать. Из мира С++ до нас доносятся стоны, что количество вложенных уровней кода надо сокращать, именно тем способом, что у меня в примере. Не надо только жертвовать надёжностью кода ради этого. Используйте if let Some(cthul) == cthulhu {}
Как один из упоротых вариантов: можно распаковать перед использованием, и потом с распакованным значением работать. Но выглядит как-то так себе:
let cthulhu = if let Some(cthulhu) = call_cthulhu() {
cthulhu
} else {
return;
};
// ...
tickle(cthulhu);
В таких случаях очень хорошо бы использовать ?
, но это не всегда возможно
let cthulhu = match (call_cthulhu()) {
Some(c) => c,
_ => return,
};
Да, такой вариант уже приятнее глазу, но он всё еще не сравнится с оператором ?
. А для него необходимо иметь возвращаемое значение вида как минимум Option<()>
что воспринимается как: можем вернуть ничего либо ничего. Хотя мне кажется здесь лучше вернуть ошибку, если Ктулху не пришел и ее пробросить выше, но нужно смотреть на контекст.
Для просто для краткости. Вот прямо сегодня на работе кодил метод "получить рамку слева от текущей". Неполучиться рамка может только если её нет - ну и зачем мне возвращать ошибку, если и так всё понятно? У меня такие ситуации возникают часто, когда функцию планируется вызывать в цикле до тех пор, пока она не сломается. Как в том анекдоте про пилу. И тогда совсем не важно, почему она сломалась: результат достигнут, список рамок отсюда до левого края составлен, зачем мне суть ошибки? А, ну и ещё я на С++ пишу на работе, там Option есть, а Result нет, а колхозить не хочу.
А для него необходимо иметь возвращаемое значение вида как минимум
Option<()>
Но ведь ничто не мешает в, условно, одну строчку конвертироваться из Option<T>
в Result<T, E>
и далее сразу его вернуть try-оператором.
fn try_operator_variant() -> Result<String, &'static str> {
let cthulhu = call_cthulhu()
.ok_or("Failed to call cthulhu")?;
tickle(cthulhu);
todo!("Don't forget about me")
}
Учитывя что у вас сигнатуры возвращаемых данных расходятся, то оно даже не соберётся. С распакованным значением в таком случае надо работать внутри же условия.
у вас сигнатуры возвращаемых данных расходятся
Почему же? Оба варианта правильные.
В случае с if-let-else
ещё происходит shadowing предыдущей локальной переменной cthulhu: Option<Cthulhu>
новой cthulhu: Cthulhu
, полученной в качестве результата if-let-else
выражения.
Ну и, как верно подметил выше @medvedevia, с недавних пор есть компактный let-else
:
let Some(cthulhu) = call_cthulhu() else {
return;
};
// ...
tickle(cthulhu);
количество вложенных уровней кода надо сокращать
Для этого можно использовать макрос ward!
let cthulhu = ward!(cthulhu, return);
Использовать unwrap() в Rust — это нормально