-
Notifications
You must be signed in to change notification settings - Fork 166
Expand file tree
/
Copy path01-zig-weird.qmd
More file actions
1619 lines (1245 loc) · 69.8 KB
/
Copy path01-zig-weird.qmd
File metadata and controls
1619 lines (1245 loc) · 69.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
engine: knitr
knitr: true
syntax-definition: "../Assets/zig.xml"
---
```{r}
#| include: false
source("../zig_engine.R")
knitr::opts_chunk$set(
auto_main = FALSE,
build_type = "lib"
)
```
# Introducing Zig
In this chapter, I want to introduce you to the world of Zig.
Zig is a very young language that is being actively developed.
As a consequence, its world is still very wild and to be explored.
This book is my attempt to help you on your personal journey for
understanding and exploring the exciting world of Zig.
I assume you have previous experience with some programming
language in this book, not necessarily with a low-level one.
So, if you have experience with Python, or Javascript, for example, it will be fine.
But, if you do have experience with low-level languages, such as C, C++, or
Rust, you will probably learn faster throughout this book.
## What is Zig?
Zig is a modern, low-level, and general-purpose programming language. Some programmers think of
Zig as a modern and better version of C.
In the author's personal interpretation, Zig is tightly connected with "less is more".
Instead of trying to become a modern language by adding more and more features,
many of the core improvements that Zig brings to the
table are actually about removing annoying behaviours/features from C and C++.
In other words, Zig tries to be better by simplifying the language, and by having more consistent and robust behaviour.
As a result, analyzing, writing and debugging applications become much easier and simpler in Zig, than it is in C or C++.
This philosophy becomes clear with the following phrase from the official website of Zig:
> "Focus on debugging your application rather than debugging your programming language knowledge".
This phrase is especially true for C++ programmers. Because C++ is a gigantic language,
with tons of features, and also, there are lots of different "flavors of C++". These elements
are what makes C++ so complex and hard to learn. Zig tries to go in the opposite direction.
Zig is a very simple language, more closely related to other simple languages such as C and Go.
The phrase above is still important for C programmers too. Because, even C being a simple
language, it's still hard sometimes to read and understand C code. For example, pre-processor macros in
C are a frequent source of confusion. Sometimes, they really make it hard to debug
C programs. Because macros are essentially a second language embedded in C that obscures
your C code. With macros, you are no longer 100% sure about which pieces
of the code are being sent to the compiler, i.e.
they obscure the actual source code that you wrote.
You don't have macros in Zig. In Zig, the code you write, is the actual code that gets compiled by the compiler.
You also don't have a hidden control flow happening behind the scenes. And, you also
don't have functions or operators from the standard library that make
hidden memory allocations behind your back.
By being a simpler language, Zig becomes much more clear and easier to read/write,
but at the same time, it also achieves a much more robust state, with more consistent
behaviour in edge situations. Once again, less is more.
## Hello world in Zig
We begin our journey in Zig by creating a small "Hello World" program.
To start a new Zig project on your computer, you simply call the `init` command
from the `zig` compiler.
Just create a new directory on your computer, then, init a new Zig project
inside this directory, like this:
```bash
mkdir hello_world
cd hello_world
zig init
```
```
info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
info: see `zig build --help` for a menu of options
```
### Understanding the project files {#sec-project-files}
After you run the `init` command from the `zig` compiler, some new files
are created inside of your current directory. First, a "source" (`src`) directory
is created, containing two files, `main.zig` and `root.zig`. Each `.zig` file
is a separate Zig module, which is simply a text file that contains some Zig code.
By convention, the `main.zig` module is where your main function lives. Thus,
if you are building an executable program in Zig, you need to declare a `main()` function,
which represents the entrypoint of your program, i.e., where the execution of your program begins.
However, if you are building a library (instead of an executable program), then,
the normal procedure is to delete this `main.zig` file and start with the `root.zig` module.
By convention, the `root.zig` module is the root source file of your library.
```bash
tree .
```
```
.
├── build.zig
├── build.zig.zon
└── src
├── main.zig
└── root.zig
1 directory, 4 files
```
The `init` command also creates two additional files in our working directory:
`build.zig` and `build.zig.zon`. The first file (`build.zig`) represents a build script written in Zig.
This script is executed when you call the `build` command from the `zig` compiler.
In other words, this file contains Zig code that executes the necessary steps to build the entire project.
Low-level languages normally use a compiler to build your
source code into binary executables or binary libraries.
Nevertheless, this process of compiling your source code and building
binary executables or binary libraries from it, became a real challenge
in the programming world, once the projects became bigger and bigger.
As a result, programmers created "build systems", which are a second set of tools designed to make this process
of compiling and building complex projects, easier.
Examples of build systems are CMake, GNU Make, GNU Autoconf and Ninja,
which are used to build complex C and C++ projects.
With these systems, you can write scripts, which are called "build scripts".
They simply are scripts that describe the necessary steps to compile/build
your project.
However, these are separate tools, that do not
belong to C/C++ compilers, like `gcc` or `clang`.
As a result, in C/C++ projects, you have not only to install and
manage your C/C++ compilers, but you also have to install and manage
these build systems separately.
In Zig, we don't need to use a separate set of tools to build our projects,
because a build system is embedded inside the language itself.
We can use this build system to write small scripts in Zig,
which describe the necessary steps to build/compile our Zig project[^zig-build-system].
So, everything you need to build a complex Zig project is the
`zig` compiler, and nothing more.
[^zig-build-system]: <https://ziglang.org/learn/overview/#zig-build-system>.
The second generated file (`build.zig.zon`) is a JSON-like file, in which you can describe
your project, and also, declare a set of dependencies of your project that you want to fetch from the internet.
In other words, you can use this `build.zig.zon` file to include a list of external libraries in your project.
One possible way to include an external Zig library in your project, is to manually build
and install the library in your system, and just link your source code
with the library at the build step of your project.
However, if this external Zig library is available on GitHub for example,
and it has a valid `build.zig.zon` file in the root folder of the project,
which describes the project, you can easily include this library in
your project by simply listing this external library in your `build.zig.zon` file.
In other words, this `build.zig.zon` file works similarly to the `package.json`
file in Javascript projects, or the `Pipfile` file in Python projects,
or the `Cargo.toml` file in Rust projects. You can read more about this
specific file in a couple of articles on the internet[^zig-zon][^zig-zon2], and
you can also see the expected schema for this `build.zig.zon` file
in a documentation file inside the official repository of Zig[^zig-zon-schema].
[^zig-zon]: <https://zig.news/edyu/zig-package-manager-wtf-is-zon-558e>
[^zig-zon2]: <https://medium.com/@edlyuu/zig-package-manager-2-wtf-is-build-zig-zon-and-build-zig-0-11-0-update-5bc46e830fc1>
[^zig-zon-schema]: <https://github.com/ziglang/zig/blob/master/doc/build.zig.zon.md>
### The file `root.zig` {#sec-root-file}
Let's take a look into the `root.zig` file.
You might have noticed that every line of code with an expression ends with a semicolon (`;`).
This follows the syntax of a C-family programming language[^c-family].
[^c-family]: <https://en.wikipedia.org/wiki/List_of_C-family_programming_languages>
Also, notice the `@import()` call at the first line. We use this built-in function
to import functionality from other Zig modules into our current module.
This `@import()` function works similarly to the `#include` pre-processor
in C or C++, or, to the `import` statement in Python or Javascript code.
In this example, we are importing the `std` module,
which gives you access to the Zig Standard Library.
In this `root.zig` file, we can also see how assignments (i.e., creating new objects)
are made in Zig. You can create a new object in Zig by using the syntax
`(const|var) name = value;`. In the example below, we are creating two constant
objects (`std` and `testing`). In @sec-assignments we talk more about objects in general.
```{zig}
#| auto_main: false
#| build_type: "ast"
const std = @import("std");
const testing = std.testing;
export fn add(a: i32, b: i32) i32 {
return a + b;
}
```
Functions in Zig are declared using the `fn` keyword.
In this `root.zig` module, we are declaring a function called `add()`, which has two arguments named `a` and `b`.
The function returns an integer of the type `i32` as result.
Zig is a strongly-typed language. There are some specific situations where you can (if you want to) omit
the type of an object in your code, if this type can be inferred by the `zig` compiler (we talk more
about that in @sec-type-inference). But there are other situations where you do need to be explicit.
For example, you do have to explicitly specify the type of each function argument, and also,
the return type of every function that you create in Zig.
We specify the type of an object or a function argument in Zig by
using a colon character (`:`) followed by the type after the name of this object/function argument.
With the expressions `a: i32` and `b: i32`, we know that both `a` and `b` arguments have type `i32`,
which is a signed 32 bit integer. In this part,
the syntax in Zig is identical to the syntax in Rust, which also specifies types by
using the colon character.
Lastly, we have the return type of the function at the end of the line, before we open
the curly braces to start writing the function's body. In the example above, this type is also
a signed 32 bit integer (`i32`) value.
Notice that we also have an `export` keyword before the function declaration. This keyword
is similar to the `extern` keyword in C. It exposes the function
to make it available in the library API. Therefore, if you are writing
a library for other people to use, you have to expose the functions
you write in the public API of this library by using this `export` keyword.
If we removed the `export` keyword from the `add()` function declaration,
then, this function would be no longer exposed in the library object built
by the `zig` compiler.
### The `main.zig` file {#sec-main-file}
Now that we have learned a lot about Zig's syntax from the `root.zig` file,
let's take a look at the `main.zig` file.
A lot of the elements we saw in `root.zig` are also present in `main.zig`.
But there are some other elements that we haven't seen yet, so let's dive in.
First, look at the return type of the `main()` function in this file.
We can see a small change. The return type of the function (`void`) is accompanied by an exclamation mark (`!`).
This exclamation mark tells us that this `main()` function might return an error.
It's worth noting that, a `main()` function in Zig is allowed to return nothing (`void`),
or an unsigned 8-bit integer (`u8`) value[^u8-example], or an error. In other words, you can write your `main()` function in Zig
to return essentially nothing (`void`), or, if you prefer, you can also write a more C-like `main()` function,
which returns an integer value that usually serves as a "status code" for the process.
[^u8-example]: You can see an example of a `main()` function that returns an `u8` value in the `return-integer.zig` file, <https://github.com/pedropark99/zig-book/blob/main/ZigExamples/zig-basics/return-integer.zig>
In this example, the return type annotation of `main()` indicates that this function can either
return nothing (`void`), or return an error. This exclamation mark in the return type annotation
is an interesting and powerful feature of Zig. In summary, if you write a function and something inside
the body of this function might return an error, then, you are forced to:
- either add the exclamation mark to the return type of the function and make it clear that
this function might return an error.
- explicitly handle this error inside the function.
In most programming languages, we normally handle (or deal with) an error through
a *try catch* pattern. Zig does have both `try` and `catch` keywords. But they work
a little differently than what you're probably used to in other languages.
If we look at the `main()` function below, you can see that we do have a `try` keyword
on the 5th line. But we do not have a `catch` keyword in this code.
In Zig, we use the `try` keyword to execute an expression that might return an error,
which, in this example, is the `stdout.print()` expression.
In essence, the `try` keyword executes the expression `stdout.print()`. If this expression
returns a valid value, then, the `try` keyword does absolutely nothing. It only passes the value forward.
It's like if this `try` keyword was never there. However, if the expression does return an error, then,
the `try` keyword will unwrap the error value, then, it returns this error from the function
and also prints the current stack trace to `stderr`.
This might sound weird to you if you come from a high-level language. Because in
high-level languages, such as Python, if an error occurs somewhere, this error is automatically
returned and the execution of your program will automatically stop even if you don't want
to stop the execution. You are obligated to face the error.
```{zig}
#| auto_main: false
#| build_type: "ast"
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello, {s}!\n", .{"world"});
}
```
Another thing that you might have noticed in this code example, is that
the `main()` function is marked with the `pub` keyword. It marks the `main()`
function as a *public function* from this module. Every function in your Zig
module is by default private to this Zig module and can only be called from within the module.
Unless, you explicitly mark this function as a public function with the `pub` keyword.
If you think about it, this `pub` keyword in Zig does essentially the opposite of what the `static` keyword
does in C/C++. By making a function "public" you allow other Zig modules to access and call this function.
A calling Zig module imports another module by using the `@import()` built-in function, which makes
all public functions from the imported module visible to the calling Zig module.
### Compiling your source code {#sec-compile-code}
You can compile your Zig modules into a binary executable by running the `build-exe` command
from the `zig` compiler. You simply list all the Zig modules that you want to build after
the `build-exe` command, separated by spaces. In the example below, we are compiling the module `main.zig`.
```bash
zig build-exe src/main.zig
```
Since we are building an executable, the `zig` compiler will look for a `main()` function
declared in any of the files that you list after the `build-exe` command. If
the compiler does not find a `main()` function declared somewhere, a
compilation error will be raised, warning about this mistake.
The `zig` compiler also offers a `build-lib` and `build-obj` commands, which work
the exact same way as the `build-exe` command. The only difference is that, they compile your
Zig modules into a portable C ABI library, or, into object files, respectively.
In the case of the `build-exe` command, a binary executable file is created by the `zig`
compiler in the root directory of your project.
If we take a look now at the contents of our current directory, with a simple `ls` command, we can
see the binary file called `main` that was created by the compiler.
```bash
ls
```
```
build.zig build.zig.zon main src
```
If I execute this binary executable, I get the "Hello World" message in the terminal
, as we expected.
```bash
./main
```
```
Hello, world!
```
### Compile and execute at the same time {#sec-compile-run-code}
In the previous section, I presented the `zig build-exe` command, which
compiles Zig modules into an executable file. However, this means that,
in order to execute the executable file, we have to run two different commands.
First, the `zig build-exe` command, and then, we call the executable file
created by the compiler.
But what if we wanted to perform these two steps,
all at once, in a single command? We can do that by using the `zig run`
command.
```bash
zig run src/main.zig
```
```
Hello, world!
```
### Important note for Windows users
First of all, this is a Windows-specific thing, and, therefore, does not apply to other
operating systems, such as Linux and macOS. In summary, if you have a piece of Zig code that
includes some global variables whose initialization rely on runtime resources, then,
you might have some troubles while trying to compile this Zig code on Windows.
An example of that is accessing the `stdout` (i.e., the *standard output* of your system), which is usually
done in Zig by using the expression `std.io.getStdOut()`. If you use this expression to instantiate
a global variable in a Zig module, then, the compilation of your Zig code will very likely fail on Windows,
with an "unable to evaluate comptime expression" error message.
This failure in the compilation process happens because all global variables in Zig are initialized
at *compile-time*. However, on Windows, operations like accessing the `stdout` (or opening a file) depend on
resources that are available only at *runtime* (you will learn more about compile-time versus runtime
in @sec-compile-time).
For example, if you try to compile this code example on Windows, you will likely get the error message
exposed below:
```{zig}
#| auto_main: false
#| build_type: "ast"
const std = @import("std");
// ERROR! Compile-time error that emerges from
// this next line, on the `stdout` object
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
_ = try stdout.write("Hello\n");
}
```
```
t.zig:2107:28: error: unable to evaluate comptime expression
break :blk asm {
^~~
```
To avoid this problem on Windows, we need to force the `zig` compiler to instantiate this
`stdout` object only at runtime, instead of instantiating it at compile-time. We can achieve
that by simply moving the expression to a function body.
This solves the problem because all expressions that are inside a function body in Zig
are evaluated only at runtime, unless you use the `comptime` keyword explicitly to change this behaviour.
You will learn more about this `comptime` keyword in @sec-comptime.
```{zig}
#| build_type: "ast"
#| auto_main: false
const std = @import("std");
pub fn main() !void {
// SUCCESS: Stdout initialized at runtime.
const stdout = std.io.getStdOut().writer();
_ = try stdout.write("Hello\n");
}
```
```
Hello
```
You can read more details about this Windows-specific limitation in a couple of
GitHub issues opened at the official Zig repository. More specifically, the issues
17186 [^cissue1] and 19864 [^cissue2].
[^cissue1]: <https://github.com/ziglang/zig/issues/17186>
[^cissue2]: <https://github.com/ziglang/zig/issues/19864>
### Compiling the entire project {#sec-compile-project}
Just as I described in @sec-project-files, as our project grows in size and
complexity, we usually prefer to organize the compilation and build process
of the project into a build script, using some sort of "build system".
In other words, as our project grows in size and complexity,
the `build-exe`, `build-lib` and `build-obj` commands become
harder to use directly. Because then, we start to list
multiple and multiple modules at the same time. We also
start to add built-in compilation flags to customize the
build process for our needs, etc. It becomes a lot of work
to write the necessary commands by hand.
In C/C++ projects, programmers normally opt to use CMake, Ninja, `Makefile` or `configure` scripts
to organize this process. However, in Zig, we have a native build system in the language itself.
So, we can write build scripts in Zig to compile and build Zig projects. Then, all we
need to do, is to call the `zig build` command to build our project.
So, when you execute the `zig build` command, the `zig` compiler will search
for a Zig module named `build.zig` inside your current directory, which
should be your build script, containing the necessary code to compile and
build your project. If the compiler does find this `build.zig` file in your directory,
then, the compiler will essentially execute a `zig run` command
over this `build.zig` file, to compile and execute this build
script, which in turn, will compile and build your entire project.
```bash
zig build
```
After you execute this "build project" command, a `zig-out` directory
is created in the root of your project directory, where you can find
the binary executables and libraries created from your Zig modules
accordingly to the build commands that you specified at `build.zig`.
We will talk more about the build system in Zig later in this book.
In the example below, I'm executing the binary executable
named `hello_world` that was generated by the compiler after the
`zig build` command.
```bash
./zig-out/bin/hello_world
```
```
Hello, world!
```
## How to learn Zig?
What are the best strategies to learn Zig?
First of all, of course this book will help you a lot on your journey through Zig.
But you will also need some extra resources if you want to be really good at Zig.
As a first tip, you can join a community with Zig programmers to get some help
, when you need it:
- Reddit forum: <https://www.reddit.com/r/Zig/>;
- Ziggit community: <https://ziggit.dev/>;
- Discord, Slack, Telegram, and others: <https://github.com/ziglang/zig/wiki/Community>;
Now, one of the best ways to learn Zig is to simply read Zig code. Try
to read Zig code often, and things will become more clear.
A C/C++ programmer would also probably give you this same tip.
Because this strategy really works!
Now, where can you find Zig code to read?
I personally think that, the best way of reading Zig code is to read the source code of the
Zig Standard Library. The Zig Standard Library is available at the [`lib/std` folder](https://github.com/ziglang/zig/tree/master/lib/std)[^zig-lib-std] on
the official GitHub repository of Zig. Access this folder, and start exploring the Zig modules.
Also, a great alternative is to read code from other large Zig
codebases, such as:
1. the [Javascript runtime Bun](https://github.com/oven-sh/bun)[^bunjs].
1. the [game engine Mach](https://github.com/hexops/mach)[^mach].
1. a [LLama 2 LLM model implementation in Zig](https://github.com/cgbur/llama2.zig/tree/main)[^ll2].
1. the [financial transactions database `tigerbeetle`](https://github.com/tigerbeetle/tigerbeetle)[^tiger].
1. the [command-line arguments parser `zig-clap`](https://github.com/Hejsil/zig-clap)[^clap].
1. the [UI framework `capy`](https://github.com/capy-ui/capy)[^capy].
1. the [Language Protocol implementation for Zig, `zls`](https://github.com/zigtools/zls)[^zls].
1. the [event-loop library `libxev`](https://github.com/mitchellh/libxev)[^xev].
[^xev]: <https://github.com/mitchellh/libxev>
[^zls]: <https://github.com/zigtools/zls>
[^capy]: <https://github.com/capy-ui/capy>
[^clap]: <https://github.com/Hejsil/zig-clap>
[^tiger]: <https://github.com/tigerbeetle/tigerbeetle>
[^ll2]: <https://github.com/cgbur/llama2.zig/tree/main>
[^mach]: <https://github.com/hexops/mach>
[^bunjs]: <https://github.com/oven-sh/bun>.
All these assets are available on GitHub,
and this is great, because we can use the GitHub search bar to our advantage,
to find Zig code that fits our description.
For example, you can always include `lang:Zig` in the GitHub search bar when you
are searching for a particular pattern. This will limit the search to only Zig modules.
[^zig-lib-std]: <https://github.com/ziglang/zig/tree/master/lib/std>
Also, a great alternative is to consult online resources and documentation.
Here is a quick list of resources that I personally use from time to time to learn
more about the language each day:
- Zig Language Reference: <https://ziglang.org/documentation/master/>;
- Zig Standard Library Reference: <https://ziglang.org/documentation/master/std/>;
- Zig Guide: <https://zig.guide/>;
- Karl Seguin Blog: <https://www.openmymind.net/>;
- Zig News: <https://zig.news/>;
- Read the code written by one of the Zig core team members: <https://github.com/kubkon>;
- Some livecoding sessions are transmitted in the Zig Showtime Youtube Channel: <https://www.youtube.com/@ZigSHOWTIME/videos>;
Another great strategy to learn Zig, or honestly, to learn any language you want,
is to practice it by solving exercises. For example, there is a famous repository
in the Zig community called [Ziglings](https://ziglings.org)[^ziglings]
, which contains more than 100 small exercises that you can solve. It's a repository of
tiny programs written in Zig that are currently broken, and your responsibility is to
fix these programs, and make them work again.
[^ziglings]: <https://ziglings.org>.
A famous tech YouTuber known as *The Primeagen* also posted some videos (on YouTube)
where he solves these exercises from Ziglings. The first video is named
["Trying Zig Part 1"](https://www.youtube.com/watch?v=OPuztQfM3Fg&t=2524s&ab_channel=TheVimeagen)[^prime1].
[^prime1]: <https://www.youtube.com/watch?v=OPuztQfM3Fg&t=2524s&ab_channel=TheVimeagen>.
Another great alternative, is to solve the [Advent of Code exercises](https://adventofcode.com/)[^advent-code].
There are people that already took the time to learn and solve the exercises, and they posted
their solutions on GitHub as well, so, in case you need some resource to compare while solving
the exercises, you can look at these two repositories:
- <https://github.com/SpexGuy/Zig-AoC-Template>;
- <https://github.com/fjebaker/advent-of-code-2022>;
[^advent-code]: <https://adventofcode.com/>
## Creating new objects in Zig (i.e., identifiers) {#sec-assignments}
Let's talk more about objects in Zig. Readers that have past experience
with other programming languages might know this concept through
a different name, such as: "variable" or "identifier". In this book, I choose
to use the term "object" to refer to this concept.
To create a new object (or a new "identifier") in Zig, we use
the keywords `const` or `var`. These keywords specify if the object
that you are creating is mutable or not.
If you use `const`, then the object you are
creating is a constant (or immutable) object, which means that once you declare this object, you
can no longer change the value stored inside this object.
On the other side, if you use `var`, then, you are creating a variable (or mutable) object.
You can change the value of this object as many times you want. Using the
keyword `var` in Zig is similar to using the keywords `let mut` in Rust.
### Constant objects vs variable objects
In the code example below, we are creating a new constant object called `age`.
This object stores a number representing the age of someone. However, this code example
does not compile successfully. Because on the next line of code, we are trying to change the value
of the object `age` to 25.
The `zig` compiler detects that we are trying to change
the value of an object/identifier that is constant, and because of that,
the compiler will raise a compilation error, warning us about the mistake.
```{zig}
#| eval: false
const age = 24;
// The line below is not valid!
age = 25;
```
```
t.zig:10:5: error: cannot assign to constant
age = 25;
~~^~~
```
In contrast, if you use `var`, then, the object created is a variable object.
With `var` you can declare this object in your source code, and then,
change the value of this object how many times you want over future points
in your source code.
So, using the same code example exposed above, if I change the declaration of the
`age` object to use the `var` keyword, then, the program gets compiled successfully.
Because now, the `zig` compiler detects that we are changing the value of an
object that allows this behaviour, because it's a "variable object".
```{zig}
#| build_type: "run"
#| auto_main: true
var age: u8 = 24;
age = 25;
```
### Declaring without an initial value
By default, when you declare a new object in Zig, you must give it
an initial value. In other words, this means
that we have to declare, and, at the same time, initialize every object we
create in our source code.
On the other hand, you can, in fact, declare a new object in your source code,
and not give it an explicit value. But we need to use a special keyword for that,
which is the `undefined` keyword.
It's important to emphasize that, you should avoid using `undefined` as much as possible.
Because when you use this keyword, you leave your object uninitialized, and, as a consequence,
if for some reason, your code uses this object while it's uninitialized, then, you will definitely
have undefined behaviour and major bugs in your program.
In the example below, I'm declaring the `age` object again. But this time,
I do not give it an initial value. The variable is only initialized at
the second line of code, where I store the number 25 in this object.
```{zig}
#| auto_main: true
#| build_type: "run"
var age: u8 = undefined;
age = 25;
```
Having these points in mind, just remember that you should avoid as much as possible to use `undefined` in your code.
Always declare and initialize your objects. Because this gives you much more safety in your program.
But in case you really need to declare an object without initializing it... the
`undefined` keyword is the way to do it in Zig.
### There is no such thing as unused objects
Every object (being constant or variable) that you declare in Zig **must be used in some way**. You can give this object
to a function call, as a function argument, or, you can use it in another expression
to calculate the value of another object, or, you can call a method that belongs to this
particular object.
It doesn't matter in which way you use it. As long as you use it.
If you try to break this rule, i.e., if your try to declare an object, but not use it,
the `zig` compiler will not compile your Zig source code, and it will issue a error
message warning that you have unused objects in your code.
Let's demonstrate this with an example. In the source code below, we declare a constant object
called `age`. If you try to compile a simple Zig program with this line of code below,
the compiler will return an error as demonstrated below:
```{zig}
#| build_type: "ast"
#| auto_main: false
const age = 15;
```
```
t.zig:4:11: error: unused local constant
const age = 15;
^~~
```
Everytime you declare a new object in Zig, you have two choices:
1. you either use the value of this object;
1. or you explicitly discard the value of the object;
To explicitly discard the value of any object (constant or variable), all you need to do is to assign
this object to a special character in Zig, which is the underscore (`_`).
When you assign an object to an underscore, like in the example below, the `zig` compiler will automatically
discard the value of this particular object.
You can see in the example below that, this time, the compiler did not
complain about any "unused constant", and successfully compiled our source code.
```{zig}
#| auto_main: true
#| build_type: "run"
// It compiles!
const age = 15;
_ = age;
```
Now, remember, everytime you assign a particular object to the underscore, this object
is essentially destroyed. It's discarded by the compiler. This means that you can no longer
use this object further in your code. It doesn't exist anymore.
So if you try to use the constant `age` in the example below, after we discarded it, you
will get a loud error message from the compiler (talking about a "pointless discard")
warning you about this mistake.
```{zig}
#| eval: false
// It does not compile.
const age = 15;
_ = age;
// Using a discarded value!
std.debug.print("{d}\n", .{age + 2});
```
```
t.zig:7:5: error: pointless discard
of local constant
```
This same rule applies to variable objects. Every variable object must also be used in
some way. And if you assign a variable object to the underscore,
this object also gets discarded, and you can no longer use this object.
### You must mutate every variable objects
Every variable object that you create in your source code must be mutated at some point.
In other words, if you declare an object as a variable
object, with the keyword `var`, and you do not change the value of this object
at some point in the future, the `zig` compiler will detect this,
and it will raise an error warning you about this mistake.
The concept behind this is that every object you create in Zig should be preferably a
constant object, unless you really need an object whose value will
change during the execution of your program.
So, if I try to declare a variable object such as `where_i_live` below,
and I do not change the value of this object in some way,
the `zig` compiler raises an error message with the phrase "variable is never mutated".
```{zig}
#| eval: false
var where_i_live = "Belo Horizonte";
_ = where_i_live;
```
```
t.zig:7:5: error: local variable is never mutated
t.zig:7:5: note: consider using 'const'
```
## Primitive Data Types {#sec-primitive-data-types}
Zig has many different primitive data types available for you to use.
You can see the full list of available data types at the official
[Language Reference page](https://ziglang.org/documentation/master/#Primitive-Types)[^lang-data-types].
[^lang-data-types]: <https://ziglang.org/documentation/master/#Primitive-Types>.
But here is a quick list:
- Unsigned integers: `u8`, 8-bit integer; `u16`, 16-bit integer; `u32`, 32-bit integer; `u64`, 64-bit integer; `u128`, 128-bit integer.
- Signed integers: `i8`, 8-bit integer; `i16`, 16-bit integer; `i32`, 32-bit integer; `i64`, 64-bit integer; `i128`, 128-bit integer.
- Float number: `f16`, 16-bit floating point; `f32`, 32-bit floating point; `f64`, 64-bit floating point; `f128`, 128-bit floating point;
- Boolean: `bool`, represents true or false values.
- C ABI compatible types: `c_long`, `c_char`, `c_short`, `c_ushort`, `c_int`, `c_uint`, and many others.
- Pointer sized integers: `isize` and `usize`.
## Arrays {#sec-arrays}
You create arrays in Zig by using a syntax that resembles the C syntax.
First, you specify the size of the array (i.e., the number of elements that will be stored in the array)
you want to create inside a pair of brackets.
Then, you specify the data type of the elements that will be stored inside this array.
All elements present in an array in Zig must have the same data type. For example, you cannot mix elements
of type `f32` with elements of type `i32` in the same array.
After that, you simply list the values that you want to store in this array inside
a pair of curly braces.
In the example below, I am creating two constant objects that contain different arrays.
The first object contains an array of 4 integer values, while the second object,
an array of 3 floating point values.
Now, you should notice that in the object `ls`, I am
not explicitly specifying the size of the array inside of the brackets. Instead
of using a literal value (like the value 4 that I used in the `ns` object), I am
using the special character underscore (`_`). This syntax tells the `zig` compiler
to fill this field with the number of elements listed inside of the curly braces.
So, this syntax `[_]` is for lazy (or smart) programmers who leave the job of
counting how many elements there are in the curly braces for the compiler.
```{zig}
#| auto_main: true
#| build_type: "run"
const ns = [4]u8{48, 24, 12, 6};
const ls = [_]f64{432.1, 87.2, 900.05};
_ = ns; _ = ls;
```
It's worth noting that these are static arrays, meaning that
they cannot grow in size.
Once you declare your array, you cannot change the size of it.
This is very common in low level languages.
Because low level languages normally wants to give you (the programmer) full control over memory,
and the way in which arrays are expanded is tightly related to
memory management.
### Selecting elements of the array {#sec-select-array-elem}
One very common activity is to select specific portions of an array
you have in your source code.
In Zig, you can select a specific element from your
array, by simply providing the index of this particular
element inside brackets after the object name.
In the example below, I am selecting the third element from the
`ns` array. Notice that Zig is a "zero-index" based language,
like C, C++, Rust, Python, and many other languages.
```{zig}
#| auto_main: true
#| build_type: "run"
const ns = [4]u8{48, 24, 12, 6};
try stdout.print("{d}\n", .{ ns[2] });
```
In contrast, you can also select specific slices (or sections) of your array, by using a
range selector. Some programmers also call these selectors of "slice selectors",
and they also exist in Rust, and have the exact same syntax as in Zig.
Anyway, a range selector is a special expression in Zig that defines
a range of indexes, and it have the syntax `start..end`.
In the example below, at the second line of code,
the `sl` object stores a slice (or a portion) of the
`ns` array. More precisely, the elements at index 1 and 2
in the `ns` array.
```{zig}
#| auto_main: true
#| build_type: "run"
const ns = [4]u8{48, 24, 12, 6};
const sl = ns[1..3];
_ = sl;
```
When you use the `start..end` syntax,
the "end tail" of the range selector is non-inclusive,
meaning that, the index at the end is not included in the range that is
selected from the array.
Therefore, the syntax `start..end` actually means `start..end - 1` in practice.
You can for example, create a slice that goes from the first to the
last elements of the array, by using `ar[0..ar.len]` syntax
In other words, it's a slice that
accesses all elements in the array.
```{zig}
#| auto_main: true
#| build_type: "run"
const ar = [4]u8{48, 24, 12, 6};
const sl = ar[0..ar.len];
_ = sl;
```
You can also use the syntax `start..` in your range selector.
Which tells the `zig` compiler to select the portion of the array
that begins at the `start` index until the last element of the array.
In the example below, we are selecting the range from index 1
until the end of the array.
```{zig}
#| auto_main: true
#| build_type: "run"
const ns = [4]u8{48, 24, 12, 6};
const sl = ns[1..];
_ = sl;
```
### More on slices
As we discussed before, in Zig, you can select specific portions of an existing
array. This is called *slicing* in Zig [@zigguide], because when you select a portion
of an array, you are creating a slice object from that array.
A slice object is essentially a pointer object accompanied by a length number.
The pointer object points to the first element in the slice, and the
length number tells the `zig` compiler how many elements there are in this slice.
> Slices can be thought of as a pair of `[*]T` (the pointer to the data) and a `usize` (the element count) [@zigguide].
Through the pointer contained inside the slice you can access the elements (or values)
that are inside this range (or portion) that you selected from the original array.
But the length number (which you can access through the `len` property of your slice object)
is the really big improvement (over C arrays for example) that Zig brings to the table here.
Because with this length number
the `zig` compiler can easily check if you are trying to access an index that is out of the bounds of this particular slice,
or, if you are causing any buffer overflow problems. In the example below,
we access the `len` property of the slice `sl`, which tells us that this slice
has 2 elements in it.
```{zig}
#| auto_main: true
#| build_type: "run"
const ns = [4]u8{48, 24, 12, 6};
const sl = ns[1..3];
try stdout.print("{d}\n", .{sl.len});
```
### Array operators
There are two array operators available in Zig that are very useful.
The array concatenation operator (`++`), and the array multiplication operator (`**`). As the name suggests,
these are array operators.
One important detail about these two operators is that they work
only when both operands have a size (or "length") that is compile-time known.
We are going to talk more about
the differences between "compile-time known" and "runtime known" in @sec-compile-time.
But for now, keep this information in mind, that you cannot use these operators in every situation.
In summary, the `++` operator creates a new array that is the concatenation,
of both arrays provided as operands. So, the expression `a ++ b` produces
a new array which contains all the elements from arrays `a` and `b`.
```{zig}
#| auto_main: true
#| build_type: "run"
const a = [_]u8{1,2,3};
const b = [_]u8{4,5};
const c = a ++ b;
try stdout.print("{any}\n", .{c});
```
This `++` operator is particularly useful to concatenate strings together.
Strings in Zig are described in depth in @sec-zig-strings. In summary, a string object in Zig