В Rust все ошибки делятся на две категории: recoverable (обрабатываемые) и unrecoverable (необрабатываемые). При возникновении обрабатываемой ошибки, (например, отсутствие запрошенного файла) можно сообщить о проблеме пользователю и вернуться к нормальной работе программы (если файл отсуствует - то можно обработать - создать новый файл). Необрабатываемые ошибки (например, обращение к несуществующему элементу вектора) нередко являются показателями наличия в программе багов, в такой ситуации сложно как-то обработать ситуацию.
Для обработки обрабатываемых ошибок Rust предоставляет специальный тип Result<T, E>, а для обработки ситуации, когда возникает необрабатываемая ошибка - макрос panic!. Хотя оба средства также могут сочетаться в рамках обработки одной ошибки.
Для обработки ситуаций, когда возникает необрабатываемая ошибка, применяется макрос panic!. При его выполнении программа выводит на консоль сообщение об ошибке, очищает стек и завершает выполнение программы.
Ряд потенциальных необрабатываемых ошибок Rust может отловить на этапе компиляции, например, возьмем ситуацию, когда мы обращаемся к несуществующему элементу массива:
fn main() { let numbers = [1, 2, 3, 4]; println!("{}", numbers[10]); }
В данном случае в массиве numbers 4 элемента, соответственно элемента с индексом 10, который мы пытаемся вывести на консоль, в массиве не существует. И при компиляции мы получим ошибку:
Однако изменим тип данных с массива на вектор:
fn main() { let numbers = vec![1, 2, 3, 4]; println!("{}", numbers[10]); }
В векторе также 4 элемента, а элемента с индексом 10 не существует. Однако вектор может увеличиваться, и компилятор не сможет отловить возникновение ошибки. Поэтому компиляция завершится успешно, однако при выполнении программы мы все равно столкнемся с ошибкой:
Это как раз и результат выполнения макроса panic!
, которое прописано в исходном коде. В выводимом на консоль макросом сообщении
мы можем увидеть прежде всего сообщение об ошибке: "index out of bounds: the len is 4 but the index is 10".
Кроме того, мы можем увидеть строку кода, на которой произошла ошибка. В этом случае нам легче идентифицировать место и характер ошибки и внести соответствующие
исправления.
Но мы сами можем вызывать данный макрос в различных частях программы для обработки подобных ситуаций. Например, для имитации ошибки просто применим этот макрос:
fn main() { panic!("непредвиденная ошибка"); }
В макрос panic!
передается сообщение, которое затем будет выводиться на консоль при возникновении ошибки. Так, при выполнении этой программы
мы увидим на консоли наше сообщение и краткую информацию о возникшей ошибке:
Рассмотрим другой более практичный пример: у нас есть функция, которая извне получает некоторые данные. Однако эти данные могу быть некорректными.
И в этом случае мы можем вызвать макрос panic
:
fn main(){ let tom = create_person("Tom", 136); println!("Name: {} Age: {}", tom.name, tom.age); } struct Person{ name: String, age:u8} fn create_person(username: &str, userage: u8) -> Person{ if userage > 110{ panic!("Некорректный возраст. Возраст должен быть меньше 110"); } else { let new_person = Person{name: String::from(username), age: userage }; new_person } }
Здесь функция create_person()
получает извне имя и возраст пользователя и по ним создает объект структуры Person, который возвращается
из функции. Однако если возраст некорретный (в рамках данной программы условимся, что таким является возраст выше 110), то вызывается
макрос panic!
, который с помощью передаваемого сообщения раскравает причину ошибки.