Elixir has three error mechanisms: errors, throws and exits. Let us explore each of them.
If we use the first function approach, then we need to use case for pattern matching the error and take action according to that. In the second case, we use the try rescue approach for error prone code and handle errors accordingly. It is entirely upto you.
Those situations are quite uncommon in practice except when interfacing with libraries that do not provide a proper API. For example, let’s imagine the Enum module did not provide any API for finding a value and that we needed to find the first multiple of 13 in a list of numbers:
Error
Errors (or exceptions) are used when exceptional things happen in the code. A sample error can be retrieved by trying to add a number into an string:IO.puts(1 + "Hello")When running above program, it produces following error:
** (ArithmeticError) bad argument in arithmetic expression :erlang.+(1, "Hello")This was a sample inbuilt error.
Raising errors
We can raise errors using the raise functions. Let us see an example:#Runtime Error with just a message raise "oops" # ** (RuntimeError) oopsOther errors can be raised with raise/2 passing the error name and a list of keyword arguments
#Other error type with a message raise ArgumentError, message: "invalid argument foo"You can also define your own errors and raise those. For example:
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
Rescuing Errors
We don't want our programs to abruptly quit but rather handle these errors gracefully. For this we use error handling. We rescue errors using the try/rescue construct. Let us have a look at an example:err = try do raise "oops" rescue e in RuntimeError -> e end IO.puts(err.message)When running above program, it produces following result:
oopsWhat we have done here is handle errors in the rescue statement using pattern matching. If we dont have any use of the error, and just want to use it for identification purposes, we can also use the form:
err = try do 1 + "Hello" rescue RuntimeError -> "You've got a runtime error!" ArithmeticError -> "You've got a Argument error!" end IO.puts(err)When running above program, it produces following result:
You've got a Argument error!NOTE: Most functions in the elixir standard library are implemented twice, once returning tuples and the other time raising errors. For example, the File.read and File.read! functions. The first one returned a tuple if the file was read successfully and if an error was encountered, this tuple was used to give the reason for the error. The second one raised an error if an error was encountered.
If we use the first function approach, then we need to use case for pattern matching the error and take action according to that. In the second case, we use the try rescue approach for error prone code and handle errors accordingly. It is entirely upto you.
Throws
In Elixir, a value can be thrown and later be caught. throw and catch are reserved for situations where it is not possible to retrieve a value unless by using throw and catch.Those situations are quite uncommon in practice except when interfacing with libraries that do not provide a proper API. For example, let’s imagine the Enum module did not provide any API for finding a value and that we needed to find the first multiple of 13 in a list of numbers:
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)When running above program, it produces following result:
Got 26
Exit
When a process dies of “natural causes” (e.g., unhandled exceptions), it sends an exit signal. A process can also die by explicitly sending an exit signal. For example:spawn_link fn -> exit(1) endIn the example above, the linked process died by sending an exit signal with value of 1. Note that exit can also be “caught” using try/catch. For example:
val = try do exit "I am exiting" catch :exit, _ -> "not really" end IO.puts(val)When running above program, it produces following result:
not really
After
Sometimes it’s necessary to ensure that a resource is cleaned up after some action that could potentially raise an error. The try/after construct allows you to do that. For example, we can open a file and use an after clause to close it–even if something goes wrong.{:ok, file} = File.open "sample", [:utf8, :write] try do IO.write file, "olá" raise "oops, something went wrong" after File.close(file) endWhen we run this program, it'll give us an error. But the after statement will ensure that the file descriptor is closed upon any such event.
No comments:
Post a Comment