Skip to content

Commit 77dc10f

Browse files
authored
Merge pull request #24 from pedropark99/fix
Add a section about function arguments being immutable
2 parents a17bba3 + 95ed21b commit 77dc10f

File tree

12 files changed

+379
-206
lines changed

12 files changed

+379
-206
lines changed

Chapters/01-zig-weird.qmd

+1-1
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ t.zig:7:5: error: local variable is never mutated
730730
t.zig:7:5: note: consider using 'const'
731731
```
732732

733-
## Primitive Data Types
733+
## Primitive Data Types {#sec-primitive-data-types}
734734

735735
Zig have many different primitive data types available for you to use.
736736
You can see the full list of available data types at the official

Chapters/03-structs.qmd

+115-20
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,90 @@ for (ns) |i| {
434434
```
435435

436436

437+
438+
## Function parameters are immutable {#sec-fun-pars}
439+
440+
We have already discussed a lot of the syntax behind function declarations at @sec-root-file and @sec-main-file.
441+
But I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig.
442+
In summary, function parameters are immutable in Zig.
443+
444+
Take the code example below, where we declare a simple function that just tries to add
445+
some amount to the input integer, and returns the result back. But if you look closely
446+
at the body of this `add2()` function, you will notice that we try
447+
to save the result back into the `x` function argument.
448+
449+
In other words, this function not only use the value that it received through the function argument
450+
`x`, but it also tries to change the value of this function argument, by assigning the addition result
451+
into `x`. However, function arguments in Zig are immutable. You cannot change their values, or, you
452+
cannot assign values to them inside the body's function.
453+
454+
This is the reason why, the code example below do not compile successfully. If you try to compile
455+
this code example, you get a compile error warning you that you are trying to change the value of a
456+
immutable (i.e. constant) object.
457+
458+
```{zig}
459+
#| eval: false
460+
const std = @import("std");
461+
fn add2(x: u32) u32 {
462+
x = x + 2;
463+
return x;
464+
}
465+
466+
pub fn main() !void {
467+
const y = add2(4);
468+
std.debug.print("{d}\n", .{y});
469+
}
470+
```
471+
472+
```
473+
t.zig:3:5: error: cannot assign to constant
474+
x = x + 2;
475+
^
476+
```
477+
478+
479+
If a function argument receives as input a object whose data type is
480+
any of the primitive types that we have listed at @sec-primitive-data-types
481+
this object is always passed by value to the function. In other words, this object
482+
is copied to the function stack frame.
483+
484+
However, if the input object have a more complex data type, for example, it might
485+
be a struct instance, or an array, or a union, etc., in cases like that, the `zig` compiler
486+
will take the liberty of deciding for you which strategy is best. The `zig` compiler will
487+
pass your object to the function either by value, or by reference. The compiler will always
488+
choose the strategy that is faster for you.
489+
This optimization that you get for free is possible only because function arguments are
490+
immutable in Zig.
491+
492+
To overcome this barrier, we need to take the lead, and explicitly choose to pass the
493+
object by reference. That is, instead of depending on the `zig` compiler to decide for us, we need
494+
to explicitly mark the function argument as a pointer. This way, we are telling the compiler
495+
that this function argument will be passed by reference to the function.
496+
497+
By making it a pointer, we can finally use and alter directly the value of this function argument inside
498+
the body of the `add2()` function. You can see that the code example below compiles successfully.
499+
500+
```{zig}
501+
#| auto_main: false
502+
const std = @import("std");
503+
fn add2(x: *u32) void {
504+
const d: u32 = 2;
505+
x.* = x.* + d;
506+
}
507+
508+
pub fn main() !void {
509+
var x: u32 = 4;
510+
add2(&x);
511+
std.debug.print("Result: {d}\n", .{x});
512+
}
513+
```
514+
515+
```
516+
Result: 6
517+
```
518+
519+
520+
437521
## Structs and OOP {#sec-structs-and-oop}
438522

439523
Zig is a language more closely related to C (which is a procedural language),
@@ -599,7 +683,7 @@ const Vec3 = struct {
599683
```
600684

601685

602-
### The `self` method argument
686+
### The `self` method argument {#sec-self-arg}
603687

604688
In every language that have OOP, when we declare a method of some class or struct, we
605689
usually declare this method as a function that have a `self` argument.
@@ -672,17 +756,17 @@ Distance: 3.3970575502926055
672756

673757
Sometimes you don't need to care about the state of your struct object. Sometimes, you just need
674758
to instantiate and use the objects, without altering their state. You can notice that when you have methods
675-
inside this struct object that might use the values that are present the data members, but they
676-
do not alter the values in the data members of structs in anyway.
759+
inside your struct declaration that might use the values that are present in the data members, but they
760+
do not alter the values in the data members of the struct in anyway.
677761

678-
The `Vec3` struct that we presented in the previous section is an example of that.
762+
The `Vec3` struct that was presented at @sec-self-arg is an example of that.
679763
This struct have a single method named `distance()`, and this method do use the values
680764
present in all three data members of the struct (`x`, `y` and `z`). But at the same time,
681765
this method do not change the values of these data members in any point.
682766

683767
As a result of that, when we create `Vec3` objects we usually create them as
684-
constant objects, like the `v1` and `v2` objects presented in the previous
685-
code example. We can create them as variable objects with the `var` keyword,
768+
constant objects, like the `v1` and `v2` objects presented at @sec-self-arg.
769+
We can create them as variable objects with the `var` keyword,
686770
if we want to. But because the methods of this `Vec3` struct do not change
687771
the state of the objects in any point, is unnecessary to mark them
688772
as variable objects.
@@ -694,7 +778,7 @@ More specifically, when you have a method in a struct that changes the state
694778
of the object (i.e. change the value of a data member), the `self` argument
695779
in this method must be annotated in a different manner.
696780

697-
As I described in the previous section, the `self` argument in methods of
781+
As I described at @sec-self-arg, the `self` argument in methods of
698782
a struct is the argument that receives as input the object from which the method
699783
was called from. We usually annotate this argument in the methods by writing `self`,
700784
followed by the colon character (`:`), and the data type of the struct to which
@@ -707,7 +791,7 @@ method.
707791

708792
But what if we do have a method that alters the state of the object, by altering the
709793
values of it's data members. How should we annotate `self` in this instance? The answer is:
710-
"we should pass a pointer of `x` to `self`, and not simply a copy of `x` to `self`".
794+
"we should annotate `self` as a pointer of `x`, instead of just `x`".
711795
In other words, you should annotate `self` as `self: *x`, instead of annotating it
712796
as `self: x`.
713797

@@ -746,7 +830,7 @@ to our `Vec3` struct named `double()`. This method essentially doubles the
746830
coordinate values of our vector object. Also notice that, in the
747831
case of the `double()` method, we annotated the `self` argument as `*Vec3`,
748832
indicating that this argument receives a pointer (or a reference, if you prefer to call it this way)
749-
to a `Vec3` object, instead of receiving a copy of the object directly, as input.
833+
to a `Vec3` object as input.
750834

751835
```{zig}
752836
#| eval: false
@@ -763,8 +847,8 @@ Doubled: 8.4
763847

764848

765849

766-
If you change the `self` argument in this `double()` method to `self: Vec3`, like in the
767-
`distance()` method, you will get the error exposed below as result. Notice that this
850+
Now, if you change the `self` argument in this `double()` method to `self: Vec3`, like in the
851+
`distance()` method, you will get the compiler error exposed below as result. Notice that this
768852
error message is indicating a line from the `double()` method body,
769853
indicating that you cannot alter the value of the `x` data member.
770854

@@ -774,31 +858,42 @@ indicating that you cannot alter the value of the `x` data member.
774858
```
775859

776860
This error message indicates that the `x` data member belongs to a constant object,
777-
and, because of that, it cannot be changed. Even though we marked the `v3` object
778-
that we have created in the previous code example as a variable object.
779-
So even though this `x` data member belongs to a variable object in our code, this error
780-
message is pointing to the opposite direction.
861+
and, because of that, it cannot be changed. Ultimately, this error message
862+
is telling us that the `self` argument is constant.
781863

782864
```
783865
t.zig:16:13: error: cannot assign to constant
784866
self.x = self.x * 2.0;
785867
~~~~^~
786868
```
787869

788-
But this error message is misleading, because the only thing that we have changed
789-
is the `self` argument signature from `*Vec3` to `Vec3` in the `double()` method. So, just remember this
790-
general rule below about your method declarations:
870+
If you take some time, and think hard about this error message, you will understand it.
871+
You already have the tools to understand why we are getting this error message.
872+
We have talked about it already at @sec-fun-pars.
873+
So remember, every function argument is immutable in Zig, and `self`
874+
is included in this rule.
875+
876+
It does not matter if the object that you pass as input to the function argument is
877+
a variable object or not. In this example, we marked the `v3` object as a variable object.
878+
But this does not matter. Because it is not about the input object, it is about
879+
the function argument.
880+
881+
The problem begins when we try to alter the value of `self` directly, which is a function argument,
882+
and, every function argument is immutable by default. You may quest yourself how can we overcome
883+
this barrier, and once again, the solution was also discussed at @sec-fun-pars.
884+
We overcome this barrier, by explicitly marking the `self` argument as a pointer.
885+
791886

792887
::: {.callout-note}
793888
If a method of your `x` struct alters the state of the object, by
794889
changing the value of any data member, then, remember to use `self: *x`,
795890
instead of `self: x` in the function signature of this method.
796891
:::
797892

893+
798894
You could also interpret the content discussed in this section as:
799895
"if you need to alter the state of your `x` struct object in one of it's methods,
800-
you must pass the `x` struct object by reference to the `self` argument of this method,
801-
instead of passing it by value".
896+
you must explicitly pass the `x` struct object by reference to the `self` argument of this method".
802897

803898

804899

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const std = @import("std");
2+
// This code does not compile because we are trying to
3+
// change the value of a function parameter.
4+
fn add2(x: u32) u32 {
5+
x = x + 2;
6+
return x;
7+
}
8+
9+
pub fn main() !void {
10+
const y = add2(4);
11+
std.debug.print("{d}\n", .{y});
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const std = @import("std");
2+
fn add2(x: *u32) void {
3+
const d: u32 = 2;
4+
x.* = x.* + d;
5+
}
6+
7+
pub fn main() !void {
8+
var x: u32 = 4;
9+
add2(&x);
10+
std.debug.print("{d}\n", .{x});
11+
}

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

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

_freeze/Chapters/03-structs/execute-results/html.json

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

docs/Chapters/01-zig-weird.html

+4-4
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ <h2 id="toc-title">Table of contents</h2>
277277
<li><a href="#there-is-no-such-thing-as-unused-objects" id="toc-there-is-no-such-thing-as-unused-objects" class="nav-link" data-scroll-target="#there-is-no-such-thing-as-unused-objects"><span class="header-section-number">1.4.3</span> There is no such thing as unused objects</a></li>
278278
<li><a href="#you-must-mutate-every-variable-objects" id="toc-you-must-mutate-every-variable-objects" class="nav-link" data-scroll-target="#you-must-mutate-every-variable-objects"><span class="header-section-number">1.4.4</span> You must mutate every variable objects</a></li>
279279
</ul></li>
280-
<li><a href="#primitive-data-types" id="toc-primitive-data-types" class="nav-link" data-scroll-target="#primitive-data-types"><span class="header-section-number">1.5</span> Primitive Data Types</a></li>
280+
<li><a href="#sec-primitive-data-types" id="toc-sec-primitive-data-types" class="nav-link" data-scroll-target="#sec-primitive-data-types"><span class="header-section-number">1.5</span> Primitive Data Types</a></li>
281281
<li><a href="#sec-arrays" id="toc-sec-arrays" class="nav-link" data-scroll-target="#sec-arrays"><span class="header-section-number">1.6</span> Arrays</a>
282282
<ul class="collapse">
283283
<li><a href="#selecting-elements-of-the-array" id="toc-selecting-elements-of-the-array" class="nav-link" data-scroll-target="#selecting-elements-of-the-array"><span class="header-section-number">1.6.1</span> Selecting elements of the array</a></li>
@@ -572,8 +572,8 @@ <h3 data-number="1.4.4" class="anchored" data-anchor-id="you-must-mutate-every-v
572572
t.zig:7:5: note: consider using 'const'</code></pre>
573573
</section>
574574
</section>
575-
<section id="primitive-data-types" class="level2" data-number="1.5">
576-
<h2 data-number="1.5" class="anchored" data-anchor-id="primitive-data-types"><span class="header-section-number">1.5</span> Primitive Data Types</h2>
575+
<section id="sec-primitive-data-types" class="level2" data-number="1.5">
576+
<h2 data-number="1.5" class="anchored" data-anchor-id="sec-primitive-data-types"><span class="header-section-number">1.5</span> Primitive Data Types</h2>
577577
<p>Zig have many different primitive data types available for you to use. You can see the full list of available data types at the official <a href="https://ziglang.org/documentation/master/#Primitive-Types">Language Reference page</a><a href="#fn15" class="footnote-ref" id="fnref15" role="doc-noteref"><sup>15</sup></a>.</p>
578578
<p>But here is a quick list:</p>
579579
<ul>
@@ -874,7 +874,7 @@ <h2 data-number="1.10" class="anchored" data-anchor-id="other-parts-of-zig"><spa
874874
</ul>
875875
<p>But, for now, this amount of knowledge is enough for us to continue with this book. Later, over the next chapters we will still talk more about other parts of Zig’s syntax that are also equally important as the other parts. Such as:</p>
876876
<ul>
877-
<li>How Object-Oriented programming can be done in Zig through <em>struct declarations</em> at <a href="03-structs.html#sec-structs-and-oop" class="quarto-xref"><span>Section 2.2</span></a>.</li>
877+
<li>How Object-Oriented programming can be done in Zig through <em>struct declarations</em> at <a href="03-structs.html#sec-structs-and-oop" class="quarto-xref"><span>Section 2.3</span></a>.</li>
878878
<li>Basic control flow syntax at <a href="03-structs.html#sec-zig-control-flow" class="quarto-xref"><span>Section 2.1</span></a>.</li>
879879
<li>Enums at <a href="04-http-server.html#sec-enum" class="quarto-xref"><span>Section 7.6</span></a>;</li>
880880
<li>Pointers and Optionals at <a href="05-pointers.html" class="quarto-xref"><span>Chapter 6</span></a>;</li>

0 commit comments

Comments
 (0)