You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
You can also define your own errors by creating a module and using the `defexception` construct inside it; this way, you'll create an error with the same name as the module it's defined in. The most common case is to define a custom exception with a message field:
33
+
You can also define your own errors by creating a module and using the `defexception` construct inside it. This way, you'll create an error with the same name as the module it's defined in. The most common case is to define a custom exception with a message field:
34
34
35
35
```elixir
36
36
iex>defmoduleMyErrordo
@@ -53,9 +53,9 @@ iex> try do
53
53
%RuntimeError{message:"oops"}
54
54
```
55
55
56
-
The example above rescues the runtime error and returns the error itself which is then printed in the `iex` session.
56
+
The example above rescues the runtime error and returns the exception itself, which is then printed in the `iex` session.
57
57
58
-
If you don't have any use for the error, you don't have to provide it:
58
+
If you don't have any use for the exception, you don't have to pass a variable to `rescue`:
59
59
60
60
```elixir
61
61
iex>trydo
@@ -66,7 +66,7 @@ iex> try do
66
66
"Error!"
67
67
```
68
68
69
-
In practice, however, Elixir developers rarely use the `try/rescue` construct. For example, many languages would force you to rescue an error when a file cannot be opened successfully. Elixir instead provides a `File.read/1` function which returns a tuple containing information about whether the file was opened successfully:
69
+
In practice, Elixir developers rarely use the `try/rescue` construct. For example, many languages would force you to rescue an error when a file cannot be opened successfully. Elixir instead provides a `File.read/1` function which returns a tuple containing information about whether the file was opened successfully:
70
70
71
71
```elixir
72
72
iex>File.read("hello")
@@ -77,7 +77,7 @@ iex> File.read("hello")
77
77
{:ok, "world"}
78
78
```
79
79
80
-
There is no `try/rescue` here. In case you want to handle multiple outcomes of opening a file, you can use pattern matching within the `case` construct:
80
+
There is no `try/rescue` here. In case you want to handle multiple outcomes of opening a file, you can use pattern matching using the `case` construct:
81
81
82
82
```elixir
83
83
iex>caseFile.read("hello") do
@@ -86,8 +86,6 @@ iex> case File.read("hello") do
86
86
...>end
87
87
```
88
88
89
-
At the end of the day, it's up to your application to decide if an error while opening a file is exceptional or not. That's why Elixir doesn't impose exceptions on `File.read/1` and many other functions. Instead, it leaves it up to the developer to choose the best way to proceed.
90
-
91
89
For the cases where you do expect a file to exist (and the lack of that file is truly an *error*) you may use `File.read!/1`:
92
90
93
91
```elixir
@@ -96,9 +94,27 @@ iex> File.read!("unknown")
96
94
(elixir) lib/file.ex:272: File.read!/1
97
95
```
98
96
99
-
Many functions in the standard library follow the pattern of having a counterpart that raises an exception instead of returning tuples to match against. The convention is to create a function (`foo`) which returns `{:ok, result}` or `{:error, reason}` tuples and another function (`foo!`, same name but with a trailing `!`) that takes the same arguments as `foo` but which raises an exception if there's an error. `foo!` should return the result (not wrapped in a tuple) if everything goes fine. The [`File` module](https://hexdocs.pm/elixir/File.html) is a good example of this convention.
97
+
At the end of the day, it's up to your application to decide if an error while opening a file is exceptional or not. That's why Elixir doesn't impose exceptions on `File.read/1` and many other functions. Instead, it leaves it up to the developer to choose the best way to proceed.
98
+
99
+
Many functions in the standard library follow the pattern of having a counterpart that raises an exception instead of returning tuples to match against. The convention is to create a function (`foo`) which returns `{:ok, result}` or `{:error, reason}` tuples and another function (`foo!`, same name but with a trailing `!`) that takes the same arguments as `foo` but which raises an exception if there's an error. `foo!` should return the result (not wrapped in a tuple) if everything goes fine.
100
+
101
+
### Reraise
100
102
101
-
In Elixir, we avoid using `try/rescue` because **we don't use errors for control flow**. We take errors literally: they are reserved for unexpected and/or exceptional situations. In case you actually need flow control constructs, *throws* should be used. That's what we are going to see next.
103
+
While we generally avoid using `try/rescue` in Elixir, one situation where we may want to use such constracuts is for observability/monitoring. Imagine you want to log that something went wrong, you could do:
104
+
105
+
```elixir
106
+
trydo
107
+
... some code ...
108
+
rescue
109
+
e ->
110
+
Logger.error(Exception.format(:error, e, __STACKTRACE__))
111
+
reraise e, __STACKTRACE__
112
+
end
113
+
```
114
+
115
+
In the example above, we rescued the exception, logged it, and then re-raised it. We use the `__STACKTRACE__` construct both when formatting the exception and when re-raising. This ensures we reraise the exception as is, without changing value or its origin.
116
+
117
+
Generally speaking, we take errors in Elixir literally: they are reserved for unexpected and/or exceptional situations, never for controlling the flow of our code. In case you actually need flow control constructs, *throws* should be used. That's what we are going to see next.
102
118
103
119
## Throws
104
120
@@ -214,7 +230,7 @@ Exceptions in the `else` block are not caught. If no pattern inside the `else` b
214
230
215
231
## Variables scope
216
232
217
-
It is important to bear in mind that variables defined inside `try/catch/rescue/after` blocks do not leak to the outer context. This is because the `try` block may fail and as such the variables may never be bound in the first place. In other words, this code is invalid:
233
+
Similar to `case`, `cond`, `if` and other constructs in Elixir, variables defined inside `try/catch/rescue/after` blocks do not leak to the outer context. In other words, this code is invalid:
Instead, you can store the value of the `try` expression:
246
+
Instead, you should return the value of the `try` expression:
231
247
232
248
```elixir
233
249
iex> what_happened =
@@ -241,4 +257,17 @@ iex> what_happened
241
257
:rescued
242
258
```
243
259
244
-
This finishes our introduction to `try`, `catch`, and `rescue`. You will find they are used less frequently in Elixir than in other languages, although they may be handy in some situations where a library or some particular code is not playing "by the rules".
260
+
Furthermore, variables defined in the do-block of `try` are not available inside `rescue/after/else` either. This is because the `try` block may fail at any moment and therefore the variables may have never been bound in the first place. So this also isn't valid:
0 commit comments