Skip to content

Commit

Permalink
Merge pull request #16 from pedropark99/defer
Browse files Browse the repository at this point in the history
Add a section to explain the keyword `defer`
  • Loading branch information
pedropark99 authored Jul 30, 2024
2 parents 38b4008 + ccb4b9d commit b9e5d69
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 180 deletions.
2 changes: 1 addition & 1 deletion Chapters/01-memory.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ pub fn main() !void {
}
```

Also, notice that in this example, we use the keyword `defer` to run a small
Also, notice that in this example, we use the `defer` keyword (which I described at @sec-defer) to run a small
piece of code at the end of the current scope, which is the expression `allocator.free(input)`.
When you execute this expression, the allocator will free the memory that it allocated
for the `input` object.
Expand Down
49 changes: 43 additions & 6 deletions Chapters/01-zig-weird.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,47 @@ lowercase letter, and it would work fine.



### The `defer` keyword {#sec-defer}

With the `defer` keyword you can execute expressions at the end of the current scope.
Take the `foo()` function below as an example. When we execute this function, the expression
that prints the message "Exiting function ..." get's executed only at
the end of the function scope.

```{zig}
#| auto_main: false
#| echo: true
#| results: "hide"
const std = @import("std");
const stdout = std.io.getStdOut().writer();
fn foo() !void {
defer std.debug.print(
"Exiting function ...\n", .{}
);
try stdout.print("Adding some numbers ...\n", .{});
const x = 2 + 2; _ = x;
try stdout.print("Multiplying ...\n", .{});
const y = 2 * 8; _ = y;
}
pub fn main() !void {
try foo();
}
```

```
Adding some numbers ...
Multiplying ...
Exiting function ...
```

It doesn't matter how the function exits (i.e. because
of an error, or, because of an return statement, or whatever),
just remember, this expression get's executed when the function exits.




### For loops

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

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,
I present the `ArenaAllocator()`, which is a special type of allocator object that receives a second (child)
allocator object at instantiation. We use the `init()` method to create a new `ArenaAllocator()` object,
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
of the current scope.

The `init()` and `deinit()` methods are both used extensively in Zig code, and you will see both of
them being used when we talk about allocators at @sec-allocators.
But, as another example, let's build a simple `User` struct to represent an user of some sort of system.
If you look at the `User` struct below, you can see the `struct` keyword, and inside of a
pair of curly braces, we write the struct's body.
Expand Down
44 changes: 39 additions & 5 deletions Chapters/09-error-handling.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -474,20 +474,54 @@ fn create_user(db: Database, allocator: Allocator) !User {
By using `errdefer` to destroy the `user` object that we have just created,
we garantee that the memory allocated for this `user` object
get's freed, before the execution of the program stops.

Because if the expression `try db.add(user)` returns an error value,
the execution of our program stops, and we loose all references and control over the memory
that we have allocated for the `user` object.
As a result, if we do not free the memory associated with the `user` object before the program stops,
we cannot free this memory anymore. We simply loose our chance to do the right thing.
That is why `errdefer` is essential in this situation.

Having all this in mind, the `errdefer` keyword is different but also similar
to the `defer` keyword. The only difference between the two is when the provided expression
get's executed. The `defer` keyword always execute the provided expression at the end of the
current scope, while `errdefer` executes the provided expression when an error occurs in the
Just to make very clear the differences between `defer` (which I described at @sec-defer)
and `errdefer`, it might be worth to discuss the subject a bit further.
You might still have the question "why use `errdefer` if we can use `defer` instead?"
in your mind.

Although being similar, the key difference between `errdefer` and `defer` keyword
is when the provided expression get's executed.
The `defer` keyword always execute the provided expression at the end of the
current scope, no matter how your code exits this scope.
In contrast, `errdefer` executes the provided expression only when an error occurs in the
current scope.

This becomes important if a resource that you allocate in the
current scope get's freed later in your code, in a different scope.
The `create_user()` functions is an example of this. If you think
closely about this function, you will notice that this function returns
the `user` object as the result.

In other words, the allocated memory for the `user` object does not get
freed inside the `create_user()`, if the function returns succesfully.
So, if an error does not occur inside this function, the `user` object
is returned from the function, and probably, the code that runs after
this `create_user()` function will be responsible for freeying
the memory of the `user` object.

But what if an error do occur inside the `create_user()`? What happens then?
This would mean that the execution of your code would stop in this `create_user()`
function, and, as a consequence, the code that runs after this `create_user()`
function would simply not run, and, as a result, the memory of the `user` object
would not be freed before your program stops.

This is the perfect scenario for `errdefer`. We use this keyword to garantee
that our program will free the allocated memory for the `user` object,
even if an error occurs inside the `create_user()` function.

If you allocate and free some memory for an object in the same scope, then,
just use `defer` and be happy, `errdefer` have no use for you in such situation.
But if you allocate some memory in a scope A, but you only free this memory
later, in a scope B for example, then, `errdefer` becomes useful to avoid leaking memory
in sketchy situations.



## Union type in Zig {#sec-unions}
Expand Down
4 changes: 2 additions & 2 deletions _freeze/Chapters/01-memory/execute-results/html.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions _freeze/Chapters/01-zig-weird/execute-results/html.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions _freeze/Chapters/09-error-handling/execute-results/html.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/Chapters/01-memory.html
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ <h3 data-number="2.2.8" class="anchored" data-anchor-id="the-alloc-and-free-meth
<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>
<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>
</div>
<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>
<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>
<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>
</section>
<section id="the-create-and-destroy-methods" class="level3" data-number="2.2.9">
Expand Down
Loading

0 comments on commit b9e5d69

Please sign in to comment.