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 удобны в прототипах и тестах, но в надёжном коде ошибку обрабатывают.
Проверьте себя
1. Для каких ошибок предназначен Result, а не panic!?
AДля багов в логике
BДля ожидаемых восстановимых ошибок, которые вызывающий код может обработать
CResult не нужен
DТолько для индексации
2. Что делает panic!?
AВозвращает ошибку наверх
BНемедленно аварийно завершает выполнение потока
CИгнорирует ошибку
DПревращает Err в Ok
3. Почему в библиотеках предпочитают возвращать Result, а не паниковать?
AResult быстрее
BЧтобы вызывающий код сам решил, как реагировать на ошибку
Cpanic! запрещён в библиотеках
DResult короче пишется
Поддержать проект