Whats the difference between the two? They seem to do identical things but one is doced as ‘only use very rarely’
I got a little confused by those docs too, and made a mental note to go back and see if I could figure out the difference.
But about the “only use very rarely”, I feel confident shedding some light on that.
Maybe someone will come along and correct me, but I believe that the Elixir ecosystem is fairly opinionated (in a good way) about not catching exceptions/errors. 95% of the time we should just ignore them and let them crash the app. Then, we look at the crashes, which should be only due to programming errors, and fix our bugs.
Said differently, The exceptions are generally things outside of our control that we can’t really program for. And: this is how we should design our own code: to use {:ok, :error}
for business logic errors, and reaching for raising exceptions for unsalvageable inconsistencies.
These are not crash errors. Writing a compiler. I want to bail out deep down in the parser if I get , say, an unknown variable name, a misused keyword etc.
My rule of thumb is that try/rescue
is for cases when I do in fact want to rescue just exceptions and have a fairly good idea about what those exceptions might be. try/catch
in my experience is for when I want to catch EVERYTHING for logging/telemetry purposes. For example, libraries often do this to emit a telemetry event. Here’s Bandit example. In these cases you usually wrap someone else’s code.
You’re describing business logic errors based on user input. These are completely expected and not “exceptional” in the elixir world.
I suggest learning about with
and other patterns for handling error returns.
NB: whether or not to bail out deep down in the parser is a design decision. Many compiler writers try to continue parsing, reporting as many errors as they can find, instead of stopping at the first one detected. Apologies if you already knew about this.
try/rescue and try/throw are not comparable.
I guess you rather want to know the difference between try/rescue and try/catch.
rescue
will only rescue errors, that is Elixir exception and Erlang errors. catch
is even more rarely used. It will catch errors too but can also catch what is thrown with throw
and calls to exit/1
. I think is explained well enough in the docs though. What part is unclear to you?
rescue
is a shorthand, mostly. catch
can do its job, but there’s more boilerplate and some of the shapes aren’t as nice:
try do
1 + :foo
rescue
e ->
IO.puts "OH NO #{inspect(e)}"
end
(prints OH NO %ArithmeticError{message: "bad argument in arithmetic expression"}
)
versus
try do
1 + :foo
catch
:error, e ->
IO.puts "OH NO #{inspect(e)}"
end
which prints OH NO :badarith
Using catch
in this way also means that Erlang errors are not transformed into Elixir exceptions - note the different inspect
output for the two cases.
I would treat try
/catch
as a control flow (goto
-like,) while try
/rescue
as handling an exceptional situation.
You‘d use throw
(+catch) there. It‘s the usecase it exists for in elixir. Though it‘s used sparingly and if you can replace things with normal control flow/returning that‘s usually preferred.
This is mostly considered to be a separate tool to error handling / rescue
.
I rarely use throw/catch. In some rare cases, like certain kinds of parsing with deep stack traces, I will use throw and catch. It’s not an exception exactly because I expect some input will be invalid. But throw/catch in this case avoids having to thread ok/error tuples through a bunch of nested functions.
different types of errors require different recovery mechanisms. Some need to bail out up to , say, next statement, some to next token of some defined recovery set, some to next function …
i am designing this strategy at the moment and am trying to understand the tools at my disposal
The documentation is really not very good.
Syntax
- what is the syntax for ‘catch’, it is not explained anywhere. Docs have
catch
x -> "Got #{x}"
and (you show)
catch
:error, e ->
IO.puts "OH NO #{inspect(e)}"
Note that there is one thing after catch in the first one 2 in the second, what does that mean?
-
what does that ‘->’ mean, it looks like a match, is it?
-
can I have multiple catches to catch different things (like in other languages), if not how do I say ’ i want to catch x, but not y’
-
whats the syntax of rescue, is it different from catch
-
what does this mean
rescue
e in RuntimeError -> e
it suggests that RuntimeError is a collection of some kind. The docs for RuntimeError suggest its a single thing
- I can have catch without try! (hidden away at the end under the description of Exit), how about rescue, how about after
Dynamics
- does a raise propagate to a caller? All the examples just show one code block. Does it flow up the stack like exceptions in , say, c++? Or is it just propagated to the immediate caller.
- how about throw, several times I have seen people say ‘its like a fancy goto’. This strongly suggests that it only works inside one function.
- throw/1 docs says “A non-local return from a function”. Which makes it sound like its just a quick out for a function, and does not flow up the stack.
- what happens if nobody catches a throw
- what happens if I have a try/catch and somebody does a raise?
- what happens if I have a try / rescue and somebody does a throw?
- what happens if nobody rescues a raise
Actually I found the answers to a lot of these here Elixir - try/catch vs try/rescue? - Stack Overflow
This reveals all sorts of stuff that not in the elixir docs anywhere
Nobody has said what the difference between these two are. I don’t mean what are their use cases but, functionally what do they do different that leads to them having different use cases. There must be a difference
Functionally there is no difference between throw and raise: Errors and Error Handling — Erlang System Documentation v28.0.2
The difference is in the values being bubbled up: errors (in elixir usually exception structs), exits and thrown data. The different syntax options for elixirs try
are due to matching on those various things.
The reason why you usually don‘t see throw being used across function boundries is because it‘s considered actual bad practice to leak throws. You want to keep the throw and related catch as close to each other as possible. Otherwise you risk your code paths from getting difficult to maintain and debug. You‘re explicitly opting out of explicit control flow, so you want to at least make the implicit flow easy to follow.
Also if you feel like the elixir docs can be improved please send a PR. It‘s always easier to see the flaws for someone not knowing the destribed already.
I know you found some answers but I’ll try to reply to all your questions anyway
Note that there is one thing after catch in the first one 2 in the second, what does that mean?
By default, you can catch
throws:
x -> "got #{x}"
is equivalent to :throw, x -> "got #{x}"
.
If you want to catch errors or exit you must specify :error
or :exit
. You can also catch any of them:
_, x -> ...
or_some_var, x -> ...
to ignore the typesome_var, x -> ...
to assign a variable.
what does that ‘->’ mean, it looks like a match, is it?
Yes, as in a case
or receive
, the left part is a match and the right part of the arrow is what to execute on match.
can I have multiple catches to catch different things (like in other languages), if not how do I say ’ i want to catch x, but not y’
Yes. Just like in a case
, the first matching branch will be selected.
whats the syntax of rescue, is it different from catch
Matching with rescue
is limited. You can only match anything e -> ...
, a specific exception type RuntimeError -> ...
or a specific runtime error with assigning the error to a variable e in RuntimeError -> ...
.
what does this mean
it suggests that RuntimeError is a collection of some kind. The docs for RuntimeError suggest its a single thing
Here, in
in e in RuntimeError
just means that you match+assign the e
variable if it is that kind of error. I believe the in
operator was chosen to avoid adding a new operator or syntax element. There is no concept of collection involved.
does a raise propagate to a caller? All the examples just show one code block. Does it flow up the stack like exceptions in , say, c++? Or is it just propagated to the immediate caller.
Yes, exceptions propagate to the upmost caller, in the current process. If there is no catch/rescue in the stack, the current process exits. It really is like in most programming languages. You can have several layers of try/catch/rescue in the stack, and throws will bubble up to the closest. But we generally do not do that, as it’s hard to debug and as said, exceptions should be exceptional.
how about throw, several times I have seen people say ‘its like a fancy goto’. This strongly suggests that it only works inside one function.
Throw is often used to go up in the stack quickly. For instance as discussed before when implementing parsers, or when finding values in deeply nested data structures. To avoid writing complicated code to return ok/error tuples all over the place, you can just throw. This is best used in private functions where the catch is made in the same module. It’s bad practice to let throws bubble up into code consuming the module.
throw/1 docs says “A non-local return from a function”. Which makes it sound like its just a quick out for a function, and does not flow up the stack.
I’m not sure what “local return” means actually. But no, it’s the opposite, with throw you can “return” a value way up in the stack (given you catch it), instead of returning to the direct caller of the throwing function.
what happens if nobody catches a throw
The process exits.
what happens if I have a try/catch and somebody does a raise
If the catch is catching errors :error, foo -> ...
then it will be caught, otherwise it will bubble up.
what happens if I have a try / rescue and somebody does a throw?
It will not be caught.
what happens if nobody rescues a raise
The process exits.
Semantically:
-
A
throw
is meant to be caught by you (typically within the same module that throws), can be any term -
An
error
is when something goes wrong (typically not rescued), it is an exception in Elixir -
An
exit
is when a process is crashing (typically not caught), can be any term
throw
s are handled by catch
s, errors are handled by rescue
s. If I had a magic wand, exit
would not be part of the language, especially because it is often mixed with the separate exit signal, but it exists in Erlang, so we have to support it, hence the catch kind, reason ->
notation.
PRs to improve the docs are always welcome.
@josevalim Thanks for chipping in
Previous answer from @luf said
Here,
in
ine in RuntimeError
just means that you match+assign thee
variable if it is that kind of error. I believe thein
operator was chosen to avoid adding a new operator or syntax element. There is no concept of collection involved.
Ie its an overload of ‘in’ with a completely different meaning. What exactly is that meaning. What are its syntax rules?
Given that all ‘thrown’ things can be caught by ‘catch’ what the functional difference , I mean what happens differently under the hood that makes ‘raise’ a normal use thing, ‘throw’ a rarely used thing and ‘exit’ a thing that the language inventor wishes wasnt even there.
This is oversimplified, but throw/catch comes from Erlang. The payload can be any Erlang term (usually a tuple) and it relies on historical implicit conventions of how to use it. Elixir uses raise/rescue to encode/decode an Exception struct as the payload as an explicit convention.