A common pattern in this case is to have a get/x and a get!/x function (where x is the arity).
The unbanged version does return a tuple which first element is either :ok or :error while the second element is the result of the computation or the reason of failure (atom or string) depending on the first tuple.
The banged version on the other hand does either return the result of the computation or raises an exeption.
You can observe this pattern very often in the elixir standard library very often, an example of this is Map.fetch/2 and Map.fetch!/2.
The banged version is very often a simple wrapper around the unbanged one as in the following example:
@spec foo(any) :: {:ok, Foo.t} | {:error, String.t}
def foo(bar), do: bar |> do_the_magic
@spec foo!(any) :: Foo.t | no_return
def foo!(bar) do
case foo(bar) do
{:ok, result} -> result
{:error, _} -> raise FooError
end
end
In the context of well designed supervision trees you probably do not even need to catch something raised, as well as you probably do not need to handle {:error, _} cases. Just try to match-assign the {:ok, _} and let the process die when it failed.
In the case you really have to handle the errors because of reasons that matter, do not raise, but use signaling tuples as in the unbanged functions, thrown axceptions do add some overhead and do add a significant runtime-cost.
Exceptions are to see exactly as this! An exception, a thing that might happen, but you do not consider it under normal circumstances. Use them only if you really want to fail, do not try to recover from them!






















