Raising an Exception

You can raise an exception using the raise function. At its simplest, you pass it a string and it generates an exception of type RuntimeError.

 iex>​ ​raise​ ​"​​Giving up"
 **​ (RuntimeError) Giving up
  erl_eval.erl:572: :erl_eval.do_apply/6

You can also pass the type of the exception, along with other optional fields. All exceptions implement at least the message field.

 iex>​ ​raise​ RuntimeError
 **​ (RuntimeError) runtime error
  erl_eval.erl:572: :erl_eval.do_apply/6
 iex>​ ​raise​ RuntimeError, ​message:​ ​"​​override message"
 **​ (RuntimeError) override message
  erl_eval.erl:572: :erl_eval.do_apply/6

You can intercept exceptions using the try function. It takes a block of code to execute, and optional rescue, catch, and after clauses.

The rescue and catch clauses look a bit like the body of a case function—they take patterns and code to execute if the pattern matches. The subject of the pattern is the exception that was raised.

Here’s an example of exception handling in action. We define a module that has a public function, start. It calls a different helper function depending on the value of its parameter. With 0, it runs smoothly. With 1, 2, or 3, it causes the VM to raise an error, which we catch and report.

exceptions/exception.ex
 defmodule​ Boom ​do
 def​ start(n) ​do
 try​ ​do
  raise_error(n)
 rescue
  [ FunctionClauseError, RuntimeError ] ->
  IO.puts ​"​​no function match or runtime error"
  error ​in​ [ArithmeticError] ->
  IO.inspect error
  IO.puts ​"​​Uh-oh! Arithmetic error"
  reraise ​"​​too late, we're doomed"​, System.stacktrace
  other_errors ->
  IO.puts ​"​​Disaster! ​​#{​inspect other_errors​}​​"
 after
  IO.puts ​"​​DONE!"
 end
 end
 
 defp​ raise_error(0) ​do
  IO.puts ​"​​No error"
 end
 
 defp​ raise_error(val = 1) ​do
  IO.puts ​"​​About to divide by zero"
  1 / (val-1)
 end
 
 defp​ raise_error(2) ​do
  IO.puts ​"​​About to call a function that doesn't exist"
  raise_error(99)
 end
 
 defp​ raise_error(3) ​do
  IO.puts ​"​​About to try creating a directory with no permission"
  File.mkdir!(​"​​/not_allowed"​)
 end
 end

We define three different exception patterns. The first matches one of the two exceptions, FunctionClauseError or RuntimeError. The second matches an ArithmeticError and stores the exception value in the variable error. And the last clause catches any exception into the variable other_error.

We also include an after clause. This will always run at the end of the try function, regardless of whether an exception was raised.

Finally, look at the handling of ArithmeticError. As well as reporting the error, we call reraise. This raises the current exception, but lets us add a message. We also pass in the stack trace (which is actually the stack trace at the point the original exception was raised). Let’s see all this in IEx:

 iex>​ c(​"​​exception.ex"​)
 [Boom]
 iex>​ Boom.start 1
 About to divide by zero
 %ArithmeticError{}
 Uh-oh! Arithmetic error
 DONE!
 **​ (RuntimeError) too late, we're doomed
  exception.ex:26: Boom.raise_error/1
  exception.ex:5: Boom.start/1
 
 iex>​ Boom.start 2
 About to call a function that doesn't exist
 no function match or runtime error
 DONE!
 :ok
 
 iex>​ Boom.start 3
 About to try creating a directory with no permission
 Disaster! %File.Error{action: "make directory", path: "/not_allowed",
  reason: :eacces}
 DONE!
 :ok