У Elixir есть три механизма ошибок: ошибки, броски и выходы. Давайте рассмотрим каждый механизм подробно.
ошибка
Ошибки (или исключения) используются, когда в коде происходят исключительные вещи. Пример ошибки можно получить, если попытаться добавить число в строку —
IO.puts(1 + "Hello")
Когда вышеуказанная программа запущена, она выдает следующую ошибку:
** (ArithmeticError) bad argument in arithmetic expression :erlang.+(1, "Hello")
Это была встроенная ошибка образца.
Повышение ошибок
Мы можем поднять ошибки, используя функции повышения. Давайте рассмотрим пример, чтобы понять то же самое —
#Runtime Error with just a message raise "oops" # ** (RuntimeError) oops
Другие ошибки могут быть вызваны с помощью параметра allow / 2, передающего имя ошибки и список ключевых аргументов.
#Other error type with a message raise ArgumentError, message: "invalid argument foo"
Вы также можете определить свои собственные ошибки и поднять их. Рассмотрим следующий пример —
defmodule MyError do defexception message: "default message" end raise MyError # Raises error with default message raise MyError, message: "custom message" # Raises error with custom message
Спасая ошибки
Мы не хотим, чтобы наши программы внезапно закрывались, скорее, ошибки должны тщательно обрабатываться. Для этого мы используем обработку ошибок. Мы спасаем ошибки, используя конструкцию try / rescue . Давайте рассмотрим следующий пример, чтобы понять то же самое —
err = try do raise "oops" rescue e in RuntimeError -> e end IO.puts(err.message)
Когда вышеуказанная программа запущена, она дает следующий результат —
oops
Мы обработали ошибки в операторе спасения, используя сопоставление с образцом. Если мы не используем ошибку и просто хотим использовать ее для идентификации, мы также можем использовать форму:
err = try do 1 + "Hello" rescue RuntimeError -> "You've got a runtime error!" ArithmeticError -> "You've got a Argument error!" end IO.puts(err)
При запуске вышеуказанной программы, она дает следующий результат —
You've got a Argument error!
ПРИМЕЧАНИЕ. — Большинство функций в стандартной библиотеке Elixir реализованы дважды: один раз возвращает кортежи, а другой — ошибки. Например, File.read и File.read! функции. Первый вернул кортеж, если файл был успешно прочитан, и если произошла ошибка, этот кортеж использовался, чтобы указать причину ошибки. Второй вызвал ошибку, если возникла ошибка.
Если мы используем подход первой функции, то нам нужно использовать case для сопоставления с шаблоном ошибки и действовать в соответствии с этим. Во втором случае мы используем подход try rescue для подверженного ошибкам кода и соответственно обрабатываем ошибки.
Броски
В эликсире значение может быть выброшено, а затем поймано. Throw и Catch зарезервированы для ситуаций, когда невозможно извлечь значение, если не использовать throw и catch.
Практические примеры довольно необычны, за исключением случаев взаимодействия с библиотеками. Например, давайте теперь предположим, что модуль Enum не предоставил никакого API для поиска значения и что нам нужно было найти первое кратное 13 в списке чисел —
val = try do Enum.each 20..100, fn(x) -> if rem(x, 13) == 0, do: throw(x) end "Got nothing" catch x -> "Got #{x}" end IO.puts(val)
Когда вышеуказанная программа запущена, она дает следующий результат —
Got 26
Выход
Когда процесс умирает по «естественным причинам» (например, необработанные исключения), он посылает сигнал выхода. Процесс также может умереть, явно отправив сигнал на выход. Давайте рассмотрим следующий пример —
spawn_link fn -> exit(1) end
В приведенном выше примере связанный процесс умер, отправив сигнал выхода со значением 1. Обратите внимание, что выход также можно «перехватить» с помощью try / catch. Например —
val = try do exit "I am exiting" catch :exit, _ -> "not really" end IO.puts(val)
Когда вышеуказанная программа запущена, она дает следующий результат —
not really
После
Иногда необходимо убедиться, что ресурс очищен после некоторого действия, которое потенциально может вызвать ошибку. Конструкция try / after позволяет вам это сделать. Например, мы можем открыть файл и использовать предложение after, чтобы закрыть его, даже если что-то пойдет не так.
{:ok, file} = File.open "sample", [:utf8, :write] try do IO.write file, "olá" raise "oops, something went wrong" after File.close(file) end
Когда мы запустим эту программу, она выдаст нам ошибку. Но оператор after гарантирует, что дескриптор файла будет закрыт при любом таком событии.