panic! против Result
Когда программа должна аварийно остановиться (panic!), а когда — вернуть ошибку через Result.
panic! — макрос, который немедленно прерывает выполнение потока: это сигнал о непоправимой ситуации, ошибке программиста.
Что такое паника
Паника — это аварийная остановка. Программа печатает сообщение, разворачивает стек и завершается. Паника возникает, например, при выходе за границу массива, делении на ноль или вызове unwrap() на None/Err.
fn main() {
let v = vec![1, 2, 3];
println!("{}", v[10]); // panic: index out of bounds: the len is 3 but the index is 10
}Вызвать панику явно можно макросом panic!:
fn main() {
let config_loaded = false;
if !config_loaded {
panic!("конфигурация не загружена — продолжать нельзя");
}
}Два рода ошибок
Ключ к выбору — поделить ошибки на два рода.
- Восстановимые — ожидаемые сбои, с которыми вызывающий код может что-то сделать: файл не найден, пользователь ввёл не число, сеть недоступна. Для них —
Result. - Непоправимые — нарушение инвариантов, баг в логике, ситуация, из которой нет осмысленного выхода. Для них —
panic!.
| Ситуация | Чем обрабатывать |
| Файл не открылся | Result |
| Пользователь ввёл не число | Result |
| Нарушен инвариант, который «не может» нарушиться | panic! |
| Индекс заведомо в пределах массива (баг, если нет) | паника от индексации |
Правило большого пальца
Если ошибку можно разумно обработать и продолжить — возвращайте Result и дайте решать вызывающему коду. Паника же говорит «дальше идти нельзя, это баг». В библиотеках почти всегда предпочитают Result: пусть приложение само решит, паниковать ему или нет.
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
// деление на ноль — ожидаемая ситуация, возвращаем ошибку
return Err(String::from("деление на ноль"));
}
Ok(a / b)
}
fn main() {
match divide(10.0, 0.0) {
Ok(r) => println!("результат: {r}"),
Err(e) => println!("не удалось: {e}"),
}
}Вывод:
не удалось: деление на ноль
unwrap и expect — управляемая паника
Методы unwrap() и expect("...") достают значение из Result/Option, но паникуют при ошибке. expect позволяет задать понятное сообщение. Их уместно использовать в примерах, прототипах и тестах; в продакшен-коде ошибку обычно обрабатывают явно.
fn main() {
let n: i32 = "42".parse().expect("строка должна быть числом");
println!("{n}");
}Вывод:
42
Итог
panic!— для непоправимых ошибок и багов: программа аварийно останавливается.Result— для ожидаемых, восстановимых сбоев: решение принимает вызывающий код.unwrap/expectудобны в прототипах и тестах, но в надёжном коде ошибку обрабатывают.