Skip to content

Commit 238e6c0

Browse files
committed
Minor rephrasing and refactorings to the try/catch chapter
Just rephrased some sentences and cleaned out here and there.
1 parent ddf579c commit 238e6c0

File tree

1 file changed

+22
-22
lines changed

1 file changed

+22
-22
lines changed

getting_started/19.markdown

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ Elixir has three error mechanisms: errors, throws and exits. In this chapter we
1212

1313
## 19.1 Errors
1414

15-
A sample error can be retrieved by trying to add a number into an atom:
15+
Errors (or *exceptions*) are used when exceptional things happen in the code. A sample error can be retrieved by trying to add a number into an atom:
1616

1717
```iex
1818
iex> :foo + 1
1919
** (ArithmeticError) bad argument in arithmetic expression
2020
:erlang.+(:foo, 1)
2121
```
2222

23-
A runtime error can be raised any time by using the `raise/1` macro:
23+
A runtime error can be raised any time by using `raise/1`:
2424

2525
```iex
2626
iex> raise "oops"
@@ -34,19 +34,19 @@ iex> raise ArgumentError, message: "invalid argument foo"
3434
** (ArgumentError) invalid argument foo
3535
```
3636

37-
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:
3838

3939
```iex
4040
iex> defmodule MyError do
41-
iex> defexception message: "default message"
41+
iex> defexception message: "default message"
4242
iex> end
4343
iex> raise MyError
4444
** (MyError) default message
4545
iex> raise MyError, message: "custom message"
4646
** (MyError) custom message
4747
```
4848

49-
Exceptions can be rescued by using the `try/rescue` construct:
49+
Errors can be **rescued** using the `try/rescue` construct:
5050

5151
```iex
5252
iex> try do
@@ -57,7 +57,7 @@ iex> try do
5757
%RuntimeError{message: "oops"}
5858
```
5959

60-
The example above rescues the runtime error and returns the error itself which is then printed in the `iex` session. In practice Elixir 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:
6161

6262
```iex
6363
iex> File.read "hello"
@@ -66,34 +66,36 @@ iex> File.write "hello", "world"
6666
:ok
6767
iex> File.read "hello"
6868
{:ok, "world"}
69-
````
69+
```
7070

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 `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:
7272

7373
```iex
7474
iex> case File.read "hello" do
75-
...> {:ok, body} -> IO.puts "got ok"
76-
...> {:error, body} -> IO.puts "got error"
75+
...> {:ok, body} -> IO.puts "Success: #{body}"
76+
...> {:error, reason} -> IO.puts "Error: #{reason}"
7777
...> end
7878
```
7979

80-
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.
8181

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`:
8383

8484
```iex
8585
iex> File.read! "unknown"
8686
** (File.Error) could not read file unknown: no such file or directory
8787
(elixir) lib/file.ex:305: File.read!/1
8888
```
8989

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.
9193

9294
## 19.2 Throws
9395

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`.
9597

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:
9799

98100
```iex
99101
iex> try do
@@ -107,7 +109,7 @@ iex> try do
107109
"Got -39"
108110
```
109111

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:
111113

112114
```iex
113115
iex> Enum.find -50..50, &(rem(&1, 13) == 0)
@@ -116,7 +118,7 @@ iex> Enum.find -50..50, &(rem(&1, 13) == 0)
116118

117119
## 19.3 Exits
118120

119-
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:
120122

121123
```iex
122124
iex> spawn_link fn -> exit(1) end
@@ -139,13 +141,13 @@ iex> try do
139141

140142
Using `try/catch` is already uncommon and using it to catch exits is even more rare.
141143

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.
143145

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.
145147

146148
## 19.4 After
147149

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:
149151

150152
```iex
151153
iex> {:ok, file} = File.open "sample", [:utf8, :write]
@@ -175,5 +177,3 @@ iex> from_after
175177
```
176178

177179
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

Comments
 (0)