Spec-Zone.ru › Crystal

Обработка исключений

В Crystal ошибки обрабатываются с помощью выбрасывания и перехвата исключений.

Выбрасывание исключения

Исключения выбрасываются вызовом метода верхнего уровня raise. В отличие от других ключевых слов, raise — это обычный метод с двумя перегрузками: одна принимает String, а другая принимает экземпляр Exception:

raise "OH NO!"
raise Exception.new("Some error")

Вариант со String просто создаёт новый экземпляр Exception с указанным сообщением.

Выбрасывать можно только экземпляры Exception или его подклассов.

Определение пользовательских исключений

Чтобы определить пользовательский тип исключения, достаточно создать подкласс Exception:

class MyException < Exception
end

class MyOtherException < Exception
end

Как и всегда, для исключения можно определить конструктор или использовать конструктор по умолчанию.

Перехват исключений

Чтобы перехватить любое исключение, используйте выражение begin ... rescue ... end:

begin
  raise "OH NO!"
rescue
  puts "Rescued!"
end

# Output: Rescued!

Чтобы получить доступ к перехваченному исключению, можно указать переменную в условии rescue:

begin
  raise "OH NO!"
rescue ex
  puts ex.message
end

# Output: OH NO!

Чтобы перехватить исключение только одного типа (или любого из его подклассов):

begin
  raise MyException.new("OH NO!")
rescue MyException
  puts "Rescued MyException"
end

# Output: Rescued MyException

А чтобы получить к нему доступ, используйте синтаксис, похожий на ограничения типов:

begin
  raise MyException.new("OH NO!")
rescue ex : MyException
  puts "Rescued MyException: #{ex.message}"
end

# Output: Rescued MyException: OH NO!

Можно указать несколько условий rescue:

begin
  # ...
rescue ex1 : MyException
  # only MyException...
rescue ex2 : MyOtherException
  # only MyOtherException...
rescue
  # any other kind of exception
end

Также можно перехватить сразу исключения нескольких типов, указав объединённый тип:

begin
  # ...
rescue ex : MyException | MyOtherException
  # only MyException or MyOtherException
rescue
  # any other kind of exception
end

else

Условие else выполняется, только если ни одно исключение не было перехвачено:

begin
  something_dangerous
rescue
  # execute this if an exception is raised
else
  # execute this if an exception isn't raised
end

Условие else можно указать, только если указано хотя бы одно условие rescue.

ensure

Условие ensure выполняется в конце выражения begin ... end или begin ... rescue ... end независимо от того, было ли выброшено исключение:

begin
  something_dangerous
ensure
  puts "Cleanup..."
end

# Will print "Cleanup..." after invoking something_dangerous,
# regardless of whether it raised or not

Или:

begin
  something_dangerous
rescue
  # ...
else
  # ...
ensure
  # this will always be executed
end

Условия ensure обычно используются для очистки, освобождения ресурсов и тому подобного.

Краткая форма синтаксиса

Для обработки исключений предусмотрена краткая форма синтаксиса: считайте, что определение метода или блока — это неявное выражение begin ... end, а затем укажите условия rescue, else и ensure:

def some_method
  something_dangerous
rescue
  # execute if an exception is raised
end

# The above is the same as:
def some_method
  begin
    something_dangerous
  rescue
    # execute if an exception is raised
  end
end

С ensure:

def some_method
  something_dangerous
ensure
  # always execute this
end

# The above is the same as:
def some_method
  begin
    something_dangerous
  ensure
    # always execute this
  end
end

# Similarly, the shorthand also works with blocks:
(1..10).each do |n|
  # potentially dangerous operation


rescue
  # ..
else
  # ..
ensure
  # ..
end

Вывод типов

Переменные, объявленные внутри части begin обработчика исключений, также получают тип Nil, если рассматривать их внутри тела rescue или ensure. Например:

begin
  a = something_dangerous_that_returns_Int32
ensure
  puts a + 1 # error, undefined method '+' for Nil
end

Это происходит даже в том случае, если something_dangerous_that_returns_Int32 никогда не выбрасывает исключение или если a присвоили значение, а затем выполнили метод, который потенциально может выбросить исключение:

begin
  a = 1
  something_dangerous
ensure
  puts a + 1 # error, undefined method '+' for Nil
end

Хотя очевидно, что a всегда будет присвоено значение, компилятор всё равно считает, что a могло не получить возможность инициализироваться. Возможно, в будущем эта логика улучшится, но сейчас она заставляет сводить обработчики исключений к необходимому минимуму, благодаря чему намерение кода становится яснее:

# Clearer than the above: `a` doesn't need
# to be in the exception handling code.
a = 1
begin
  something_dangerous
ensure
  puts a + 1 # works
end

Альтернативные способы обработки ошибок

Хотя исключения — один из механизмов обработки ошибок, это не единственный доступный вариант. Выбрасывание исключения требует выделения памяти, а выполнение обработчика исключений обычно происходит медленно.

Стандартная библиотека обычно предоставляет пару методов для выполнения одной задачи: один выбрасывает исключение, другой возвращает nil. Например:

array = [1, 2, 3]
array[4]  # raises because of IndexError
array[4]? # returns nil because of index out of bounds

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

To the extent possible under law, the persons who contributed to this workhave waived
all copyright and related or neighboring rights to this workby associating CC0 with it.
https://crystal-lang.org/reference/1.2/syntax_and_semantics/exception_handling.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API