W0052 - Avoid catch
Warning
-module(main).
-export([catcher/2]).
catcher(X,Y) ->
case catch X/Y of
%% ^^^^^ warning: Avoid `catch`.
{'EXIT', {badarith,_}} -> "uh oh";
N -> N
end.
Explanation
try ... catch ... end
in Erlang has been available for a very long time, and
the old, simplistic catch Expr
will be dropped from the language. In most
places where it is used, it carries with it some unwanted (and often unknown)
corner case behaviour, such as discarding the original error location,
conflating errors, exits, and throws, and mixing up thrown values with normally
returned values in a way that makes it very hard to see what the author
intended, and whether the implementation really does what they hoped.
Maintainance of such code is very hard.
Starting with Erlang OTP 28, it will be possible to enable warnings for use of
old-style catch
expressions via the warn_deprecated_catch
flag. The
old-style catch will be deprecated and removed in a future version of OTP (most
likely OTP 29 or OTP 30). Therefore, it is recommended to avoid switching to
try ... catch ... end
.
Examples
Let's consider the following example:
case catch api() of
{'EXIT', Reason} ->
caught_it;
_ ->
other
end.
First notice that this always returns other
if api()
calls throw(...)
because foo = (catch throw(foo))
. We only get something interesting if api()
calls exit(...)
. So, if your goal was to do something special when there is an
exception, case catch
is definitely not what you want.
If you only want to catch exits, it might work, unless the API legitimately
returns a tuple with first element being 'EXIT'
. So, this pattern is brittle
and does not generalize. Here is a bette way:
try api() of
_ ->
other
catch
_:_ ->
caught_it
end.
It is only one line longer than case catch
, does not miss throw()
s, has less
runtime overhead, has 15 fewer characters, and never trips on special return
values of functions.
One case where using try/catch
is more cumbersome is if you want to treat
exceptions and unexpected return values the same. In that case you may want to
do:
case catch api() of
expected -> continue_processing();
Other -> handle_unexpected(Other)
end.
However, if api()
calls throw(expected)
it gets treated the same as if it
returns expected. This makes the intended interface of api()
unclear. So, it
is still recommended to use try/catch
for this case:
try api() of
expected -> continue_processing();
Other -> handle_unexpected(Other)
catch
Type:Reason -> handle_unexpected(Type, Reason)
end.
It is more verbose but makes the interface and control flow clear.