Error Handling
Pattern Matching
case File.read("example.txt") do
{:ok, content} ->
IO.puts("File content: #{content}")
{:error, reason} ->
IO.puts("Error reading file: #{reason}")
end
with
with is handy for match result of a serial of operations:
with {:ok, file} <- File.open("example.txt"),
content = IO.read(file),
:ok <- File.close(file),
{:ok, data} <- Jason.decode(content) do
data
else
{:error, reason} ->
IO.puts("Error: #{reason}")
_ ->
IO.puts("Unknown error occurred")
end
with allows you to focus on the successful path of the computation. It is called "Railway-oriented programming".
This is a common approach in functional programming languages and others where exceptions aren't the primary error handling mechanism.
In haskell there is do and >>=.
In rust to work with Result<T, E> and Option<T> there is ? operator.
In JavaScript, it's Promise chaining using .then().
try/rescue
For catching exceptions(not common in idiomatic Elixir):
try do
String.to_integer("not_a_number")
rescue
ArgumentError -> "Invalid argument"
e in RuntimeError -> "Runtime error: #{e.message}"
_ -> "Unknown error"
after
# like finally in other languages
IO.puts("Operation attempted")
end
Handling process termination
# trap to prevent the parent process from exiting
Process.flag(:trap_exit, true)
spawn_link(fn ->
exit("I am exiting")
end)
Task
task = Task.async(fn ->
File.read!("missing_file.txt")
end)
case Task.yield(task, 5000) || Task.shutdown(task) do
{:ok, result} ->
result
nil ->
"Timeout"
{:exit, reason} ->
IO.puts inspect(reason)
end