Skip to main content

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.

See also