Rust обработка ошибок

Обновлено: 06.07.2024

Error handling is the process of handling the possibility of failure. For example, failing to read a file and then continuing to use that bad input would clearly be problematic. Noticing and explicitly managing those errors saves the rest of the program from various pitfalls.

There are various ways to deal with errors in Rust, which are described in the following subchapters. They all have more or less subtle differences and different use cases. As a rule of thumb:

An explicit panic is mainly useful for tests and dealing with unrecoverable errors. For prototyping it can be useful, for example when dealing with functions that haven't been implemented yet, but in those cases the more descriptive unimplemented is better. In tests panic is a reasonable way to explicitly fail.

The Option type is for when a value is optional or when the lack of a value is not an error condition. For example the parent of a directory - / and C: don't have one. When dealing with Option s, unwrap is fine for prototyping and cases where it's absolutely certain that there is guaranteed to be a value. However expect is more useful since it lets you specify an error message in case something goes wrong anyway.

When there is a chance that things do go wrong and the caller has to deal with the problem, use Result . You can unwrap and expect them as well (please don't do that unless it's a test or quick prototype).

For a more rigorous discussion of error handling, refer to the error handling section in the official book.

Вспомните раздел "Обработка потенциального сбоя с помощью типа Result " главы 2: мы использовали там перечисление Result , имеющее два варианта, Ok и Err для обработки сбоев. Само перечисление определено следующим образом:

Типы T и E являются параметрами обобщённого типа: мы обсудим обобщённые типы более подробно в Главе 10. Все что вам нужно знать прямо сейчас - это то, что T представляет тип значения, которое будет возвращено в случае успеха внутри варианта Ok , а E представляет тип ошибки, которая будет возвращена при сбое внутри варианта Err . Так как тип Result имеет эти типовые параметры (generic type parameters), мы можем использовать тип Result и его методы, которые определены в стандартной библиотеке, в ситуациях, когда тип успешного значения и значения ошибки, которые мы хотим вернуть, отличаются.

Давайте вызовем функцию, которая возвращает значение Result , потому что может потерпеть неудачу. В листинге 9-3 мы пытаемся открыть файл.

Листинг 9-3: Открытие файла

Ошибка говорит нам о том, что возвращаемым типом функции File::open является Result<T, E> . Типовой параметр T здесь равен типу успешного выполнения, std::fs::File , то есть дескриптору файла. Тип E , используемый в значении ошибки, равен std::io::Error .

Этот возвращаемый тип означает, что вызов File::open может завершиться успешно и вернуть дескриптор файла, с помощью которого можно читать из файла или писать в него. Вызов функции также может завершиться ошибкой: например, файла может не существовать или у нас может не быть прав на доступ к нему. Функция File::open должна иметь способ сообщить нам, был ли её вызов успешен или потерпел неудачу и одновременно возвратить либо дескриптор файла либо информацию об ошибке. Эта информация - именно то, что возвращает перечисление Result .

Когда вызов File::open успешен, значение в переменной f будет экземпляром Ok , внутри которого содержится дескриптор файла. Если вызов не успешный, значением переменной f будет экземпляр Err , который содержит больше информации о том, какая ошибка произошла.

Необходимо дописать в код листинга 9-3 выполнение разных действий в зависимости от значения, которое вернёт вызов File::open . Листинг 9-4 показывает один из способов обработки Result - пользуясь базовым инструментом языка, таким как выражение match , рассмотренным в Главе 6.

Листинг 9-4: Использование выражения match для обработки возвращаемых вариантов типа Result

Обратите внимание, что также как перечисление Option , перечисление Result и его варианты, входят в область видимости благодаря авто-импорту (prelude), поэтому не нужно указывать Result:: перед использованием вариантов Ok и Err в ветках выражения match .

Здесь мы говорим Rust, что когда результат - это Ok , то надо вернуть внутреннее значение file из варианта Ok , и затем мы присваиваем это значение дескриптора файла переменной f . После match мы можем использовать дескриптор файла для чтения или записи.

Обработка ошибок в Rust

В Rust есть два перечисления на которых строится, практически, вся обработка ошибок: Option и Result . Рассмотрим их подробнее.

Option

Семантика его проста: либо мы имеем некоторые данные, либо они отсутствуют. Таким образом, возвращая из функции Option мы, тем самым, выражаем мысль, что, возможно, мы не получим ожидаемый результат.

Result

В отличие от Option , Result позволяет установить не только отсутствие данных, но и причину, в связи с которой они отсутствуют.

Рассмотрим теперь, как в Rust выразить три действия при ошибке, которые мы перечислили в начале статьи:

Завершить работу приложения.

Пропустить ошибку на более высокий уровень.

Завершаем работу приложения

Так как для обработки ошибок, обычно, используются Option и Result , для завершения работы программы нужно писать что-то вроде:

Для удобства, Option и Result содержат ассоциированную функцию unwrap() , позволяющую не повторять приведённый выше код. Если перечисление находится в состоянии успеха, то unwrap() достаёт данные из перечисления и позволяет с ними работать. В случае ошибки, unwrap() вызывает панику. У unwrap() есть аналог, позволяющий добавить произвольный текст к выводу: expect() .

Обрабатываем ошибку

Вызывая функцию, которая может не сработать, мы получаем в качестве результата Option или Result . Если нам известно, что делать в случае неудачи, мы должны выразить свои намерения через конструкции языка. Рассмотрим пример:

В данном примере мы используем разные способы замены строки настроек, в случае неудачи при её получении:

s1 - явно сопоставляем Option с шаблоном и указываем альтернативу.

s2 - используем функцию unwrap_or_default() , которая в случае отсутствия данных возвращает значение по умолчанию (пустую строку).

s3 - используем unwrap_or() , возвращающую свой аргумент в случае отсутствия данных.

s4 - используем unwrap_or_else() , возвращающую результат вызова переданного в неё функтора в случае отсутствия данных. Такой подход позволяет вычислять значение резервного варианта не заранее, а только в случае пустого Option .

Перечисление Result предоставляет аналогичные методы.

Пропускаем ошибку выше

Для начала, сделаем это вручную. Для Option :

Как видно в примерах, такой подход требует большого количества match конструкций. Это усложняет код, ухудшает его читабельность и добавляет разработчику дополнительной рутинной работы. Во избежание всего этого, создатели языка ввели оператор ? . Расположенный после Option или Result , он заменяет собой match конструкцию. В случае наличия значения, он возвращает его для дальнейшего использования. В случае ошибки, возвращает её из функции. Воспользуемся им в наших примерах. Для Option всё очевидно:

Для Result всё обстоит немного сложнее. Ведь в случае, если происходит LoadDllError , то компилятору нужно как-то преобразовать её в InitModuleError для возврата из функции. Для этого оператор ? пытается найти способ преобразования для этих ошибок. Для того, чтобы создать такой способ, в стандартной библиотеке существует трейт From . Воспользуемся им:

Иными словами, Rust требует явно описывать способы преобразования ошибок друг в друга при передаче их верхним уровням иерархии вызовов.

Динамические ошибки

Как видно из определения, он требует реализации трейтов Debug и Display . Таким образом, Rust вводит требования для всех типов реализующих Error : уметь выводить отладочную и текстовую информацию о себе. Рассмотрим на примере:

Содержание

Избегайте отбрасывания ошибок во время их преобразований

Крейт error-chain делает сопоставление с различными типами ошибок, возвращаемых функцией, возможным и относительно компактным. ErrorKind определяет тип ошибки.

Используется библиотека reqwest для запроса веб-службы генератора случайных целых чисел. Преобразует строковый ответ в целое число. Стандартная библиотека Rust, библиотека reqwest и веб-сервис могут вернуть ошибки. Хорошо определённые ошибки Rust используют foreign_links . Дополнительный вариант ErrorKind для ошибки веб-службы использует блок errors в error_chain! макросе.

Трейты

Трейты схожи с концепцией интерфейсов в других языках. Их можно реализовывать на типах, расширяя их функционал. Также, функции могут накладывать ограничение на трейты принимаемых аргументов. Ограничения проверяются при компиляции. Например:

В данном примере мы определили трейт Print и реализовали его для встроенного целочисленного типа i32 . Также, мы определили функцию print_value() , принимающую обобщённый (generic) аргумент value , ограничив варианты его типа только теми, которые реализуют трейт Print . Поэтому в main() мы можем вызвать print_value() только с i32 аргументом.

Более того, при определённых условиях, можно создавать трейт объекты (trait objects). Это динамический объекты, которые могут быть созданы из любого типа, реализующего данный трейт. Конкретная реализация метода трейта выбирается динамически (dynamic dispatch). Например:

В данном коде нет необходимости делать функцию say_something() обобщённой, так как конкретная реализация, скрытая за трейт объектом разрешается во время выполнения программы, а не при компиляции.

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

Данный код не компилируется, так как мы пытаемся реализовать трейт Mammal на типе Dog , не реализовав Animal , от которого Mammal унаследован.

Перечисления с данными

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

Ключевое слово match позволяет описать действия для различных вариантов перечисления и их содержимого.

Перечисления могут быть обобщенными:

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

Полезные библиотеки

Рассмотрим две популярные библиотеки, упрощающие обработку ошибок: thiserror и anyhow.

thiserror

Данная библиотека предоставляет макросы, позволяющие упростить рутинные действия: описание способов конвертации ошибок через From , и реализация трейтов Error и Display . Рассмотрим на примере:

Данные макросы значительно сокращают объём boilerplate кода для обработки ошибок.

anyhow

Данную библиотеку удобно использовать, когда единственное, что интересует нас в ошибке - её текстовое описание. anyhow предоставляет структуру Error . В неё может быть сконвертирован любой объект, реализующий трейт std::Error , что значительно упрощает распространение ошибки по иерархии вызовов. Помимо этого, anyhow::Error позволяет добавлять текстовое описание контекста, в котором произошла ошибка. Эта библиотека сочетается с thiserror. Пример:

Макрос anyhow::bail!() в примере создаёт anyhow::Error с заданным описанием и возвращает её из функции. Псевдоним anyhow::Result определяется так:

Обработка различных ошибок с помощью match

Код в листинге 9-4 будет вызывать panic! независимо от того, почему вызов File::open не удался. Мы бы хотели предпринять различные действия для разных причин сбоя. Если открытие File::open не удалось из-за отсутствия файла, мы хотим создать файл и вернуть его дескриптор. Если вызов File::open не удался по любой другой причине (например, потому что у нас не было прав на открытие файла), то мы хотим вызвать panic! как у нас сделано в листинге 9-4. Посмотрите листинг 9-5, в котором мы добавили дополнительное внутреннее выражение match .

Листинг 9-5: Обработка различных ошибок разными способами

Типом значения возвращаемого функцией File::open внутри Err варианта является io::Error , структура из стандартной библиотеки. Данная структура имеет метод kind , который можно вызвать для получения значения io::ErrorKind . Перечисление io::ErrorKind из стандартной библиотеки имеет варианты, представляющие различные типы ошибок, которые могут появиться при выполнении операций в io (крейте который занимается проблемами ввода/вывода данных). Вариант, который мы хотим использовать, это ErrorKind::NotFound . Он даёт информацию, о том, что файл который мы пытаемся открыть ещё не существует. Итак, во второй строке мы вызываем сопоставление шаблона с переменной f и попадаем в ветку с обработкой ошибки, но также у нас есть внутренняя проверка для сопоставления error.kind() ошибки.

Достаточно про match ! Код с match является очень удобным, но также достаточно примитивным. В Главе 13 вы узнаете про замыкания (closures); тип Result<T, E> имеет много методов, реализованных с помощью выражения match и принимающих замыкание в качестве входного значения. Использование данных методов сделает ваш код более лаконичным. Более опытные разработчики могли бы написать код как в листинге 9-5, вместо нашего:

Несмотря на то, что данный код имеет такое же поведение как в листинге 9-5, он не содержит ни одного выражения match и проще для чтения. Рекомендуем вам вернутся к примеру этого раздела после того как вы прочитаете Главу 13 и изучите метод unwrap_or_else по документации стандартной библиотеки. Многие из методов о которых вы узнаете в документации и Главе 13 могут очистить код от больших, вложенных выражений match при обработке ошибок.

Сокращённые способы обработки ошибок unwrap и expect

Использование match работает неплохо, однако может выглядеть несколько многословно и не всегда хорошо передаёт намерения. У типа Result<T, E> есть много методов для различных задач. Один из них, unwrap , является сокращённым методом, который реализован прямо как выражение match из листинга 9-4. Если значение Result это Ok , unwrap вернёт значение внутри Ok . Если же Result это Err , unwrap вызовет макрос panic! . Вот пример unwrap в действии:

Проброс ошибок

Когда вы пишете функцию, реализация которой вызывает что-то, что может завершиться ошибкой, вместо обработки ошибки в этой функции, вы можете вернуть ошибку в вызывающий код, чтобы он мог решить, что с ней делать. Такой приём известен как распространение ошибки, propagating the error. Благодаря нему мы даём больше контроля вызывающему коду, где может быть больше информации или логики, которая диктует, как ошибка должна обрабатываться, чем было бы в месте появления этой ошибки.

Например, код программы 9-6 читает имя пользователя из файла. Если файл не существует или не может быть прочтён, то функция возвращает ошибку в код, который вызвал данную функцию:

Листинг 9-6: Функция, которая возвращает ошибки в вызывающий код, используя оператор match

Данную функцию можно записать гораздо короче. Чтобы больше проникнуться обработкой ошибок, мы сначала сделаем многое самостоятельно, а в конце покажем более короткий способ. Давайте сначала рассмотрим тип возвращаемого значения: Result<String, io::Error> . Здесь есть возвращаемое значение функции типа Result<T, E> где шаблонный параметр T был заполнен конкретным типом String и шаблонный параметр E был заполнен конкретным типом io::Error . Если эта функция выполнится успешно, будет возвращено Ok , содержащее значение типа String - имя пользователя прочитанное функцией из файла. Если же при чтении файла будут какие-либо проблемы, то вызываемый код получит значение Err с экземпляром io::Error , в котором содержится больше информации об ошибке. Мы выбрали io::Error в качестве возвращаемого значения функции, потому что обе операции, которые мы вызываем внутри этой функции, возвращают этот тип ошибки: функция File::open и метод read_to_string .

Тело функции начинается с вызова File::open . Затем мы обрабатываем значение Result возвращённое с помощью match аналогично коду match листинга 9-4, но вместо вызова panic! для случая Err делаем ранний возврат из данной функции и передаём ошибку из File::open обратно в вызывающий код, как ошибку уже текущей функции. Если File::open выполнится успешно, мы сохраняем дескриптор файла в переменной f и выполнение продолжается далее.

Код, вызывающий данный код, будет обрабатывать либо значение Ok , содержащее имя пользователя, либо значение Err , содержащее io::Error . Мы не знаем, что будет делать вызывающий код с этими значениями. Если вызывающий код получает значение Err , он может вызвать panic! и завершить программу, использовать имя пользователя по умолчанию, или например, попытается получить имя пользователя из какого-то другого места. У нас недостаточно информации о том, чего пытается достичь вызывающий код, поэтому мы пробрасываем всю информацию об успехе или ошибке наверх для её правильной обработки.

Такая схема распространения ошибок настолько распространена в Rust, что Rust предоставляет оператор вопросительный знак ? для простоты.

Сокращение для проброса ошибок: оператор ?

Код программы 9-6 показывает реализацию функции read_username_from_file , функционал которой аналогичен коду программы 9-5, но реализация использует оператор ? :

Листинг 9-7: Функция, которая возвращает ошибки в вызывающий код, используя оператор ?

Оператор ? , помещаемый после значения типа Result , работает почти таким же образом, как выражение match , которое мы определили для обработки значений типа Result в листинге 9-6. Если значение Result равно Ok , значение внутри Ok будет возвращено из этого выражения и программа продолжит выполнение. Если значение является Err , то Err будет возвращено из всей функции, как если бы мы использовали ключевое слово return , таким образом ошибка передаётся в вызывающий код.

Имеется разница между тем, что делает выражение match листинга 9-6 и оператор ? . Ошибочные значения при выполнении методов с оператором ? возвращаются через функцию from , определённую в типаже From стандартной библиотеки. Данный типаж используется для конвертирования ошибок одного типа в ошибки другого типа. Когда оператор ? вызывает функцию from , то полученный тип ошибки конвертируется в тип ошибки, который определён для возврата в текущей функции. Это удобно, когда функция возвращает один тип ошибки для представления всех возможных вариантов, из-за которых она может не завершиться успешно, даже если части кода функции могут не выполниться по разным причинам. Если каждый тип ошибки реализует функцию from определяя, как конвертировать себя в возвращаемый тип ошибки, то оператор ? позаботится об этой конвертации автоматически.

В коде примера 9-7 оператор ? в конце вызова функции File::open возвращает значения содержимого Ok в переменную f . Если же в при работе этой функции произошла ошибка, оператор ? произведёт ранний возврат из функции со значением Err . То же касается ? на конце вызова read_to_string .

Использование оператора ? позволят уменьшить количество строк кода и сделать реализацию проще. Написанный в предыдущем примере код можно
сделать ещё короче с помощью сокращения промежуточных переменных и конвейерного вызова нескольких методов подряд, как показано в листинге 9-8:

Листинг 9-8. Цепочка вызовов методов после оператора ?

Мы перенесли в начало функции создание новой переменной s типа String ; эта часть не изменилась. Вместо создания переменной f мы добавили вызов read_to_string непосредственно к результату File::open("hello.txt")? , У нас ещё есть ? в конце вызова read_to_string , и мы по-прежнему возвращаем значение Ok , содержащее имя пользователя в s когда оба метода: File::open и read_to_string успешны, а не возвращают ошибки. Функциональность снова такая же, как в листинге 9-6 и листинге 9-7; это просто другой, более эргономичный способ решения той же задачи.

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

Листинг 9-9: Использование fs::read_to_string вместо открытия и чтения файла

Чтение файла в строку довольно распространённая операция, так что Rust предоставляет удобную функцию fs::read_to_string , которая открывает файл, создаёт новую String , читает содержимое файла, размещает его в String и возвращает её. Конечно, использование функции fs::read_to_string не даёт возможности объяснить обработку всех ошибок, поэтому мы сначала изучили длинный способ.

Оператор ? можно использовать для функций возвращающих Result

Оператор ? может использоваться в функциях, которые имеют возвращаемый тип Result , потому что он работает так же, как выражение match , определённое в листинге 9-6. Той частью match , которая требует возвращаемый тип Result , является код return Err(e) , таким образом возвращаемый тип функции может быть Result , чтобы быть совместимым с этим return .

Посмотрим что происходит, если использовать оператор ? в теле функции main , которая, как вы помните, имеет возвращаемый тип () :

Эта ошибка указывает на то, что разрешено использовать оператор ? только в функциях, которые возвращают Result или Option или другой тип, который реализует типаж std::ops::Try . Если вы пишете функцию, которая не возвращает один из этих типов, и хотите использовать ? при вызове других функций, возвращающих Result<T, E> , у вас есть два варианта решения этой проблемы. Один из методов - изменить тип возвращаемого значения вашей функции на Result<T, E> , при условии что у вас нет ограничений, препятствующих этому. Другая техника заключается в использовании match или одного из методов Result<T, E> для обработки Result<T, E> любым подходящим способом.

Функция main является специальной и имеются ограничение на то, какой должен быть её возвращаемый тип. Один из допустимых типов для main это () , другой - Result<T, E> , как в примере:

Тип Box<dyn Error> называется типаж объектом, о котором мы поговорим в разделе "Использование типаж объектов, которые допускают значения различных типов" Главы 17. А пока вы можете читать обозначение Box<dyn Error> как "любая ошибка". Использование ? в main функции с этим возвращаемым типом также разрешено.

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


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

Получение трассировки (backtrace) сложных сценариев ошибок

Этот рецепт показывает, как обрабатывать сложный сценарий ошибки, а затем напечатать обратную трассировку. Он полагается на chain_err для расширения стека ошибок путём добавления новых ошибок. Стек ошибок можно развернуть, что обеспечивает лучший контекст для понимания причины возникновения ошибки.

Приведённые ниже рецепты пытаются десериализовать значение 256 в тип u8 . Ошибка всплывёт из Serde, затем csv и наконец к коду пользователя.

Отображение трассировки ошибки:

Запустите рецепт с RUST_BACKTRACE=1 чтобы отобразить подробную трассировку backtrace связанную с этой ошибкой.

Немного о синтаксисе Rust


Механизм обработки ошибок включает себя две особенности языка Rust: перечисления с данными и трейты.

Заключение

В начале статьи мы рассмотрели три возможных варианта действий, при получении ошибки: завершить работу программы, обработать ошибку и передать ошибку вверх по иерархии вызовов. Далее, разобравшись с особенностями синтаксиса, мы разобрались на примерах, как выразить наши намерения по отношению к ошибке на языке Rust. Мы увидели, что любой из вариантов поведения должен быть выражен явно. Такой подход повышает надёжность приложения, так как не позволяет разработчику случайно проигнорировать ошибку. С другой стороны, явное описание своих намерений требует дополнительных усилий. Минимизировать эти усилия позволяют библиотеки thiserror и anyhow.

Благодарю за внимание. Поменьше вам ошибок!

Статья написана в преддверии старта курса Rust Developer. Приглашаю всех желающих на бесплатный урок, в рамках которого на примере построения простого веб сервиса рассмотрим популярный веб-фреймворк actix-web в связке с MongoDB + Redis и другие полезные библиотеки для backend разработки.

Обрабатывает ошибку, возникающую при попытке открыть файл, который не существует. Это достигается с помощью библиотеки error-chain, которая заботится об автоматической генерации большого количества стандартного кода, необходимого для обработки ошибок в Rust .

Io(std::io::Error) внутри foreign_links позволяет автоматическое преобразование из std::io::Error в объявленный тип в error_chain! , реализующий типаж Error .

Приведённый ниже рецепт расскажет, как долго работает система, открыв файл Unix /proc/uptime и проанализировав его содержимое, получив первое число. Он возвращает время безотказной работы, если нет ошибки.

Другие рецепты в этой книге скрывают сгенерированный код error-chain, его можно увидеть, развернув код с помощью кнопки ⤢.

Что делать с ошибкой?


Для начала, порассуждаем о возможных вариантах действий при возникновении ошибки в ходе выполнения программы. Вариантов у нас, в конечном счёте, всего три:

Завершить работу программы. Это самый простой вариант, не требующий больших усилий от разработчика. Он применим в случаях, когда ошибка не позволяет программе корректно выполнять свои функции. В качестве примера можно рассмотреть приложение, представляющее собой обёртку над некоторой динамической библиотекой. Скажем, графический интерфейс. Приложение поставляется с этой библиотекой и не несёт какой-либо пользы в отрыве от неё. Разумно предположить, что приложение не должно работать без этой библиотеки. Поэтому, вполне обосновано, при ошибке загрузки библиотеки, прерывать работу приложения.

Обработать ошибку. Чтобы программа могла продолжить выполнение после возникновения ошибки, требуется отреагировать на эту ошибку так, чтобы корректная часть программы могла далее выполнять свои функции, потеряв, возможно, доступ к некоторым возможностям. Рассмотрим приложение, использующее модули в виде динамических библиотек. В данном случае, отсутствие библиотеки модуля, необходимого для выполнения выбранного пользователем действия - это повод отменить выполнение действия, а не прерывать программу. Как вариант, сообщим пользователю об отсутствии требуемого модуля и предложим другие варианты работы.

Пропустить ошибку на более высокий уровень. Далеко не всегда, в момент получения ошибки, есть возможность однозначно выбрать способ её обработки. В таких случаях можно передать ответственность по обработке ошибки выше по иерархии вызовов. Например, подсистема загрузки конфигурационных файлов может использоваться сразу в нескольких других системах приложения. Поэтому не разумно обрабатывать случай отсутствия запрошенного файла внутри неё, одинаково для всех обратившихся. Более подходящий вариант - предоставить каждой клиентской системе самой решать, как действовать в случае ошибки загрузки конфигурации.

Ошибки, после которых приложение должно завершить работу называют неустранимыми. Остальные - устранимыми. Тип конкретной ошибки не зависит от самой ошибки (некорректный ввод, файл не найден, . ). Он зависит от решения разработчика: стоит ли продолжать работу программы при этой ошибке, или программа больше ничего не может сделать. Нужно искать компромисс, исходя из требований к надёжности системы и имеющимися ресурсами для разработки, так как восстановление после ошибки требует от разработчика некоторых усилий. В лучшем случае, достаточно просто сообщить о ней пользователю и продолжить работу. Но бывают ситуации, когда для восстановления от ошибки требуется создать целую резервную систему.

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

Читайте также: