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 use the `defexception/1` macro inside it. The most common case is to define an exception with a message field:
37
+
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:
38
38
39
39
```iex
40
40
iex> defmodule MyError do
41
-
iex> defexception message: "default message"
41
+
iex> defexception message: "default message"
42
42
iex> end
43
43
iex> raise MyError
44
44
** (MyError) default message
45
45
iex> raise MyError, message: "custom message"
46
46
** (MyError) custom message
47
47
```
48
48
49
-
Exceptions can be rescued by using the `try/rescue` construct:
49
+
Errors can be **rescued** using the `try/rescue` construct:
50
50
51
51
```iex
52
52
iex> try do
@@ -57,7 +57,7 @@ iex> try do
57
57
%RuntimeError{message: "oops"}
58
58
```
59
59
60
-
The example above rescues the runtime error and returns the error itself which is then printed in the `iex` session. In practiceElixir developers rarely use the `try/rescue` construct though. For example, many languages would force you to rescue an error when a file cannot open successfully. Elixir instead provides a `File.read/1` function which returns a tuple containing information if the file was opened with success or not:
60
+
The example above rescues the runtime error and returns the error itself which is then printed in the `iex` session. 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 informations about whether the file was successfully opened:
There is no `try/rescue` here. In case you want to handle multiple outcomes of opening a file, you can simply use pattern matching with `case`:
71
+
There is no `try/rescue` here. In case you want to handle multiple outcomes of opening a file, you can simply use pattern matching with the `case` construct:
At the end of the day, it is 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 we leave it up to the developer to choose the best way to proceed.
80
+
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.
81
81
82
-
For the cases where you do expect a file to exist (and the lack of a file is truly an error) you can simply use `File.read!/1`:
82
+
For the cases where you do expect a file to exist (and the lack of that file is truly an *error*) you can simply use `File.read!/1`:
83
83
84
84
```iex
85
85
iex> File.read! "unknown"
86
86
** (File.Error) could not read file unknown: no such file or directory
87
87
(elixir) lib/file.ex:305: File.read!/1
88
88
```
89
89
90
-
In other words, we avoid using `try/rescue` because **we don't use errors for control flow**. In Elixir, we take errors literally: they are reserved to unexpected and/or exceptional situations. In case you actually need flow control constructs, throws must be used. That's what we are going to see next.
90
+
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](/docs/stable/elixir/File.html) is a good example of this convention.
91
+
92
+
In Elixir, we avoid using `try/rescue` because **we don't use errors for control flow**. We take errors literally: they are reserved to 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.
91
93
92
94
## 19.2 Throws
93
95
94
-
In Elixir, one can throw a value to be caught later. `throw` and `catch` are reserved for situations where it is not possible to retrieve a value unless by using `throw` and `catch`.
96
+
In Elixir, a value can be thrown and later be caught. `throw` and `catch` are reserved for situations where it is not possible to retrieve a value unless by using `throw` and `catch`.
95
97
96
-
Those situations are quite uncommon in practice unless when interfacing with a library that does not provide the proper APIs. For example, let's imagine the `Enum` module did not provide any API for finding a value and we need to find the first number that is a multiple of 13:
98
+
Those situations are quite uncommon in practice except when interfacing with libraries that does not provide a proper API. For example, let's imagine the `Enum` module did not provide any API for finding a value and that we needed to find the first multiple of 13 in a list of numbers:
97
99
98
100
```iex
99
101
iex> try do
@@ -107,7 +109,7 @@ iex> try do
107
109
"Got -39"
108
110
```
109
111
110
-
However, in practice one can simply use `Enum.find/2`:
112
+
Since `Enum`*does* provide a proper API, in practice `Enum.find/2` is the way to go:
Every Elixir code runs inside processes that communicates with each other. When a process dies, it sends an `exit` signal. A process can also die by explicitly sending an exit signal:
121
+
All Elixir code runs inside processes that communicate with each other. When a process dies of "natural causes" (e.g., unhandled exceptions), it sends an `exit` signal. A process can also die by explicitly sending an exit signal:
120
122
121
123
```iex
122
124
iex> spawn_link fn -> exit(1) end
@@ -139,13 +141,13 @@ iex> try do
139
141
140
142
Using `try/catch` is already uncommon and using it to catch exits is even more rare.
141
143
142
-
`exit` signals are an important part of the fault tolerant system provided by the Erlang VM. Processes usually run under supervision trees which are themselves processes that just wait for `exit` signals of the supervised processes. Once an exit signal is received, the supervision strategy kicks in and the supervised process is restarted.
144
+
`exit` signals are an important part of the fault tolerant system provided by the Erlang VM. Processes usually run under supervision trees which are themselves processes that just wait for `exit` signals from the supervised processes. Once an exit signal is received, the supervision strategy kicks in and the supervised process is restarted.
143
145
144
-
It is exactly this supervision system that makes constructs like `try/catch` and `try/rescue` so uncommon in Elixir. Instead of rescuing a certain error, we'd rather "fail fast" since the supervision tree will guarantee our application will go back to a known initial state after the error.
146
+
It is exactly this supervision system that makes constructs like `try/catch` and `try/rescue` so uncommon in Elixir. Instead of rescuing an error, we'd rather "fail fast" since the supervision tree will guarantee our application will go back to a known initial state after the error.
145
147
146
148
## 19.4 After
147
149
148
-
Sometimes it is necessary to use `try/after` to guarantee a resource is cleaned up after some particular action. For example, we can open a file and guarantee it is closed with `try/after` block:
150
+
Sometimes it's necessary to ensure that a resource is cleaned up after some action that could potentially raise an error. The `try/after` construct allows you to do that. For example, we can open a file and guarantee it will be closed (even if something goes wrong) with a`try/after` block:
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".
178
-
179
-
It is time to talk about some Elixir constructs like comprehensions and sigils.
0 commit comments