コンテンツにスキップ

例外処理

Crystalのエラー処理方法は、例外を発生させて捕捉することです。

例外の発生

トップレベルの`raise`メソッドを呼び出すことで例外を発生させます。他のキーワードとは異なり、`raise`は、文字列を受け入れるものと、例外インスタンスを受け入れるものの2つのオーバーロードを持つ通常のメソッドです。

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

文字列バージョンは、そのメッセージを持つ新しい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!

1つのタイプの例外(またはそのサブクラス)のみを捕捉するには、次のようにします。

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

# Output: Rescued MyException

有効な型制限は、`::Exception`のサブクラス、モジュール型、およびこれらの共用体です。

そして、それにアクセスするには、型制限と同様の構文を使用します。

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`句は、少なくとも1つの`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`部分内で宣言された変数は、`rescue`または`ensure`本体内で考慮される場合、`Nil`型にもなります。例えば

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

エラー処理の代替方法

例外はエラー処理のメカニズムの1つとして利用できますが、唯一の選択肢ではありません。例外を発生させるにはメモリを割り当てる必要があり、例外ハンドラの実行は一般に低速です。

標準ライブラリは通常、何かを実行するための2つのメソッドを提供します。1つは例外を発生させ、もう1つは`nil`を返します。例えば

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

通常の規則では、メソッドのこのバリアントが例外を発生させる代わりに`nil`を返すことを示すために、代替の「質問」メソッドを提供します。これにより、ユーザーは例外を処理するか、`nil`を処理するかを選択できます。ただし、例外はエラー処理ロジックでコードを汚染しないため、依然として好ましい方法であるため、これはすべてのメソッドで利用できるわけではないことに注意してください。