I'm going to assume that you're already familiar with the concept of exceptions. Most modern languages have them, although they're used in some languages more than others.

Exceptions in Elixir should only be used for exceptional circumstances where something is unavailable that is expected to always be there and is essential for the normal functioning of the system. Exceptions are not a part of normal flow control in Elixir, like they are in some languages! (I'm looking at you, Java). Only use them in truly exceptional circumstances that aren't part of normal system operation.

Some functions in Elixir offer a version that returns error information and a version that throws an exception. By convention, a function with the "!" character at the end throws an exception. For example, File.open/1 does not throw an exception , but File.open!/1 does. Both do the same thing, but report errors differently.

Use the exception-throwing version when you are doing something that is expected to succeed under normal circumstances. Use the non-exception-throwing version when there's a possibility of failure under normal circumstances. In the latter case, you'll typically want to take a different action depending on whether the operation succeeds or fails.

For example, with the file opening functions, use the exception-throwing version of File.open!/1 when you are opening a file that should always be there, such as a system configuration file. If that file is missing, something has really gone wrong. Use the error code version of of File.open/1 when you are opening a file that might reasonably not be there, such as a file whose name was entered by a user. Handling errors when a non-essential data file isn't there is part of the normal flow of a system.

Raising and Handling Exceptions

Since exceptions represent truly exceptional circumstances in Elixir, they are almost never caught in your code, but are instead allowed to bubble up to a much higher level, where the Elixir robustness mechanisms can handle them. At this point I have no idea what those robustness mechanisms look like, but that's something that I will eventually learn. This is very different from other languages I'm used to, where exceptions are commonly caught and handled. For now, I just have to remember to let exceptions be free.

Raising an exception can be done using the raise keyword. You can pass a message, in which case a RuntimeError will be created containing the message. Otherwise, you can specify the particular type of exception followed by the exception properties.

For example:

iex> raise "This is an exception message"
** (RuntimeError) This is an exception message

iex> raise RuntimeError, message: "This is an exception message"
** (RuntimeError) This is an exception message

Although exceptions are not typically caught, exceptions can be caught using the try-rescue-after syntax, which directly corresponds to the try-catch-finally syntax in Javascript or C#. The try block contains the code that can throw an exception and the rescue block catches and handles exceptions. The rescue block does pattern matching and somewhat resembles the case expression in Elixir. The after block runs any code that absolutely must be run at the very end, whether an exception was thrown or not, and whether an exception was caught or not. This code usually cleans something up or releases some kind of resource.

Here's an example of how you might use try-rescue-after.

try do
	do_something()
rescue
	RuntimeError -> handle_error(error)
	[ArithmeticError, FunctionClauseError] -> handle_another_error(error)
	other_error -> handle_unknown_error(error)
after
	release_resources()
end

In the above example, we can catch a particular error type, multiple error types, and we can include a clause to handle other types of errors we hadn't anticipated. The after block will then call release_resources/0, no matter what happened previously.

There's also an else clause that can come after try and rescue. This else block is run whenever the try block runs without throwing any exceptions. The else block does pattern matching just like a case expression, with the pattern it is matching being the value of the try block.

try do
	{:ok, data}
rescue
	RuntimeError -> handle_error(error)
else
	{:ok, data} -> do_something(data)
	{:warning, data} -> do_something_else(data)
	_ -> fail()
end

In this example, the last expression in the try block will be forwarded to the else clause where it will be pattern matched.

I want to reemphasize that catching an exception like this is very uncommon in Elixir. The usual way is to just let the exception happen and fly away unhandled. The robustness mechanisms of Elixir, which I currently have only a very vague awareness of, will eventually catch that exception and do some magic things to restore normalcy. I'm curious to see what this process looks like, since I haven't encountered anything like it before.

There's also a concept of something being thrown with a throw keyword and caught a try-catch-after clause. This looks to me exactly like the usual exception handling, but the Elixir documentation indicates that it is to be used in passing back data from an uncommon situation (usually when working with a third-party library) where it is not possible to pass back the data in any other way. This is apparently quite rare and developers will only have to resort to using this mechanism when nothing else will work.

I honestly don't understand what the difference is between throw-try-catch and raise-try-rescue. Why are there two different versions of what looks like the same thing? There must be some difference in behavior between the two, but I have yet to see any explanation of it.

Here's an example of throw-try-catch.

try do
	throw {:ok, data}
catch
	{:ok, data} -> do_something()
	{:error, message} -> log_error(message)		

Everything running in Elixir is contained within an Elixir process. Well, they're actually processes in the Erlang VM, but I'll call them Elixir processes. Anyway, an Elixir process can die due to an unhandled exception (a common occurrence), but you can also purposely kill the current Elixir process by calling the exit/1 function, passing the reason for exiting.

iex> exit "Goodbye, cruel world!"
** (exit) "Goodbye, cruel world!"

That doesn't appear to kill IEx, but IEx probably is design to host multiple processes and handle exceptions without dying.

It's also possible to create your own type of exception using a module with the defexception. I suspect that I will rarely need to do this, so I'm not going to dig into it any further until it turns out that I really do need to do so.

From what I understand, everything I've covered in this post is uncommon, and that many developers will rarely use any of this. I'm glad that I'm at least aware of this functionality, but since everything I've read so far has strongly emphasized that it should almost never be used except in rare circumstances, I'm looking forward to learning about how Elixir typically deals and recovers from errors.

For now, I'm going to file this information away and assume that I'll know when the time comes to use it.