Skip to content

Commit b9e5d69

Browse files
authored
Merge pull request #16 from pedropark99/defer
Add a section to explain the keyword `defer`
2 parents 38b4008 + ccb4b9d commit b9e5d69

File tree

10 files changed

+283
-180
lines changed

10 files changed

+283
-180
lines changed

Chapters/01-memory.qmd

+1-1
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ pub fn main() !void {
639639
}
640640
```
641641

642-
Also, notice that in this example, we use the keyword `defer` to run a small
642+
Also, notice that in this example, we use the `defer` keyword (which I described at @sec-defer) to run a small
643643
piece of code at the end of the current scope, which is the expression `allocator.free(input)`.
644644
When you execute this expression, the allocator will free the memory that it allocated
645645
for the `input` object.

Chapters/01-zig-weird.qmd

+43-6
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,47 @@ lowercase letter, and it would work fine.
12651265

12661266

12671267

1268+
### The `defer` keyword {#sec-defer}
1269+
1270+
With the `defer` keyword you can execute expressions at the end of the current scope.
1271+
Take the `foo()` function below as an example. When we execute this function, the expression
1272+
that prints the message "Exiting function ..." get's executed only at
1273+
the end of the function scope.
1274+
1275+
```{zig}
1276+
#| auto_main: false
1277+
#| echo: true
1278+
#| results: "hide"
1279+
const std = @import("std");
1280+
const stdout = std.io.getStdOut().writer();
1281+
fn foo() !void {
1282+
defer std.debug.print(
1283+
"Exiting function ...\n", .{}
1284+
);
1285+
try stdout.print("Adding some numbers ...\n", .{});
1286+
const x = 2 + 2; _ = x;
1287+
try stdout.print("Multiplying ...\n", .{});
1288+
const y = 2 * 8; _ = y;
1289+
}
1290+
1291+
pub fn main() !void {
1292+
try foo();
1293+
}
1294+
```
1295+
1296+
```
1297+
Adding some numbers ...
1298+
Multiplying ...
1299+
Exiting function ...
1300+
```
1301+
1302+
It doesn't matter how the function exits (i.e. because
1303+
of an error, or, because of an return statement, or whatever),
1304+
just remember, this expression get's executed when the function exits.
1305+
1306+
1307+
1308+
12681309
### For loops
12691310

12701311
A loop allows you to execute the same lines of code multiple times,
@@ -1434,12 +1475,8 @@ This is just a naming convention that you will find across the entire Zig standa
14341475
So, in Zig, the `init()` method of a struct is normally the constructor method of the class represented by this struct.
14351476
While the `deinit()` method is the method used for destroying an existing instance of that struct.
14361477

1437-
Both the `init()` and `deinit()` methods are used extensively in Zig code, and you will see both of them at @sec-arena-allocator. In this section,
1438-
I present the `ArenaAllocator()`, which is a special type of allocator object that receives a second (child)
1439-
allocator object at instantiation. We use the `init()` method to create a new `ArenaAllocator()` object,
1440-
then, on the next line, we also used the `deinit()` method in conjunction with the `defer` keyword, to destroy this arena allocator object at the end
1441-
of the current scope.
1442-
1478+
The `init()` and `deinit()` methods are both used extensively in Zig code, and you will see both of
1479+
them being used when we talk about allocators at @sec-allocators.
14431480
But, as another example, let's build a simple `User` struct to represent an user of some sort of system.
14441481
If you look at the `User` struct below, you can see the `struct` keyword, and inside of a
14451482
pair of curly braces, we write the struct's body.

Chapters/09-error-handling.qmd

+39-5
Original file line numberDiff line numberDiff line change
@@ -474,20 +474,54 @@ fn create_user(db: Database, allocator: Allocator) !User {
474474
By using `errdefer` to destroy the `user` object that we have just created,
475475
we garantee that the memory allocated for this `user` object
476476
get's freed, before the execution of the program stops.
477-
478477
Because if the expression `try db.add(user)` returns an error value,
479478
the execution of our program stops, and we loose all references and control over the memory
480479
that we have allocated for the `user` object.
481480
As a result, if we do not free the memory associated with the `user` object before the program stops,
482481
we cannot free this memory anymore. We simply loose our chance to do the right thing.
483482
That is why `errdefer` is essential in this situation.
484483

485-
Having all this in mind, the `errdefer` keyword is different but also similar
486-
to the `defer` keyword. The only difference between the two is when the provided expression
487-
get's executed. The `defer` keyword always execute the provided expression at the end of the
488-
current scope, while `errdefer` executes the provided expression when an error occurs in the
484+
Just to make very clear the differences between `defer` (which I described at @sec-defer)
485+
and `errdefer`, it might be worth to discuss the subject a bit further.
486+
You might still have the question "why use `errdefer` if we can use `defer` instead?"
487+
in your mind.
488+
489+
Although being similar, the key difference between `errdefer` and `defer` keyword
490+
is when the provided expression get's executed.
491+
The `defer` keyword always execute the provided expression at the end of the
492+
current scope, no matter how your code exits this scope.
493+
In contrast, `errdefer` executes the provided expression only when an error occurs in the
489494
current scope.
490495

496+
This becomes important if a resource that you allocate in the
497+
current scope get's freed later in your code, in a different scope.
498+
The `create_user()` functions is an example of this. If you think
499+
closely about this function, you will notice that this function returns
500+
the `user` object as the result.
501+
502+
In other words, the allocated memory for the `user` object does not get
503+
freed inside the `create_user()`, if the function returns succesfully.
504+
So, if an error does not occur inside this function, the `user` object
505+
is returned from the function, and probably, the code that runs after
506+
this `create_user()` function will be responsible for freeying
507+
the memory of the `user` object.
508+
509+
But what if an error do occur inside the `create_user()`? What happens then?
510+
This would mean that the execution of your code would stop in this `create_user()`
511+
function, and, as a consequence, the code that runs after this `create_user()`
512+
function would simply not run, and, as a result, the memory of the `user` object
513+
would not be freed before your program stops.
514+
515+
This is the perfect scenario for `errdefer`. We use this keyword to garantee
516+
that our program will free the allocated memory for the `user` object,
517+
even if an error occurs inside the `create_user()` function.
518+
519+
If you allocate and free some memory for an object in the same scope, then,
520+
just use `defer` and be happy, `errdefer` have no use for you in such situation.
521+
But if you allocate some memory in a scope A, but you only free this memory
522+
later, in a scope B for example, then, `errdefer` becomes useful to avoid leaking memory
523+
in sketchy situations.
524+
491525

492526

493527
## Union type in Zig {#sec-unions}

_freeze/Chapters/01-memory/execute-results/html.json

+2-2
Large diffs are not rendered by default.

_freeze/Chapters/01-zig-weird/execute-results/html.json

+2-2
Large diffs are not rendered by default.

_freeze/Chapters/09-error-handling/execute-results/html.json

+5-3
Large diffs are not rendered by default.

docs/Chapters/01-memory.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ <h3 data-number="2.2.8" class="anchored" data-anchor-id="the-alloc-and-free-meth
586586
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a> std.debug.print(<span class="st">"{s}</span><span class="sc">\n</span><span class="st">"</span>, .<span class="op">{</span>input<span class="op">}</span>);</span>
587587
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
588588
</div>
589-
<p>Also, notice that in this example, we use the keyword <code>defer</code> to run a small piece of code at the end of the current scope, which is the expression <code>allocator.free(input)</code>. When you execute this expression, the allocator will free the memory that it allocated for the <code>input</code> object.</p>
589+
<p>Also, notice that in this example, we use the <code>defer</code> keyword (which I described at <a href="01-zig-weird.html#sec-defer" class="quarto-xref"><span>Section 1.9.3</span></a>) to run a small piece of code at the end of the current scope, which is the expression <code>allocator.free(input)</code>. When you execute this expression, the allocator will free the memory that it allocated for the <code>input</code> object.</p>
590590
<p>We have talked about this at <a href="#sec-heap" class="quarto-xref"><span>Section 2.1.5</span></a>. You <strong>should always</strong> explicitly free any memory that you allocate using an allocator! You do that by using the <code>free()</code> method of the same allocator object you used to allocate this memory. The <code>defer</code> keyword is used in this example only to help us execute this free operation at the end of the current scope.</p>
591591
</section>
592592
<section id="the-create-and-destroy-methods" class="level3" data-number="2.2.9">

0 commit comments

Comments
 (0)