Ту пи о нот ту пи. Паниковать или нет, вот в чём вопрос. :-)

Как понять использовать макрос panic! или нет и когда лучше всего возвращать значение Result? Если код уже находится в режиме паник - нет возможности восстановления. Заранее Вы только можете выбрать использование panic! для разных ситуаций. Например, вы решили, что можете решить, что в определенных случаях восстановление ненужно. Когда вы возвращаете некоторых из них возможно реализовать восстановление состояния работы программы, в некоторых нет. Когда вы возвращаете значение перечисления Result пользователю вашего кода, то вы предлагаете ему опцию, а не решаете за него. Пользователи решают

  • попытаться восстановить текущую работу программы или самим вызвать макрос panic!, создав невосстанавливаемую ситуацию. Возвращение значение перечисления Result - это поведение по умолчанию.

Примеры, шаблоны кода и тесты: все что нужны для паники

Очень мало бывает таких ситуаций, когда будет необходимо изменить это поведение. Далее, мы разберем работы с макросом panic!. Мы напишем и разберем код, протестируем его. Далее мы смоделируем ситуации, приводящие к срабатыванию panic! в коде стандартной библиотеки. Выбор вспомогательного метода обработки ошибок зависит от целей и задач, которые вы ставите. Методы unwrap и expect весьма удобны при начальном написании, кода.

Случаи, когда вы можете получить больше информации для анализа, чем компилятор

Бывают ситуации, когда наилучшим решением было бы вызов метода unwrap, в тех случаях, когда часть кода вашей программы ожидает значение Ok. К сожалению, не всегда компилятор может корректно понять логику вашей программы. Если в соответствующем месте кода вашей программы вам необходимо получить результат работы в виде значения Result, то в этом случае, использование метода unwrap - наилучший выбор. Пример:

use std::net::IpAddr;

fn main() {

    let home = "127.0.0.1".parse::<IpAddr>().unwrap();
    println!("{}", home);
}

В данном примере, мы создаём экземпляр перечисления IpAddr и защищаем код от возможных негативных последствий результата работы анализатора с помощью метода unwrap.

Руководство по обработке ошибок

Рекомендуем использовать макрос `panic!, когда существует вероятность того, что программа может войти в ошибочное состояние. Ошибочное состояние - это не выполняются предположения, соглашения, когда могут быть недоступны или утеряны какие-либо данные. Ошибочное состояние - это непредвиденная основной логикой программы проведение. Основные тезисы ошибочного состояния:

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

Если пользователи вашего кода введут неправильные входные данные, то лучшим способом реакции на них может быть макрос panic! и информирование о этом. Также макрос panic! удобен при работе со сторонними библиотеками, логику работы кода, которых вы не можете контролировать.

Если программа вошла в ошибочное состояние, но это состояние было спрогнозировано, наилучшим решением - вернуть значение перечисления Result с информацией о проблеме.

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

В стандартной библиотеке наиболее частый вариант обработки ошибок - использования panic!. Это делается для безопасности. После получения данный информации, программист решает что делать и как лучше в данном конкретном случае обработать ошибку. В документации сообщается, если такое поведение возможно.

Множественные проверки на ошибки, конечно же делают ваш код более понятным в работе, но в тоже время достаточно неудобны для чтения. Для решения этого вопроса, в библиотеке Rust существует система типов (тех, которые могут быть обработаны компилятором). Эти типы помогают сделать Ваш код компактным и удобным для чтения. Если ваша функция имеет определённый параметр, вы даёте компилятору больше информации для анализа и принятия решений. Чем большое будет ограничений на тип данных, тем безопаснее будет ваш код.

Создание типа данных для проверки

Давайте рассмотрим идею использования типов для проверки корректности данных. Для этого будем использовать пример нашей программы "Игры в угадай число" (Глава 2). В программе мы не анализировали входные данные. В ней мы просто проверяли данные на совпадение. Было бы удобно и интересно, если бы программа реагировала на введенные неправильные данные более точно.

Один из способов решения - анализ строки. Рассмотрим пример:

loop {
    // snip

    let guess: i32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => continue,
    };

    if guess < 1 || guess > 100 {
        println!("The secret number will be between 1 and 100.");
        continue;
    }

    match guess.cmp(&secret_number) {
    // snip
}

Выражение if проверяет корректность значения - не выходит ли оно за заданный диапазон. Если значение в if - положительно - вызывается continue и итерация повторяется. После успешного прохождения дополнительных проверок - происходит сравнение значения с секретным числом.

Хотя, конечно, подобные проверки - не идеальное решение, это может быть важно, когда в программе необходимо быть уверенным, что далее будет производиться работа с числом, входящим в определенный диапазон.

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

Создадим структуру Guess, которая решает поставленную задачу:


# #![allow(unused_variables)]
#fn main() {
pub struct Guess {
    value: u32,
}

impl Guess {
    pub fn new(value: u32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess {
            value
        }
    }

    pub fn value(&self) -> u32 {
        self.value
    }
}
#}

Listing 9-8: Тип Guess, который создаёт свой экземпляр, только на основе данных, соответствующие заданным условиям

Мы определили структуру с именем Guess, которая имеет поле value типа u32.

Далее, мы реализуем функцию new, которая создаёт новый экземпляр Guess. У функции new имеется один входной параметр value типа u32. Код функции new тестирует входной параметр, чтобы убедиться, что его значение находится между 1 и 100. Если значение выходит за эти приделы, будет вызван макрос panic!. Условия при котором будет вызван этот макрос будут описаны в генерируемой документации к функции Guess::new. Мы поговорим подробнее о документации в Главе 14. Если результаты проверки значения value будут положительными - будет создан экземпляр структуры Guess.

Далее, мы реализуем метод value, который использует ссылку self и не имеет других параметров. Этот метод возвращает значение поля value типа данных u32. Это синтаксическая структура похожа на реализацию свойства для чтения. Этот метод необходим для доступа к значению поля value, т.к. это доступ к этом полю закрыт соответствующим спецификатором. Пользователи данной структуры обязаны использовать метод Guess::new для создания экземпляра.

Использование экземпляра структуры Guess заметно повышает читабельность кода, устраняет повторяемость ненужных проверок. Как следствие всё это должно привести к повышению производительности программ.

Полный код улучшенного примера программы "Угадай число":

extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

pub struct Guess {
    value: u32,
}

impl Guess {
    pub fn new(value: u32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess { value }
    }

    pub fn value(&self) -> u32 {
        self.value
    }
}

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1, 101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");


        let guess: i32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        let guess_item: Guess = Guess::new(guess as u32);
        let guess: u32 = guess_item.value();

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

Итоги

Опции отслеживания и генерации сообщений об ошибках в Rust реализованы таким образом, чтобы помочь создавать удобный для чтения и использования код. Макрос panic! сигнализирует о том, что программа находится в аварийной ситуации и предоставляет возможности как для остановки работы программы, так и для восстановления её работы. Результатом работы макроса panic! является значение перечисления Result. Вы можете использовать содержание данного значения для анализа случившийся проблемы.

Далее, мы познакомится с удобными практиками стандартной библиотеки использования перечислений Option и Result в шаблонах и рассмотрим возможности работы с шаблонами.