From 5e304d0c9b31cb6f1c0892c4afb8ded503d5d7c8 Mon Sep 17 00:00:00 2001 From: pedropark99 Date: Sun, 11 Aug 2024 22:55:27 -0300 Subject: [PATCH 1/3] Add content --- Chapters/01-zig-weird.qmd | 2 +- Chapters/03-structs.qmd | 129 ++++++- .../zig-basics/function_parameters_immu.zig | 12 + .../zig-basics/function_parameters_mmu.zig | 11 + .../01-zig-weird/execute-results/html.json | 8 +- .../03-structs/execute-results/html.json | 8 +- docs/Chapters/01-zig-weird.html | 8 +- docs/Chapters/03-structs.html | 350 ++++++++++-------- docs/Chapters/05-pointers.html | 2 +- docs/Chapters/09-data-structures.html | 2 +- docs/Chapters/10-stack-project.html | 2 +- docs/search.json | 34 +- 12 files changed, 366 insertions(+), 202 deletions(-) create mode 100644 ZigExamples/zig-basics/function_parameters_immu.zig create mode 100644 ZigExamples/zig-basics/function_parameters_mmu.zig diff --git a/Chapters/01-zig-weird.qmd b/Chapters/01-zig-weird.qmd index 262c279..d5ec27c 100644 --- a/Chapters/01-zig-weird.qmd +++ b/Chapters/01-zig-weird.qmd @@ -730,7 +730,7 @@ t.zig:7:5: error: local variable is never mutated t.zig:7:5: note: consider using 'const' ``` -## Primitive Data Types +## Primitive Data Types {#sec-primitive-data-types} 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 diff --git a/Chapters/03-structs.qmd b/Chapters/03-structs.qmd index 73dadf2..a36cc0e 100644 --- a/Chapters/03-structs.qmd +++ b/Chapters/03-structs.qmd @@ -434,6 +434,90 @@ for (ns) |i| { ``` + +## Function parameters are immutable {#sec-fun-pars} + +We have already discussed a lot of the syntax behind function declarations at @sec-root-file and @sec-main-file. +But I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig. +In summary, function parameters are immutable in Zig. + +Take the code example below, where we declare a simple function that just tries to add +some amount to the input integer, and returns the result back. But if you look closely +at the body of this `add2()` function, you will notice that we try +to save the result back into the `x` function argument. + +In other words, this function not only use the value that it received through the function argument +`x`, but it also tries to change the value of this function argument, by assigning the addition result +into `x`. However, function arguments in Zig are immutable. You cannot change their values, or, you +cannot assign values to them inside the body's function. + +This is the reason why, the code example below do not compile successfully. If you try to compile +this code example, you get a compile error warning you that you are trying to change the value of a +immutable (i.e. constant) object. + +```{zig} +#| eval: false +const std = @import("std"); +fn add2(x: u32) u32 { + x = x + 2; + return x; +} + +pub fn main() !void { + const y = add2(4); + std.debug.print("{d}\n", .{y}); +} +``` + +``` +t.zig:3:5: error: cannot assign to constant + x = x + 2; + ^ +``` + + +If a function argument receives as input a object whose data type is +any of the primitive types that we have listed at @sec-primitive-data-types +this object is always passed by value to the function. In other words, this object +is copied to the function stack frame. + +However, if the input object have a more complex data type, for example, it might +be a struct instance, or an array, or a union, etc., in cases like that, the `zig` compiler +will take the liberty of deciding for you which strategy is best. The `zig` compiler will +pass your object to the function either by value, or by reference. The compiler will always +choose the strategy that is faster for you. +This optimization that you get for free is possible only because function arguments are +immutable in Zig. + +To overcome this barrier, we need to take the lead, and explicitly choose to pass the +object by reference. That is, instead of depending on the `zig` compiler to decide for us, we need +to explicitly mark the function argument as a pointer. This way, we are telling the compiler +that this function argument will be passed by reference to the function. + +By making it a pointer, we can finally use and alter directly the value of this function argument inside +the body of the `add2()` function. You can see that the code example below compiles successfully. + +```{zig} +#| auto_main: false +const std = @import("std"); +fn add2(x: *u32) void { + const d: u32 = 2; + x.* = x.* + d; +} + +pub fn main() !void { + var x: u32 = 4; + add2(&x); + std.debug.print("Result: {d}\n", .{x}); +} +``` + +``` +Result: 6 +``` + + + ## Structs and OOP {#sec-structs-and-oop} Zig is a language more closely related to C (which is a procedural language), @@ -599,7 +683,7 @@ const Vec3 = struct { ``` -### The `self` method argument +### The `self` method argument {#sec-self-arg} In every language that have OOP, when we declare a method of some class or struct, we usually declare this method as a function that have a `self` argument. @@ -672,17 +756,17 @@ Distance: 3.3970575502926055 Sometimes you don't need to care about the state of your struct object. Sometimes, you just need to instantiate and use the objects, without altering their state. You can notice that when you have methods -inside this struct object that might use the values that are present the data members, but they -do not alter the values in the data members of structs in anyway. +inside your struct declaration that might use the values that are present in the data members, but they +do not alter the values in the data members of the struct in anyway. -The `Vec3` struct that we presented in the previous section is an example of that. +The `Vec3` struct that was presented at @sec-self-arg is an example of that. This struct have a single method named `distance()`, and this method do use the values present in all three data members of the struct (`x`, `y` and `z`). But at the same time, this method do not change the values of these data members in any point. As a result of that, when we create `Vec3` objects we usually create them as -constant objects, like the `v1` and `v2` objects presented in the previous -code example. We can create them as variable objects with the `var` keyword, +constant objects, like the `v1` and `v2` objects presented at @sec-self-arg. +We can create them as variable objects with the `var` keyword, if we want to. But because the methods of this `Vec3` struct do not change the state of the objects in any point, is unnecessary to mark them as variable objects. @@ -694,7 +778,7 @@ More specifically, when you have a method in a struct that changes the state of the object (i.e. change the value of a data member), the `self` argument in this method must be annotated in a different manner. -As I described in the previous section, the `self` argument in methods of +As I described at @sec-self-arg, the `self` argument in methods of a struct is the argument that receives as input the object from which the method was called from. We usually annotate this argument in the methods by writing `self`, followed by the colon character (`:`), and the data type of the struct to which @@ -707,7 +791,7 @@ method. But what if we do have a method that alters the state of the object, by altering the values of it's data members. How should we annotate `self` in this instance? The answer is: -"we should pass a pointer of `x` to `self`, and not simply a copy of `x` to `self`". +"we should annotate `self` as a pointer of `x`, instead of just `x`". In other words, you should annotate `self` as `self: *x`, instead of annotating it as `self: x`. @@ -746,7 +830,7 @@ to our `Vec3` struct named `double()`. This method essentially doubles the coordinate values of our vector object. Also notice that, in the case of the `double()` method, we annotated the `self` argument as `*Vec3`, indicating that this argument receives a pointer (or a reference, if you prefer to call it this way) -to a `Vec3` object, instead of receiving a copy of the object directly, as input. +to a `Vec3` object as input. ```{zig} #| eval: false @@ -763,8 +847,8 @@ Doubled: 8.4 -If you change the `self` argument in this `double()` method to `self: Vec3`, like in the -`distance()` method, you will get the error exposed below as result. Notice that this +Now, if you change the `self` argument in this `double()` method to `self: Vec3`, like in the +`distance()` method, you will get the compiler error exposed below as result. Notice that this error message is indicating a line from the `double()` method body, indicating that you cannot alter the value of the `x` data member. @@ -774,10 +858,8 @@ indicating that you cannot alter the value of the `x` data member. ``` This error message indicates that the `x` data member belongs to a constant object, -and, because of that, it cannot be changed. Even though we marked the `v3` object -that we have created in the previous code example as a variable object. -So even though this `x` data member belongs to a variable object in our code, this error -message is pointing to the opposite direction. +and, because of that, it cannot be changed. Ultimately, this error message +is telling us that the `self` argument is constant. ``` t.zig:16:13: error: cannot assign to constant @@ -785,9 +867,16 @@ t.zig:16:13: error: cannot assign to constant ~~~~^~ ``` -But this error message is misleading, because the only thing that we have changed -is the `self` argument signature from `*Vec3` to `Vec3` in the `double()` method. So, just remember this -general rule below about your method declarations: +If you take some time, and think hard about this error message, you will understand it. +You already have the tools to understand why we are getting this error message. +We have talked about it already at @sec-fun-pars. + +So remember, every function argument is immutable in Zig. +It does not matter if we marked the `v3` object as a variable object. +Because we are trying to alter `self` directly, which is a function argument, +and, every function argument is immutable by default. +We overcome this barrier, by explicitly marking the `self` argument as a pointer. + ::: {.callout-note} If a method of your `x` struct alters the state of the object, by @@ -795,10 +884,10 @@ changing the value of any data member, then, remember to use `self: *x`, instead of `self: x` in the function signature of this method. ::: + You could also interpret the content discussed in this section as: "if you need to alter the state of your `x` struct object in one of it's methods, -you must pass the `x` struct object by reference to the `self` argument of this method, -instead of passing it by value". +you must explicitly pass the `x` struct object by reference to the `self` argument of this method". diff --git a/ZigExamples/zig-basics/function_parameters_immu.zig b/ZigExamples/zig-basics/function_parameters_immu.zig new file mode 100644 index 0000000..04d6819 --- /dev/null +++ b/ZigExamples/zig-basics/function_parameters_immu.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +// This code does not compile because we are trying to +// change the value of a function parameter. +fn add2(x: u32) u32 { + x = x + 2; + return x; +} + +pub fn main() !void { + const y = add2(4); + std.debug.print("{d}\n", .{y}); +} diff --git a/ZigExamples/zig-basics/function_parameters_mmu.zig b/ZigExamples/zig-basics/function_parameters_mmu.zig new file mode 100644 index 0000000..67b069e --- /dev/null +++ b/ZigExamples/zig-basics/function_parameters_mmu.zig @@ -0,0 +1,11 @@ +const std = @import("std"); +fn add2(x: *u32) void { + const d: u32 = 2; + x.* = x.* + d; +} + +pub fn main() !void { + var x: u32 = 4; + add2(&x); + std.debug.print("{d}\n", .{x}); +} diff --git a/_freeze/Chapters/01-zig-weird/execute-results/html.json b/_freeze/Chapters/01-zig-weird/execute-results/html.json index f455245..6551d30 100644 --- a/_freeze/Chapters/01-zig-weird/execute-results/html.json +++ b/_freeze/Chapters/01-zig-weird/execute-results/html.json @@ -1,9 +1,11 @@ { - "hash": "ce3149df2be73e42076f6459c4fb9ba8", + "hash": "0005d4425101611fca139bfff9664361", "result": { "engine": "knitr", - "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n# Introducing Zig\n\nIn this chapter, I want to introduce you to the world of Zig.\nDespite it's rapidly growing over the last years, Zig is, still, a very young language^[New programming languages in general, take years and years to be developed.].\nAs a consequence, it's world is still very wild and to be explored.\nThis book is my attempt to help you on your personal journey for\nunderstanding and exploring the exciting world of Zig.\n\nI assume you have previous experience with some programming\nlanguage in this book, not necessarily with a low-level one.\nSo, if you have experience with Python, or Javascript, for example, is fine.\nBut, if you do have experience with low-level languages, such as C, C++, or\nRust, you will probably learn faster throughout this book.\n\n\n\n## What is Zig?\n\nZig is a modern, low-level, and general-purpose programming language. Some programmers interpret\nZig as the \"modern C language\". It is a simple language like C, but with some\nmodern features.\n\nIn the author's personal interpretation, Zig is tightly connected with \"less is more\".\nInstead of trying to become a modern language by adding more and more features,\nmany of the core improvements that Zig brings to the\ntable are actually about removing annoying and evil behaviours/features from C and C++.\nIn other words, Zig tries to be better by simplifying the language, and by having more consistent and robust behaviour.\nAs a result, analyzing, writing and debugging applications become much easier and simpler in Zig, than it is in C or C++.\n\nThis philosophy becomes clear with the following phrase from the official website of Zig:\n\n> \"Focus on debugging your application rather than debugging your programming language knowledge\".\n\nThis phrase is specially true for C++ programmers. Because C++ is a gigantic language,\nwith tons of features, and also, there are lots of different \"flavors of C++\". These elements\nare what makes C++ so much complex and hard to learn. Zig tries to go in the opposite direction.\nZig is a very simple language, more closely related to other simple languages such as C and Go.\n\nThe phrase above is still important for C programmers too. Because, even C being a simple\nlanguage, it is still hard sometimes to read and understand C code. For example, pre-processor macros in\nC are an evil source of confusion. They really makes it hard sometimes to debug\nC programs. Because macros are essentially a second language embedded in C that obscures\nyour C code. With macros, you are no longer 100% sure about which pieces\nof code are being sent to the compiler. It obscures the actual source code that you wrote.\n\nYou don't have macros in Zig. In Zig, the code you write, is the actual code that get's compiled by the compiler.\nYou don't have evil features that obscures you code.\nYou also don't have hidden control flow happening behind the scenes. And, you also\ndon't have functions or operators from the standard library that make\nhidden memory allocations behind your back.\n\nBy being a simpler language, Zig becomes much more clear and easier to read/write,\nbut at the same time, it also achieves a much more robust state, with more consistent\nbehaviour in edge situations. Once again, less is more.\n\n\n## Hello world in Zig\n\nWe begin our journey in Zig by creating a small \"Hello World\" program.\nTo start a new Zig project in your computer, you simply call the `init` command\nfrom the `zig` compiler.\nJust create a new directory in your computer, then, init a new Zig project\ninside this directory, like this:\n\n```bash\nmkdir hello_world\ncd hello_world\nzig init\n```\n\n```\ninfo: created build.zig\ninfo: created build.zig.zon\ninfo: created src/main.zig\ninfo: created src/root.zig\ninfo: see `zig build --help` for a menu of options\n```\n\n### Understanding the project files {#sec-project-files}\n\nAfter you run the `init` command from the `zig` compiler, some new files\nare created inside of your current directory. First, a \"source\" (`src`) directory\nis created, containing two files, `main.zig` and `root.zig`. Each `.zig` file\nis a separate Zig module, which is simply a text file that contains some Zig code.\n\n\nThe `main.zig` file for example, contains a `main()` function, which represents\nthe entrypoint of your program. It is where the execution of your program begins.\nAs you would expect from a C, C++, Rust or Go,\nto build an executabe program in Zig, you also need to declare a `main()` function in your module.\nSo, the `main.zig` module represents an executable program written in Zig.\n\nOn the other side, the `root.zig` module does not contain a `main()` function. Because\nit represents a library written in Zig. Libraries are different than executables.\nThey don't need to have an entrypoint to work.\nSo, you can choose which file (`main.zig` or `root.zig`) you want to follow depending on which type\nof project (executable or library) you want to develop.\n\n```bash\ntree .\n```\n\n```\n.\n├── build.zig\n├── build.zig.zon\n└── src\n ├── main.zig\n └── root.zig\n\n1 directory, 4 files\n```\n\n\nNow, in addition to the source directory, two other files were created in our working directory:\n`build.zig` and `build.zig.zon`. The first file (`build.zig`) represents a build script written in Zig.\nThis script is executed when you call the `build` command from the `zig` compiler.\nIn other words, this file contain Zig code that executes the necessary steps to build the entire project.\n\nIn general, low-level languages normally use a compiler to build your\nsource code into binary executables or binary libraries.\nNevertheless, this process of compiling your source code and building\nbinary executables or binary libraries from it, became a real challenge\nin the programming world, once the projects became bigger and bigger.\nAs a result, programmers created \"build systems\", which are a second set of tools designed to make this process\nof compiling and building complex projects, easier.\n\nExamples of build systems are CMake, GNU Make, GNU Autoconf and Ninja,\nwhich are used to build complex C and C++ projects.\nWith these systems, you can write scripts, which are called \"build scripts\".\nThey simply are scripts that describes the necessary steps to compile/build\nyour project.\n\nHowever, these are separate tools, that do not\nbelong to C/C++ compilers, like `gcc` or `clang`.\nAs a result, in C/C++ projects, you have not only to install and\nmanage your C/C++ compilers, but you also have to install and manage\nthese build systems separately.\n\nBut instead of using a separate build system, in Zig, we use the\nZig language itself to write build scripts.\nIn other words, Zig contains a native build system in it. And\nwe can use this build system to write small scripts in Zig,\nwhich describes the necessary steps to build/compile our Zig project[^zig-build-system].\nSo, everything you need to build a complex Zig project is the\n`zig` compiler, and nothing more.\n\n[^zig-build-system]: .\n\n\nNow that we described this topic in more depth, let's focus\non the second generated file (`build.zig.zon`), which is the Zig package manager configuration file,\nwhere you can list and manage the dependencies of your project. Yes, Zig have\na package manager (like `pip` in Python, `cargo` in Rust, or `npm` in Javascript) called Zon,\nand this `build.zig.zon` file is similar to the `package.json` file\nin Javascript projects, or, the `Pipfile` in Python projects.\n\n\n### Looking at the `root.zig` file {#sec-root-file}\n\nLet's take a look at the `root.zig` file, and start to analyze some of the\nsyntax of Zig.\nThe first thing that you might notice, is that every line of code\nthat have an expression in it, ends with a semicolon character (`;`). This is\nsimilar syntax to other languages such as C, C++ and Rust,\nwhich have the same rule.\n\nAlso, notice the `@import()` call at the first line. We use this built-in function\nto import functionality from other Zig modules into our current module.\nIn other words, the `@import()` function works similarly to the `#include` pre-processor\nin C or C++, or, to the `import` statement in Python or Javascript code.\nIn this example, we are importing the `std` module,\nwhich gives you access to the Zig standard library.\n\nIn this `root.zig` file, we can also see how assignments (i.e. creating new objects)\nare made in Zig. You can create a new object in Zig by using the following syntax\n`(const|var) name = value;`. In the example below, we are creating two constant\nobjects (`std` and `testing`). At @sec-assignments we talk more about objects in general.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst testing = std.testing;\n\nexport fn add(a: i32, b: i32) i32 {\n return a + b;\n}\n```\n:::\n\n\nFunctions in Zig are declared similarly to functions in Rust, using the `fn` keyword. In the example above,\nwe are declaring a function called `add()`, which have two arguments named `a` and `b`, and returns\na integer number (`i32`) as result.\n\nMaybe Zig is not exactly a strongly-typed language, because you do not need\nnecessarily to specify the type of every single object you create across your source code.\nBut you do have to explicitly specify the type of every function argument, and also,\nthe return type of every function you create in Zig. So, at least in function declarations,\nZig is a strongly-typed language.\n\nWe specify the type of an object or a function argument in Zig, by\nusing a colon character (`:`) followed by the type after the name of this object/function argument.\nWith the expressions `a: i32` and `b: i32`, we know that, both `a` and `b` arguments have type `i32`,\nwhich is a signed 32 bit integer. In this part,\nthe syntax in Zig is identical to the syntax in Rust, which also specifies types by\nusing the colon character.\n\nLastly, we have the return type of the function at the end of the line, before we open\nthe curly braces to start writing the function's body, which, in the example above is\nagain a signed 32 bit integer (`i32`) value. This specific part is different than it is in Rust.\nBecause in Rust, the return type of a function is specified after an arrow (`->`).\nWhile in Zig, we simply declare the return type directly after the parentheses with the function arguments.\n\nWe also have an `export` keyword before the function declaration. This keyword\nis similar to the `extern` keyword in C. It exposes the function\nto make it available in the library API.\n\nIn other words, if you have a project where you are currently building\na library for other people to use, you need to expose your functions\nso that they are available in the library's API, so that users can use it.\nIf we removed the `export` keyword from the `add()` function declaration,\nthen, this function would be no longer exposed in the library object built\nby the `zig` compiler.\n\n\nHaving that in mind, the keyword `export` is a keyword used in libraries written in Zig.\nSo, if you are not currently writing a library in your project, then, you do not need to\ncare about this keyword.\n\n\n### Looking at the `main.zig` file {#sec-main-file}\n\nNow that we have learned a lot about Zig's syntax from the `root.zig` file,\nlet's take a look at the `main.zig` file.\nA lot of the elements we saw in `root.zig` are also present in `main.zig`.\nBut we have some other elements that we did not have seen yet, so let's dive in.\n\nFirst, look at the return type of the `main()` function in this file.\nWe can see a small change. Now, the return\ntype of the function (`void`) is accompanied by an exclamation mark (`!`).\nWhat this exclamation mark is telling us, is that this `main()` function\nmight also return an error.\n\nSo, in this example, the `main()` function can either return `void`, or, return an error.\nThis is an interesting feature of Zig. If you write a function, and, something inside of\nthe body of this function might return an error, then, you are forced to:\n\n- either add the exclamation mark to the return type of the function, to make it clear that\nthis function might return an error.\n- or explicitly handle this error that might occur inside the function, to make sure that,\nif this error does happen, you are prepared, and your function will no longer return an error\nbecause you handled the error inside your function.\n\nIn most programming languages, we normally handle (or deals with) an error through\na *try catch* pattern, and Zig, this is no different. But, if we look at the `main()` function\nbelow, you can see that we do have a `try` keyword in the 5th line. But we do not have a `catch` keyword\nin this code.\n\nThis means that, we are using the keyword `try` to execute a code that might return an error,\nwhich is the `stdout.print()` expression. But because we do not have a `catch` keyword in this line,\nwe are not treating (or dealing with) this error.\nSo, if this expression do return an error, we are not catching and solving this error in any way.\nThat is why the exclamation mark was added to the return type of the function.\n\nSo, in essence, the `try` keyword executes the expression `stdout.print()`. If this expression\nreturns a valid value, then, the `try` keyword do nothing essentially. It simply passes this value forward. But, if the expression do\nreturn an error, then, the `try` keyword will unwrap and return this error from the function, and also print it's\nstack trace to `stderr`.\n\nThis might sound weird to you, if you come from a high-level language. Because in\nhigh-level languages, such as Python, if an error occurs somewhere, this error is automatically\nreturned and the execution of your program will automatically stops, even if you don't want\nto stop the execution. You are obligated to face the error.\n\nBut if you come from a low-level language, then, maybe, this idea do not sound so weird or distant to you.\nBecause in C for example, normally functions doesn't raise errors, or, they normally don't stop the execution.\nIn C, error handling\nis done by constantly checking the return value of the function. So, you run the function,\nand then, you use an if statement to check if the function returned a value that is valid,\nor, if it returned an error. If an error was returned from the function, then, the if statement\nwill execute some code that fixes this error.\n\nSo, at least for C programmers, they do need to write a lot of if statements to\nconstantly check for errors around their code. And because of that, this simple feature from Zig, might be\nextraordinary for them. Because this `try` keyword can automatically unwrap the error,\nand warn you about this error, and let you deal with it, without any extra work from the programmer.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\n\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n```\n:::\n\n\nNow, another thing that you might have noticed in this code example, is that\nthe `main()` function is marked with the `pub` keyword. This keyword means\n\"public\". It marks the `main()` function as a *public function* from this module.\n\nIn other words, every function that you declare in your Zig module is, by default, a private (or \"static\")\nfunction that belongs to this Zig module, and can only be used (or called) from within this same module.\nUnless, you explicitly mark this function as a public function with the `pub` keyword.\nThis means that the `pub` keyword in Zig do essentially the opposite of what the `static` keyword\ndo in C/C++.\n\nBy making a function \"public\", you allow other Zig modules to access and call this function,\nand use it for they own purposes.\nall these other Zig modules need to do is, to import your module with the `@import()`\nbuilt-in function. Then, they get access to all public functions that are present in\nyour Zig module.\n\n\n### Compiling your source code {#sec-compile-code}\n\nYou can compile your Zig modules into a binary executable by running the `build-exe` command\nfrom the `zig` compiler. You simply list all the Zig modules that you want to build after\nthe `build-exe` command, separated by spaces. In the example below, we are compiling the module `main.zig`.\n\n```bash\nzig build-exe src/main.zig\n```\n\nSince we are building an executable, the `zig` compiler will look for a `main()` function\ndeclared in any of the files that you list after the `build-exe` command. If\nthe compiler does not find a `main()` function declared somewhere, a\ncompilation error will be raised, warning about this mistake.\n\nThe `zig` compiler also offers a `build-lib` and `build-obj` commands, which work\nthe exact same way as the `build-exe` command. The only difference is that, they compile your\nZig modules into a portale C ABI library, or, into object files, respectively.\n\nIn the case of the `build-exe` command, a binary executable file is created by the `zig`\ncompiler in the root directory of your project.\nIf we take a look now at the contents of our current directory, with a simple `ls` command, we can\nsee the binary file called `main` that was created by the compiler.\n\n```bash\nls\n```\n\n```\nbuild.zig build.zig.zon main src\n```\n\nIf I execute this binary executable, I get the \"Hello World\" message in the terminal\n, as we expected.\n\n```bash\n./main\n```\n\n```\nHello, world!\n```\n\n\n### Compile and execute at the same time {#sec-compile-run-code}\n\nOn the previous section, I presented the `zig build-exe` command, which\ncompiles Zig modules into an executable file. However, this means that,\nin order to execute the executable file, we have to run two different commands.\nFirst, the `zig build-exe` command, and then, we call the executable file\ncreated by the compiler.\n\nBut what if we wanted to perform these two steps,\nall at once, in a single command? We can do that by using the `zig run`\ncommand.\n\n```bash\nzig run src/main.zig\n```\n\n```\nHello, world!\n```\n\n### Compiling the entire project {#sec-compile-project}\n\nJust as I described at @sec-project-files, as our project grows in size and\ncomplexity, we usually prefer to organize the compilation and build process\nof the project into a build script, using some sort of \"build system\".\n\nIn other words, as our project grows in size and complexity,\nthe `build-exe`, `build-lib` and `build-obj` commands become\nharder to use directly. Because then, we start to list\nmultiple and multiple modules at the same time. We also\nstart to add built-in compilation flags to customize the\nbuild process for our needs, etc. It becomes a lot of work\nto write the necessary commands by hand.\n\nIn C/C++ projects, programmers normally opt to use CMake, Ninja, `Makefile` or `configure` scripts\nto organize this process. However, in Zig, we have a native build system in the language itself.\nSo, we can write build scripts in Zig to compile and build Zig projects. Then, all we\nneed to do, is to call the `zig build` command to build our project.\n\nSo, when you execute the `zig build` command, the `zig` compiler will search\nfor a Zig module named `build.zig` inside your current directory, which\nshould be your build script, containing the necessary code to compile and\nbuild your project. If the compiler do find this `build.zig` file in your directory,\nthen, the compiler will essentially execute a `zig run` command\nover this `build.zig` file, to compile and execute this build\nscript, which in turn, will compile and build your entire project.\n\n\n```bash\nzig build\n```\n\n\nAfter you execute this \"build project\" command, a `zig-out` directory\nis created in the root of your project directory, where you can find\nthe binary executables and libraries created from your Zig modules\naccordingly to the build commands that you specified at `build.zig`.\nWe will talk more about the build system in Zig latter in this book.\n\nIn the example below, I'm executing the binary executable\nnamed `hello_world` that was generated by the compiler after the\n`zig build` command.\n\n```bash\n./zig-out/bin/hello_world\n```\n\n```\nHello, world!\n```\n\n\n\n## How to learn Zig?\n\nWhat are the best strategies to learn Zig? \nFirst of all, of course this book will help you a lot on your journey through Zig.\nBut you will also need some extra resources if you want to be really good at Zig.\n\nAs a first tip, you can join a community with Zig programmers to get some help\n, when you need it:\n\n- Reddit forum: ;\n- Ziggit community: ;\n- Discord, Slack, Telegram, and others: ;\n\nNow, one of the best ways to learn Zig is to simply read Zig code. Try\nto read Zig code often, and things will become more clear.\nA C/C++ programmer would also probably give you this same tip.\nBecause this strategy really works!\n\nNow, where you can find Zig code to read?\nI personally think that, the best way of reading Zig code is to read the source code of the\nZig 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\nthe official GitHub repository of Zig. Access this folder, and start exploring the Zig modules.\n\nAlso, a great alternative is to read code from other large Zig\ncodebases, such as:\n\n1. the [Javascript runtime Bun](https://github.com/oven-sh/bun)[^bunjs].\n1. the [game engine Mach](https://github.com/hexops/mach)[^mach].\n1. a [LLama 2 LLM model implementation in Zig](https://github.com/cgbur/llama2.zig/tree/main)[^ll2].\n1. the [financial transactions database `tigerbeetle`](https://github.com/tigerbeetle/tigerbeetle)[^tiger].\n1. the [command-line arguments parser `zig-clap`](https://github.com/Hejsil/zig-clap)[^clap].\n1. the [UI framework `capy`](https://github.com/capy-ui/capy)[^capy].\n1. the [Language Protocol implementation for Zig, `zls`](https://github.com/zigtools/zls)[^zls].\n1. the [event-loop library `libxev`](https://github.com/mitchellh/libxev)[^xev].\n\n[^xev]: \n[^zls]: \n[^capy]: \n[^clap]: \n[^tiger]: \n[^ll2]: \n[^mach]: \n[^bunjs]: .\n\nAll these assets are available on GitHub,\nand this is great, because we can use the GitHub search bar in our advantage,\nto find Zig code that fits our description.\nFor example, you can always include `lang:Zig` in the GitHub search bar when you\nare searching for a particular pattern. This will limit the search to only Zig modules.\n\n[^zig-lib-std]: \n\nAlso, a great alternative is to consult online resources and documentations.\nHere is a quick list of resources that I personally use from time to time to learn\nmore about the language each day:\n\n- Zig Language Reference: ;\n- Zig Standard Library Reference: ;\n- Zig Guide: ;\n- Karl Seguin Blog: ;\n- Zig News: ;\n- Read the code written by one of the Zig core team members: ;\n- Some livecoding sessions are transmitted in the Zig Showtime Youtube Channel: ;\n\n\nAnother great strategy to learn Zig, or honestly, to learn any language you want,\nis to practice it by solving exercises. For example, there is a famous repository\nin the Zig community called [Ziglings](https://codeberg.org/ziglings/exercises/)[^ziglings]\n, which contains more than 100 small exercises that you can solve. It is a repository of\ntiny programs written in Zig that are currently broken, and your responsibility is to\nfix these programs, and make them work again.\n\n[^ziglings]: .\n\nA famous tech YouTuber known as *The Primeagen* also posted some videos (at YouTube)\nwhere he solves these exercises from Ziglings. The first video is named\n[\"Trying Zig Part 1\"](https://www.youtube.com/watch?v=OPuztQfM3Fg&t=2524s&ab_channel=TheVimeagen)[^prime1].\n\n[^prime1]: .\n\nAnother great alternative, is to solve the [Advent of Code exercises](https://adventofcode.com/)[^advent-code].\nThere are people that already took the time to learn and solve the exercises, and they posted\ntheir solutions on GitHub as well, so, in case you need some resource to compare while solving\nthe exercises, you can look at these two repositories:\n\n- ;\n- ;\n\n[^advent-code]: \n\n\n\n\n\n\n## Creating new objects in Zig (i.e. identifiers) {#sec-assignments}\n\nLet's talk more about objects in Zig. Readers that have past experience\nwith other programming languages might know this concept through\na different name, such as: \"variable\" or \"identifier\". In this book, I choose\nto use the term \"object\" to refer to this concept.\n\nTo create a new object (or a new \"identifier\") in Zig, we use\nthe keywords `const` or `var`. These keywords specificy if the object\nthat you are creating is mutable or not.\nIf you use `const`, then the object you are\ncreating is a constant (or immutable) object, which means that once you declare this object, you\ncan no longer change the value stored inside this object.\n\nOn the other side, if you use `var`, then, you are creating a variable (or mutable) object.\nYou can change the value of this object as many times you want. Using the\nkeyword `var` in Zig is similar to using the keywords `let mut` in Rust.\n\n### Constant objects vs variable objects\n\nIn the code example below, we are creating a new constant object called `age`.\nThis object stores a number representing the age of someone. However, this code example\ndoes not compiles succesfully. Because on the next line of code, we are trying to change the value\nof the object `age` to 25.\n\nThe `zig` compiler detects that we are trying to change\nthe value of an object/identifier that is constant, and because of that,\nthe compiler will raise a compilation error, warning us about the mistake.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst age = 24;\n// The line below is not valid!\nage = 25;\n```\n:::\n\n\n```\nt.zig:10:5: error: cannot assign to constant\n age = 25;\n ~~^~~\n```\n\nIn contrast, if you use `var`, then, the object created is a variable object.\nWith `var` you can declare this object in your source code, and then,\nchange the value of this object how many times you want over future points\nin your source code.\n\nSo, using the same code example exposed above, if I change the declaration of the\n`age` object to use the `var` keyword, then, the program gets compiled succesfully.\nBecause now, the `zig` compiler detects that we are changing the value of an\nobject that allows this behaviour, because it is an \"variable object\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar age: u8 = 24;\nage = 25;\n```\n:::\n\n\n\n### Declaring without an initial value\n\nBy default, when you declare a new object in Zig, you must give it\nan initial value. In other words, this means\nthat we have to declare, and, at the same time, initialize every object we\ncreate in our source code.\n\nOn the other hand, you can, in fact, declare a new object in your source code,\nand not give it an explicit value. But we need to use a special keyword for that,\nwhich is the `undefined` keyword.\n\nIs important to emphasize that, you should avoid using `undefined` as much as possible.\nBecause when you use this keyword, you leave your object uninitialized, and, as a consequence,\nif for some reason, your code use this object while it is uninitialized, then, you will definitely\nhave undefined behaviour and major bugs in your program.\n\nIn the example below, I'm declaring the `age` object again. But this time,\nI do not give it an initial value. The variable is only initialized at\nthe second line of code, where I store the number 25 in this object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar age: u8 = undefined;\nage = 25;\n```\n:::\n\n\nHaving these points in mind, just remember that you should avoid as much as possible to use `undefined` in your code.\nAlways declare and initialize your objects. Because this gives you much more safety in your program.\nBut in case you really need to declare an object without initializing it... the\n`undefined` keyword is the way to do it in Zig.\n\n\n### There is no such thing as unused objects\n\nEvery object (being constant or variable) that you declare in Zig **must be used in some way**. You can give this object\nto a function call, as a function argument, or, you can use it in another expression\nto calculate the value of another object, or, you can call a method that belongs to this\nparticular object. \n\nIt doesn't matter in which way you use it. As long as you use it.\nIf you try to break this rule, i.e. if your try to declare a object, but not use it,\nthe `zig` compiler will not compile your Zig source code, and it will issue a error\nmessage warning that you have unused objects in your code.\n\nLet's demonstrate this with an example. In the source code below, we declare a constant object\ncalled `age`. If you try to compile a simple Zig program with this line of code below,\nthe compiler will return an error as demonstrated below:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst age = 15;\n```\n:::\n\n\n```\nt.zig:4:11: error: unused local constant\n const age = 15;\n ^~~\n```\n\nEverytime you declare a new object in Zig, you have two choices:\n\n1. you either use the value of this object;\n2. or you explicitly discard the value of the object;\n\nTo explicitly discard the value of any object (constant or variable), all you need to do is to assign\nthis object to an special character in Zig, which is the underscore (`_`).\nWhen you assign an object to a underscore, like in the example below, the `zig` compiler will automatically\ndiscard the value of this particular object.\n\nYou can see in the example below that, this time, the compiler did not\ncomplain about any \"unused constant\", and succesfully compiled our source code.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\n// It compiles!\nconst age = 15;\n_ = age;\n```\n:::\n\n\nNow, remember, everytime you assign a particular object to the underscore, this object\nis essentially destroyed. It is discarded by the compiler. This means that you can no longer\nuse this object further in your code. It doesn't exist anymore.\n\nSo if you try to use the constant `age` in the example below, after we discarded it, you\nwill get a loud error message from the compiler (talking about a \"pointless discard\")\nwarning you about this mistake.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\n// It does not compile.\nconst age = 15;\n_ = age;\n// Using a discarded value!\nstd.debug.print(\"{d}\\n\", .{age + 2});\n```\n:::\n\n\n```\nt.zig:7:5: error: pointless discard\n of local constant\n```\n\n\nThis same rule applies to variable objects. Every variable object must also be used in\nsome way. And if you assign a variable object to the underscore,\nthis object also get's discarded, and you can no longer use this object.\n\n\n\n### You must mutate every variable objects\n\nEvery variable object that you create in your source code must be mutated at some point.\nIn other words, if you declare an object as a variable\nobject, with the keyword `var`, and you do not change the value of this object\nat some point in the future, the `zig` compiler will detect this,\nand it will raise an error warning you about this mistake.\n\nThe concept behind this is that every object you create in Zig should be preferably a\nconstant object, unless you really need an object whose value will\nchange during the execution of your program.\n\nSo, if I try to declare a variable object such as `where_i_live` below,\nand I do not change the value of this object in some way,\nthe `zig` compiler raises an error message with the phrase \"variable is never mutated\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar where_i_live = \"Belo Horizonte\";\n_ = where_i_live;\n```\n:::\n\n\n```\nt.zig:7:5: error: local variable is never mutated\nt.zig:7:5: note: consider using 'const'\n```\n\n## Primitive Data Types\n\nZig have many different primitive data types available for you to use.\nYou can see the full list of available data types at the official\n[Language Reference page](https://ziglang.org/documentation/master/#Primitive-Types)[^lang-data-types].\n\n[^lang-data-types]: .\n\nBut here is a quick list:\n\n- Unsigned integers: `u8`, 8-bit integer; `u16`, 16-bit integer; `u32`, 32-bit integer; `u64`, 64-bit integer; `u128`, 128-bit integer.\n- Signed integers: `i8`, 8-bit integer; `i16`, 16-bit integer; `i32`, 32-bit integer; `i64`, 64-bit integer; `i128`, 128-bit integer.\n- Float number: `f16`, 16-bit floating point; `f32`, 32-bit floating point; `f64`, 64-bit floating point; `f128`, 128-bit floating point;\n- Boolean: `bool`, represents true or false values.\n- C ABI compatible types: `c_long`, `c_char`, `c_short`, `c_ushort`, `c_int`, `c_uint`, and many others.\n- Pointer sized integers: `isize` and `usize`.\n\n\n\n\n\n\n\n## Arrays {#sec-arrays}\n\nYou create arrays in Zig by using a syntax that resembles the C syntax.\nFirst, you specify the size of the array (i.e. the number of elements that will be stored in the array)\nyou want to create inside a pair of brackets.\n\nThen, you specify the data type of the elements that will be stored inside this array.\nAll elements present in an array in Zig must have the same data type. For example, you cannot mix elements\nof type `f32` with elements of type `i32` in the same array.\n\nAfter that, you simply list the values that you want to store in this array inside\na pair of curly braces.\nIn the example below, I am creating two constant objets that contain different arrays.\nThe first object contains an array of 4 integer values, while the second object,\nan array of 3 floating point values.\n\nNow, you should notice that in the object `ls`, I am\nnot explicitly specifying the size of the array inside of the brackets. Instead\nof using a literal value (like the value 4 that I used in the `ns` object), I am\nusing the special character underscore (`_`). This syntax tells the `zig` compiler\nto fill this field with the number of elements listed inside of the curly braces.\nSo, this syntax `[_]` is for lazy (or smart) programmers who leave the job of\ncounting how many elements there are in the curly braces for the compiler.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\nconst ls = [_]f64{432.1, 87.2, 900.05};\n_ = ns; _ = ls;\n```\n:::\n\n\nIs worth noting that these are static arrays, meaning that\nthey cannot grow in size.\nOnce you declare your array, you cannot change the size of it.\nThis is very commom in low level languages.\nBecause low level languages normally wants to give you (the programmer) full control over memory,\nand the way in which arrays are expanded is tightly related to\nmemory management.\n\n\n### Selecting elements of the array\n\nOne very commom activity is to select specific portions of an array\nyou have in your source code.\nIn Zig, you can select a specific element from your\narray, by simply providing the index of this particular\nelement inside brackets after the object name.\nIn the example below, I am selecting the third element from the\n`ns` array. Notice that Zig is a \"zero-index\" based language,\nlike C, C++, Rust, Python, and many other languages.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\ntry stdout.print(\"{d}\\n\", .{ ns[2] });\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n12\n```\n\n\n:::\n:::\n\n\nIn contrast, you can also select specific slices (or sections) of your array, by using a\nrange selector. Some programmers also call these selectors of \"slice selectors\",\nand they also exist in Rust, and have the exact same syntax as in Zig.\nAnyway, a range selector is a special expression in Zig that defines\na range of indexes, and it have the syntax `start..end`.\n\nIn the example below, at the second line of code,\nthe `sl` object stores a slice (or a portion) of the\n`ns` array. More precisely, the elements at index 1 and 2\nin the `ns` array. \n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\nconst sl = ns[1..3];\n_ = sl;\n```\n:::\n\n\nWhen you use the `start..end` syntax,\nthe \"end tail\" of the range selector is non-inclusive,\nmeaning that, the index at the end is not included in the range that is\nselected from the array.\nTherefore, the syntax `start..end` actually means `start..end - 1` in practice.\n\nYou can for example, create a slice that goes from the first to the\nlast elements of the array, by using `ar[0..ar.len]` syntax\nIn other words, it is a slice that\naccess all elements in the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ar = [4]u8{48, 24, 12, 6};\nconst sl = ar[0..ar.len];\n_ = sl;\n```\n:::\n\n\nYou can also use the syntax `start..` in your range selector.\nWhich tells the `zig` compiler to select the portion of the array\nthat begins at the `start` index until the last element of the array.\nIn the example below, we are selecting the range from index 1\nuntil the end of the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\nconst sl = ns[1..];\n_ = sl;\n```\n:::\n\n\n\n### More on slices\n\nAs we discussed before, in Zig, you can select specific portions of an existing\narray. This is called *slicing* in Zig [@zigguide], because when you select a portion\nof an array, you are creating a slice object from that array.\n\nA slice object is essentially a pointer object accompained by a length number.\nThe pointer object points to the first element in the slice, and the\nlength number tells the `zig` compiler how many elements there are in this slice.\n\n> Slices can be thought of as a pair of `[*]T` (the pointer to the data) and a `usize` (the element count) [@zigguide].\n\nThrough the pointer contained inside the slice you can access the elements (or values)\nthat are inside this range (or portion) that you selected from the original array.\nBut the length number (which you can access through the `len` property of your slice object)\nis the really big improvement (over C arrays for example) that Zig brings to the table here.\n\nBecause with this length number\nthe `zig` compiler can easily check if you are trying to access an index that is out of the bounds of this particular slice,\nor, if you are causing any buffer overflow problems. In the example below,\nwe access the `len` property of the slice `sl`, which tells us that this slice\nhave 2 elements in it.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\nconst sl = ns[1..3];\ntry stdout.print(\"{d}\\n\", .{sl.len});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n2\n```\n\n\n:::\n:::\n\n\n\n### Array operators\n\nThere are two array operators available in Zig that are very useful.\nThe array concatenation operator (`++`), and the array multiplication operator (`**`). As the name suggests,\nthese are array operators.\n\nOne important detail about these two operators is that they work\nonly when both operands have a size (or \"length\") that is compile-time known.\nWe are going to talk more about\nthe differences between \"compile-time known\" and \"runtime known\" at @sec-compile-time.\nBut for now, keep this information in mind, that you cannot use these operators in every situation.\n\nIn summary, the `++` operator creates a new array that is the concatenation,\nof both arrays provided as operands. So, the expression `a ++ b` produces\na new array which contains all the elements from arrays `a` and `b`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst a = [_]u8{1,2,3};\nconst b = [_]u8{4,5};\nconst c = a ++ b;\ntry stdout.print(\"{any}\\n\", .{c});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n{ 1, 2, 3, 4, 5 }\n```\n\n\n:::\n:::\n\n\nThis `++` operator is particularly useful to concatenate strings together.\nStrings in Zig are described in depth at @sec-zig-strings. In summary, a string object in Zig\nis essentially an arrays of bytes. So, you can use this array concatenation operator\nto effectively concatenate strings together.\n\nIn contrast, the `**` operator is used to replicate an array multiple\ntimes. In other words, the expression `a ** 3` creates a new array\nwhich contains the elements of the array `a` repeated 3 times.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst a = [_]u8{1,2,3};\nconst c = a ** 2;\ntry stdout.print(\"{any}\\n\", .{c});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n{ 1, 2, 3, 1, 2, 3 }\n```\n\n\n:::\n:::\n\n\n\n## Blocks and scopes {#sec-blocks}\n\nBlocks are created in Zig by a pair of curly braces. A block is just a group of\nexpressions (or statements) contained inside of a pair of curly braces. All of these expressions that\nare contained inside of this pair of curly braces belongs to the same scope.\n\nIn other words, a block just delimits a scope in your code.\nThe objects that you define inside the same block belongs to the same\nscope, and, therefore, are accessible from within this scope.\nAt the same time, these objects are not accessible outside of this scope.\nSo, you could also say that blocks are used to limit the scope of the objects that you create in\nyour source code. In less technical terms, blocks are used to specify where in your source code\nyou can access whatever object you have in your source code.\n\nSo, a block is just a group of expressions contained inside a pair of curly braces.\nAnd every block have it's own scope separated from the others.\nThe body of a function is a classic example of a block. If statements, for and while loops\n(and any other structure in the language that uses the pair of curly braces)\nare also examples of blocks.\n\nThis means that, every if statement, or for loop,\netc., that you create in your source code have it's own separate scope.\nThat is why you can't access the objects that you defined inside\nof your for loop (or if statement) in an outer scope, i.e. a scope outside of the for loop.\nBecause you are trying to access an object that belongs to a scope that is different\nthan your current scope.\n\n\nYou can create blocks within blocks, with multiple levels of nesting.\nYou can also (if you want to) give a label to a particular block, with the colon character (`:`).\nJust write `label:` before you open the pair of curly braces that delimits your block. When you label a block\nin Zig, you can use the `break` keyword to return a value from this block, like as if it\nwas a function's body. You just write the `break` keyword, followed by the block label in the format `:label`,\nand the expression that defines the value that you want to return.\n\nLike in the example below, where we are returning the value from the `y` object\nfrom the block `add_one`, and saving the result inside the `x` object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar y: i32 = 123;\nconst x = add_one: {\n y += 1;\n break :add_one y;\n};\nif (x == 124 and y == 124) {\n try stdout.print(\"Hey!\", .{});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHey!\n```\n\n\n:::\n:::\n\n\n\n\n\n\n## How strings work in Zig? {#sec-zig-strings}\n\nThe first project that we are going to build and discuss in this book is a base64 encoder/decoder (@sec-base64).\nBut in order for us to build such a thing, we need to get a better understanding on how strings work in Zig.\nSo let's discuss this specific aspect of Zig.\n\nIn Zig, a string literal (or a string object if you prefer) is a pointer to a null-terminated array\nof bytes. Each byte in this array is represented by an `u8` value, which is an unsigned 8 bit integer,\nso, it is equivalent to the C data type `unsigned char`.\n\nZig always assumes that this sequence of bytes is UTF-8 encoded. This might not be true for every\nsequence of bytes you have it, but is not really Zig's job to fix the encoding of your strings\n(you can use [`iconv`](https://www.gnu.org/software/libiconv/)[^libiconv] for that).\nToday, most of the text in our modern world, specially on the web, should be UTF-8 encoded.\nSo if your string literal is not UTF-8 encoded, then, you will likely\nhave problems in Zig.\n\n[^libiconv]: \n\nLet’s take for example the word \"Hello\". In UTF-8, this sequence of characters (H, e, l, l, o)\nis represented by the sequence of decimal numbers 72, 101, 108, 108, 111. In xecadecimal, this\nsequence is `0x48`, `0x65`, `0x6C`, `0x6C`, `0x6F`. So if I take this sequence of hexadecimal values,\nand ask Zig to print this sequence of bytes as a sequence of characters (i.e. a string), then,\nthe text \"Hello\" will be printed into the terminal:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\n\npub fn main() !void {\n const bytes = [_]u8{0x48, 0x65, 0x6C, 0x6C, 0x6F};\n try stdout.print(\"{s}\\n\", .{bytes});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHello\n```\n\n\n:::\n:::\n\n\n\nIf you want to see the actual bytes that represents a string in Zig, you can use\na `for` loop to iterate trough each byte in the string, and ask Zig to print each byte as an hexadecimal\nvalue to the terminal. You do that by using a `print()` statement with the `X` formatting specifier,\nlike you would normally do with the [`printf()` function](https://cplusplus.com/reference/cstdio/printf/)[^printfs] in C.\n\n[^printfs]: \n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n const string_literal = \"This is an example of string literal in Zig\";\n try stdout.print(\"Bytes that represents the string object: \", .{});\n for (string_literal) |byte| {\n try stdout.print(\"{X} \", .{byte});\n }\n try stdout.print(\"\\n\", .{});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nBytes that represents the string object: 54 68 69 \n 73 20 69 73 20 61 6E 20 65 78 61 6D 70 6C 65 20 6F\n F 66 20 73 74 72 69 6E 67 20 6C 69 74 65 72 61 6C 2\n 20 69 6E 20 5A 69 67 \n```\n\n\n:::\n:::\n\n\n### Strings in C\n\nAt first glance, this looks very similar to how C treats strings as well. That is, string values\nin C are also treated internally as an array of bytes, and this array is also null-terminated.\n\nBut one key difference between a Zig string and a C string, is that Zig also stores the length of\nthe array inside the string object. This small detail makes your code safer, because is much\neasier for the Zig compiler to check if you are trying to access an element that is \"out of bounds\", i.e. if\nyour trying to access memory that does not belong to you.\n\nTo achieve this same kind of safety in C, you have to do a lot of work that kind of seems pointless.\nSo getting this kind of safety is not automatic and much harder to do in C. For example, if you want\nto track the length of your string troughout your program in C, then, you first need to loop through\nthe array of bytes that represents this string, and find the null element (`'\\0'`) position to discover\nwhere exactly the array ends, or, in other words, to find how much elements the array of bytes contain.\n\nTo do that, you would need something like this in C. In this example, the C string stored in\nthe object `array` is 25 bytes long:\n\n```c\n#include \nint main() {\n char* array = \"An example of string in C\";\n int index = 0;\n while (1) {\n if (array[index] == '\\0') {\n break;\n }\n index++;\n }\n printf(\"Number of elements in the array: %d\\n\", index);\n}\n```\n\n```\nNumber of elements in the array: 25\n```\n\nBut in Zig, you do not have to do this, because the object already contains a `len`\nfield which stores the length information of the array. As an example, the `string_literal` object below is 43 bytes long:\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n const string_literal = \"This is an example of string literal in Zig\";\n try stdout.print(\"{d}\\n\", .{string_literal.len});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n43\n```\n\n\n:::\n:::\n\n\n\n### A better look at the object type\n\nNow, we can inspect better the type of objects that Zig create. To check the type of any object in Zig, you can use the\n`@TypeOf()` function. If we look at the type of the `simple_array` object below, you will find that this object\nis a array of 4 elements. Each element is a signed integer of 32 bits which corresponds to the data type `i32` in Zig.\nThat is what an object of type `[4]i32` is.\n\nBut if we look closely at the type of the `string_literal` object below, you will find that this object is a\nconstant pointer (hence the `*const` annotation) to an array of 43 elements (or 43 bytes). Each element is a\nsingle byte (more precisely, an unsigned 8 bit integer - `u8`), that is why we have the `[43:0]u8` portion of the type below.\nIn other words, the string stored inside the `string_literal` object is 43 bytes long.\nThat is why you have the type `*const [43:0]u8` below.\n\nIn the case of `string_literal`, it is a constant pointer (`*const`) because the object `string_literal` is declared\nas constant in the source code (in the line `const string_literal = ...`). So, if we changed that for some reason, if\nwe declare `string_literal` as a variable object (i.e. `var string_literal = ...`), then, `string_literal` would be\njust a normal pointer to an array of unsigned 8-bit integers (i.e. `* [43:0]u8`).\n\nNow, if we create an pointer to the `simple_array` object, then, we get a constant pointer to an array of 4 elements (`*const [4]i32`),\nwhich is very similar to the type of the `string_literal` object. This demonstrates that a string object (or a string literal)\nin Zig is already a pointer to an array.\n\nJust remember that a \"pointer to an array\" is different than an \"array\". So a string object in Zig is a pointer to an array\nof bytes, and not simply an array of bytes.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n const string_literal = \"This is an example of string literal in Zig\";\n const simple_array = [_]i32{1, 2, 3, 4};\n try stdout.print(\"Type of array object: {}\", .{@TypeOf(simple_array)});\n try stdout.print(\n \"Type of string object: {}\",\n .{@TypeOf(string_literal)}\n );\n try stdout.print(\n \"Type of a pointer that points to the array object: {}\",\n .{@TypeOf(&simple_array)}\n );\n}\n```\n:::\n\n\n```\nType of array object: [4]i32\nType of string object: *const [43:0]u8\nType of a pointer that points to\n the array object: *const [4]i32\n```\n\n\n### Byte vs unicode points\n\nIs important to point out that each byte in the array is not necessarily a single character.\nThis fact arises from the difference between a single byte and a single unicode point.\n\nThe encoding UTF-8 works by assigning a number (which is called a unicode point) to each character in\nthe string. For example, the character \"H\" is stored in UTF-8 as the decimal number 72. This means that\nthe number 72 is the unicode point for the character \"H\". Each possible character that can appear in a\nUTF-8 encoded string have its own unicode point.\n\nFor example, the Latin Capital Letter A With Stroke (Ⱥ) is represented by the number (or the unicode point)\n570. However, this decimal number (570) is higher than the maximum number stored inside a single byte, which\nis 255. In other words, the maximum decimal number that can be represented with a single byte is 255. That is why,\nthe unicode point 570 is actually stored inside the computer’s memory as the bytes `C8 BA`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n const string_literal = \"Ⱥ\";\n try stdout.print(\"Bytes that represents the string object: \", .{});\n for (string_literal) |char| {\n try stdout.print(\"{X} \", .{char});\n }\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nBytes that represents the string object: C8 BA \n```\n\n\n:::\n:::\n\n\n\nThis means that to store the character Ⱥ in an UTF-8 encoded string, we need to use two bytes together\nto represent the number 570. That is why the relationship between bytes and unicode points is not always\n1 to 1. Each unicode point is a single character in the string, but not always a single byte corresponds\nto a single unicode point.\n\nAll of this means that if you loop trough the elements of a string in Zig, you will be looping through the\nbytes that represents that string, and not through the characters of that string. In the Ⱥ example above,\nthe for loop needed two iterations (instead of a single iteration) to print the two bytes that represents this Ⱥ letter.\n\nNow, all english letters (or ASCII letters if you prefer) can be represented by a single byte in UTF-8. As a\nconsequence, if your UTF-8 string contains only english letters (or ASCII letters), then, you are lucky. Because\nthe number of bytes will be equal to the number of characters in that string. In other words, in this specific\nsituation, the relationship between bytes and unicode points is 1 to 1.\n\nBut on the other side, if your string contains other types of letters… for example, you might be working with\ntext data that contains, chinese, japanese or latin letters, then, the number of bytes necessary to represent\nyour UTF-8 string will likely be much higher than the number of characters in that string.\n\nIf you need to iterate through the characters of a string, instead of its bytes, then, you can use the\n`std.unicode.Utf8View` struct to create an iterator that iterates through the unicode points of your string.\n\nIn the example below, we loop through the japanese characters “アメリカ”. Each of the four characters in\nthis string is represented by three bytes. But the for loop iterates four times, one iteration for each\ncharacter/unicode point in this string:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n var utf8 = (\n (try std.unicode.Utf8View.init(\"アメリカ\"))\n .iterator()\n );\n while (utf8.nextCodepointSlice()) |codepoint| {\n try stdout.print(\n \"got codepoint {}\\n\",\n .{std.fmt.fmtSliceHexUpper(codepoint)}\n );\n }\n}\n```\n:::\n\n\n```\ngot codepoint E382A2\ngot codepoint E383A1\ngot codepoint E383AA\ngot codepoint E382AB\n```\n\n\n\n## Safety in Zig\n\nA general trend in modern low-level programming languages is safety. As our modern world\nbecome more interconnected with techology and computers,\nthe data produced by all of this technology becomes one of the most important\n(and also, one of the most dangerous) assets that we have.\n\nThis is probably the main reason why modern low-level programming languages\nhave been giving great attention to safety, specially memory safety, because\nmemory corruption is still the main target for hackers to exploit.\nThe reality is that we don't have an easy solution for this problem.\nFor now, we only have techniques and strategies that mitigates these\nproblems.\n\nAs Richard Feldman explains on his [most recent GOTO conference talk](https://www.youtube.com/watch?v=jIZpKpLCOiU&ab_channel=GOTOConferences)[^gotop]\n, we haven't figured it out yet a way to achieve **true safety in technology**.\nIn other words, we haven't found a way to build software that won't be exploited\nwith 100% certainty. We can greatly reduce the risks of our software being\nexploited, by ensuring memory safety for example. But this is not enough\nto achieve \"true safety\" territory.\n\nBecause even if you write your program in a \"safe language\", hackers can still\nexploit failures in the operational system where your program is running (e.g. maybe the\nsystem where your code is running have a \"backdoor exploit\" that can still\naffect your code in unexpected ways), or also, they can exploit the features\nfrom the architecture of your computer. A recently found exploit\nthat involves memory invalidation through a feature of \"memory tags\"\npresent in ARM chips is an example of that [@exploit1].\n\n[^gotop]: \n\nThe question is: what Zig and other languages have been doing to mitigate this problem?\nIf we take Rust as an example, Rust is, for the most part[^rust-safe], a memory safe\nlanguage by enforcing specific rules to the developer. In other words, the key feature\nof Rust, the *borrow checker*, forces you to follow a specific logic when you are writing\nyour Rust code, and the Rust compiler will always complain everytime you try to go out of this\npattern.\n\n[^rust-safe]: Actually, a lot of existing Rust code is still memory unsafe, because they communicate with external libraries through FFI (*foreign function interface*), which disables the borrow-checker features through the `unsafe` keyword.\n\n\nIn contrast, the Zig language is not a memory safe language by default.\nInstead of forcing the developer to follow a specific rule, the Zig language\nachieves memory safety by offering tools that the developer can use for this purpose.\nIn other words, the `zig` compiler does not obligates you to use such tools.\nBut there is often no reason to not use these tools in your Zig code,\nso you often achieve a similar level of memory safety of Rust in Zig\nby simply using these tools.\n\nThe tools listed below are related to memory safety in Zig. That is, they help you to achieve\nmemory safety in your Zig code:\n\n- `defer` allows you to keep free operations phisically close to allocations. This helps you to avoid memory leaks, \"use after free\", and also \"double-free\" problems. Furthermore, it also keeps free operations logically tied to the end of the current scope, which greatly reduces the mental overhead about object lifetime.\n- `errdefer` helps you to garantee that your program frees the allocated memory, even if a runtime error occurs.\n- pointers and object are non-nullable by default. This helps you to avoid memory problems that might arise from de-referencing null pointers.\n- Zig offers some native types of allocators (called \"testing allocators\") that can detect memory leaks and double-frees. These types of allocators are widely used on unit tests, so they make your unit tests a weapon that you can use to detect memory problems in your code.\n- arrays and slices in Zig have their lengths embedded in the object itself, which makes the `zig` compiler very effective on detecting \"index out-of-range\" type of errors, and avoiding buffer overflows.\n\n\nDespite these features that Zig offers that are related to memory safety issues, the language\nalso have some rules that help you to achieve another type of safety, which is more related to\nprogram logic safety. These rules are:\n\n- pointers and objects are non-nullable by default. Which eliminates an edge case that might break the logic of your program.\n- switch statements must exaust all possible options.\n- the `zig` compiler forces you to handle every possible error.\n\n\n## Other parts of Zig\n\nWe already learned a lot about Zig's syntax, and also, some pretty technical\ndetails about it. Just as a quick recap:\n\n- We talked about how functions are written in Zig at @sec-root-file and @sec-main-file.\n- How to create new objects/identifiers at @sec-root-file and specially at @sec-assignments.\n- How strings work in Zig at @sec-zig-strings.\n- How to use arrays and slices at @sec-arrays.\n- How to import functionality from other Zig modules at @sec-root-file.\n\n\nBut, for now, this amount of knowledge is enough for us to continue with this book.\nLater, over the next chapters we will still talk more about other parts of\nZig's syntax that are also equally important as the other parts. Such as:\n\n\n- How Object-Oriented programming can be done in Zig through *struct declarations* at @sec-structs-and-oop.\n- Basic control flow syntax at @sec-zig-control-flow.\n- Enums at @sec-enum;\n- Pointers and Optionals at @sec-pointer;\n- Error handling with `try` and `catch` at @sec-error-handling;\n- Unit tests at @sec-unittests;\n- Vectors;\n- Build System at @sec-build-system;\n\n\n\n\n", - "supporting": [], + "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n# Introducing Zig\n\nIn this chapter, I want to introduce you to the world of Zig.\nDespite it's rapidly growing over the last years, Zig is, still, a very young language^[New programming languages in general, take years and years to be developed.].\nAs a consequence, it's world is still very wild and to be explored.\nThis book is my attempt to help you on your personal journey for\nunderstanding and exploring the exciting world of Zig.\n\nI assume you have previous experience with some programming\nlanguage in this book, not necessarily with a low-level one.\nSo, if you have experience with Python, or Javascript, for example, is fine.\nBut, if you do have experience with low-level languages, such as C, C++, or\nRust, you will probably learn faster throughout this book.\n\n\n\n## What is Zig?\n\nZig is a modern, low-level, and general-purpose programming language. Some programmers interpret\nZig as the \"modern C language\". It is a simple language like C, but with some\nmodern features.\n\nIn the author's personal interpretation, Zig is tightly connected with \"less is more\".\nInstead of trying to become a modern language by adding more and more features,\nmany of the core improvements that Zig brings to the\ntable are actually about removing annoying and evil behaviours/features from C and C++.\nIn other words, Zig tries to be better by simplifying the language, and by having more consistent and robust behaviour.\nAs a result, analyzing, writing and debugging applications become much easier and simpler in Zig, than it is in C or C++.\n\nThis philosophy becomes clear with the following phrase from the official website of Zig:\n\n> \"Focus on debugging your application rather than debugging your programming language knowledge\".\n\nThis phrase is specially true for C++ programmers. Because C++ is a gigantic language,\nwith tons of features, and also, there are lots of different \"flavors of C++\". These elements\nare what makes C++ so much complex and hard to learn. Zig tries to go in the opposite direction.\nZig is a very simple language, more closely related to other simple languages such as C and Go.\n\nThe phrase above is still important for C programmers too. Because, even C being a simple\nlanguage, it is still hard sometimes to read and understand C code. For example, pre-processor macros in\nC are an evil source of confusion. They really makes it hard sometimes to debug\nC programs. Because macros are essentially a second language embedded in C that obscures\nyour C code. With macros, you are no longer 100% sure about which pieces\nof code are being sent to the compiler. It obscures the actual source code that you wrote.\n\nYou don't have macros in Zig. In Zig, the code you write, is the actual code that get's compiled by the compiler.\nYou don't have evil features that obscures you code.\nYou also don't have hidden control flow happening behind the scenes. And, you also\ndon't have functions or operators from the standard library that make\nhidden memory allocations behind your back.\n\nBy being a simpler language, Zig becomes much more clear and easier to read/write,\nbut at the same time, it also achieves a much more robust state, with more consistent\nbehaviour in edge situations. Once again, less is more.\n\n\n## Hello world in Zig\n\nWe begin our journey in Zig by creating a small \"Hello World\" program.\nTo start a new Zig project in your computer, you simply call the `init` command\nfrom the `zig` compiler.\nJust create a new directory in your computer, then, init a new Zig project\ninside this directory, like this:\n\n```bash\nmkdir hello_world\ncd hello_world\nzig init\n```\n\n```\ninfo: created build.zig\ninfo: created build.zig.zon\ninfo: created src/main.zig\ninfo: created src/root.zig\ninfo: see `zig build --help` for a menu of options\n```\n\n### Understanding the project files {#sec-project-files}\n\nAfter you run the `init` command from the `zig` compiler, some new files\nare created inside of your current directory. First, a \"source\" (`src`) directory\nis created, containing two files, `main.zig` and `root.zig`. Each `.zig` file\nis a separate Zig module, which is simply a text file that contains some Zig code.\n\n\nThe `main.zig` file for example, contains a `main()` function, which represents\nthe entrypoint of your program. It is where the execution of your program begins.\nAs you would expect from a C, C++, Rust or Go,\nto build an executabe program in Zig, you also need to declare a `main()` function in your module.\nSo, the `main.zig` module represents an executable program written in Zig.\n\nOn the other side, the `root.zig` module does not contain a `main()` function. Because\nit represents a library written in Zig. Libraries are different than executables.\nThey don't need to have an entrypoint to work.\nSo, you can choose which file (`main.zig` or `root.zig`) you want to follow depending on which type\nof project (executable or library) you want to develop.\n\n```bash\ntree .\n```\n\n```\n.\n├── build.zig\n├── build.zig.zon\n└── src\n ├── main.zig\n └── root.zig\n\n1 directory, 4 files\n```\n\n\nNow, in addition to the source directory, two other files were created in our working directory:\n`build.zig` and `build.zig.zon`. The first file (`build.zig`) represents a build script written in Zig.\nThis script is executed when you call the `build` command from the `zig` compiler.\nIn other words, this file contain Zig code that executes the necessary steps to build the entire project.\n\nIn general, low-level languages normally use a compiler to build your\nsource code into binary executables or binary libraries.\nNevertheless, this process of compiling your source code and building\nbinary executables or binary libraries from it, became a real challenge\nin the programming world, once the projects became bigger and bigger.\nAs a result, programmers created \"build systems\", which are a second set of tools designed to make this process\nof compiling and building complex projects, easier.\n\nExamples of build systems are CMake, GNU Make, GNU Autoconf and Ninja,\nwhich are used to build complex C and C++ projects.\nWith these systems, you can write scripts, which are called \"build scripts\".\nThey simply are scripts that describes the necessary steps to compile/build\nyour project.\n\nHowever, these are separate tools, that do not\nbelong to C/C++ compilers, like `gcc` or `clang`.\nAs a result, in C/C++ projects, you have not only to install and\nmanage your C/C++ compilers, but you also have to install and manage\nthese build systems separately.\n\nBut instead of using a separate build system, in Zig, we use the\nZig language itself to write build scripts.\nIn other words, Zig contains a native build system in it. And\nwe can use this build system to write small scripts in Zig,\nwhich describes the necessary steps to build/compile our Zig project[^zig-build-system].\nSo, everything you need to build a complex Zig project is the\n`zig` compiler, and nothing more.\n\n[^zig-build-system]: .\n\n\nNow that we described this topic in more depth, let's focus\non the second generated file (`build.zig.zon`), which is the Zig package manager configuration file,\nwhere you can list and manage the dependencies of your project. Yes, Zig have\na package manager (like `pip` in Python, `cargo` in Rust, or `npm` in Javascript) called Zon,\nand this `build.zig.zon` file is similar to the `package.json` file\nin Javascript projects, or, the `Pipfile` in Python projects.\n\n\n### Looking at the `root.zig` file {#sec-root-file}\n\nLet's take a look at the `root.zig` file, and start to analyze some of the\nsyntax of Zig.\nThe first thing that you might notice, is that every line of code\nthat have an expression in it, ends with a semicolon character (`;`). This is\nsimilar syntax to other languages such as C, C++ and Rust,\nwhich have the same rule.\n\nAlso, notice the `@import()` call at the first line. We use this built-in function\nto import functionality from other Zig modules into our current module.\nIn other words, the `@import()` function works similarly to the `#include` pre-processor\nin C or C++, or, to the `import` statement in Python or Javascript code.\nIn this example, we are importing the `std` module,\nwhich gives you access to the Zig standard library.\n\nIn this `root.zig` file, we can also see how assignments (i.e. creating new objects)\nare made in Zig. You can create a new object in Zig by using the following syntax\n`(const|var) name = value;`. In the example below, we are creating two constant\nobjects (`std` and `testing`). At @sec-assignments we talk more about objects in general.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst testing = std.testing;\n\nexport fn add(a: i32, b: i32) i32 {\n return a + b;\n}\n```\n:::\n\n\nFunctions in Zig are declared similarly to functions in Rust, using the `fn` keyword. In the example above,\nwe are declaring a function called `add()`, which have two arguments named `a` and `b`, and returns\na integer number (`i32`) as result.\n\nMaybe Zig is not exactly a strongly-typed language, because you do not need\nnecessarily to specify the type of every single object you create across your source code.\nBut you do have to explicitly specify the type of every function argument, and also,\nthe return type of every function you create in Zig. So, at least in function declarations,\nZig is a strongly-typed language.\n\nWe specify the type of an object or a function argument in Zig, by\nusing a colon character (`:`) followed by the type after the name of this object/function argument.\nWith the expressions `a: i32` and `b: i32`, we know that, both `a` and `b` arguments have type `i32`,\nwhich is a signed 32 bit integer. In this part,\nthe syntax in Zig is identical to the syntax in Rust, which also specifies types by\nusing the colon character.\n\nLastly, we have the return type of the function at the end of the line, before we open\nthe curly braces to start writing the function's body, which, in the example above is\nagain a signed 32 bit integer (`i32`) value. This specific part is different than it is in Rust.\nBecause in Rust, the return type of a function is specified after an arrow (`->`).\nWhile in Zig, we simply declare the return type directly after the parentheses with the function arguments.\n\nWe also have an `export` keyword before the function declaration. This keyword\nis similar to the `extern` keyword in C. It exposes the function\nto make it available in the library API.\n\nIn other words, if you have a project where you are currently building\na library for other people to use, you need to expose your functions\nso that they are available in the library's API, so that users can use it.\nIf we removed the `export` keyword from the `add()` function declaration,\nthen, this function would be no longer exposed in the library object built\nby the `zig` compiler.\n\n\nHaving that in mind, the keyword `export` is a keyword used in libraries written in Zig.\nSo, if you are not currently writing a library in your project, then, you do not need to\ncare about this keyword.\n\n\n### Looking at the `main.zig` file {#sec-main-file}\n\nNow that we have learned a lot about Zig's syntax from the `root.zig` file,\nlet's take a look at the `main.zig` file.\nA lot of the elements we saw in `root.zig` are also present in `main.zig`.\nBut we have some other elements that we did not have seen yet, so let's dive in.\n\nFirst, look at the return type of the `main()` function in this file.\nWe can see a small change. Now, the return\ntype of the function (`void`) is accompanied by an exclamation mark (`!`).\nWhat this exclamation mark is telling us, is that this `main()` function\nmight also return an error.\n\nSo, in this example, the `main()` function can either return `void`, or, return an error.\nThis is an interesting feature of Zig. If you write a function, and, something inside of\nthe body of this function might return an error, then, you are forced to:\n\n- either add the exclamation mark to the return type of the function, to make it clear that\nthis function might return an error.\n- or explicitly handle this error that might occur inside the function, to make sure that,\nif this error does happen, you are prepared, and your function will no longer return an error\nbecause you handled the error inside your function.\n\nIn most programming languages, we normally handle (or deals with) an error through\na *try catch* pattern, and Zig, this is no different. But, if we look at the `main()` function\nbelow, you can see that we do have a `try` keyword in the 5th line. But we do not have a `catch` keyword\nin this code.\n\nThis means that, we are using the keyword `try` to execute a code that might return an error,\nwhich is the `stdout.print()` expression. But because we do not have a `catch` keyword in this line,\nwe are not treating (or dealing with) this error.\nSo, if this expression do return an error, we are not catching and solving this error in any way.\nThat is why the exclamation mark was added to the return type of the function.\n\nSo, in essence, the `try` keyword executes the expression `stdout.print()`. If this expression\nreturns a valid value, then, the `try` keyword do nothing essentially. It simply passes this value forward. But, if the expression do\nreturn an error, then, the `try` keyword will unwrap and return this error from the function, and also print it's\nstack trace to `stderr`.\n\nThis might sound weird to you, if you come from a high-level language. Because in\nhigh-level languages, such as Python, if an error occurs somewhere, this error is automatically\nreturned and the execution of your program will automatically stops, even if you don't want\nto stop the execution. You are obligated to face the error.\n\nBut if you come from a low-level language, then, maybe, this idea do not sound so weird or distant to you.\nBecause in C for example, normally functions doesn't raise errors, or, they normally don't stop the execution.\nIn C, error handling\nis done by constantly checking the return value of the function. So, you run the function,\nand then, you use an if statement to check if the function returned a value that is valid,\nor, if it returned an error. If an error was returned from the function, then, the if statement\nwill execute some code that fixes this error.\n\nSo, at least for C programmers, they do need to write a lot of if statements to\nconstantly check for errors around their code. And because of that, this simple feature from Zig, might be\nextraordinary for them. Because this `try` keyword can automatically unwrap the error,\nand warn you about this error, and let you deal with it, without any extra work from the programmer.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\n\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n```\n:::\n\n\nNow, another thing that you might have noticed in this code example, is that\nthe `main()` function is marked with the `pub` keyword. This keyword means\n\"public\". It marks the `main()` function as a *public function* from this module.\n\nIn other words, every function that you declare in your Zig module is, by default, a private (or \"static\")\nfunction that belongs to this Zig module, and can only be used (or called) from within this same module.\nUnless, you explicitly mark this function as a public function with the `pub` keyword.\nThis means that the `pub` keyword in Zig do essentially the opposite of what the `static` keyword\ndo in C/C++.\n\nBy making a function \"public\", you allow other Zig modules to access and call this function,\nand use it for they own purposes.\nall these other Zig modules need to do is, to import your module with the `@import()`\nbuilt-in function. Then, they get access to all public functions that are present in\nyour Zig module.\n\n\n### Compiling your source code {#sec-compile-code}\n\nYou can compile your Zig modules into a binary executable by running the `build-exe` command\nfrom the `zig` compiler. You simply list all the Zig modules that you want to build after\nthe `build-exe` command, separated by spaces. In the example below, we are compiling the module `main.zig`.\n\n```bash\nzig build-exe src/main.zig\n```\n\nSince we are building an executable, the `zig` compiler will look for a `main()` function\ndeclared in any of the files that you list after the `build-exe` command. If\nthe compiler does not find a `main()` function declared somewhere, a\ncompilation error will be raised, warning about this mistake.\n\nThe `zig` compiler also offers a `build-lib` and `build-obj` commands, which work\nthe exact same way as the `build-exe` command. The only difference is that, they compile your\nZig modules into a portale C ABI library, or, into object files, respectively.\n\nIn the case of the `build-exe` command, a binary executable file is created by the `zig`\ncompiler in the root directory of your project.\nIf we take a look now at the contents of our current directory, with a simple `ls` command, we can\nsee the binary file called `main` that was created by the compiler.\n\n```bash\nls\n```\n\n```\nbuild.zig build.zig.zon main src\n```\n\nIf I execute this binary executable, I get the \"Hello World\" message in the terminal\n, as we expected.\n\n```bash\n./main\n```\n\n```\nHello, world!\n```\n\n\n### Compile and execute at the same time {#sec-compile-run-code}\n\nOn the previous section, I presented the `zig build-exe` command, which\ncompiles Zig modules into an executable file. However, this means that,\nin order to execute the executable file, we have to run two different commands.\nFirst, the `zig build-exe` command, and then, we call the executable file\ncreated by the compiler.\n\nBut what if we wanted to perform these two steps,\nall at once, in a single command? We can do that by using the `zig run`\ncommand.\n\n```bash\nzig run src/main.zig\n```\n\n```\nHello, world!\n```\n\n### Compiling the entire project {#sec-compile-project}\n\nJust as I described at @sec-project-files, as our project grows in size and\ncomplexity, we usually prefer to organize the compilation and build process\nof the project into a build script, using some sort of \"build system\".\n\nIn other words, as our project grows in size and complexity,\nthe `build-exe`, `build-lib` and `build-obj` commands become\nharder to use directly. Because then, we start to list\nmultiple and multiple modules at the same time. We also\nstart to add built-in compilation flags to customize the\nbuild process for our needs, etc. It becomes a lot of work\nto write the necessary commands by hand.\n\nIn C/C++ projects, programmers normally opt to use CMake, Ninja, `Makefile` or `configure` scripts\nto organize this process. However, in Zig, we have a native build system in the language itself.\nSo, we can write build scripts in Zig to compile and build Zig projects. Then, all we\nneed to do, is to call the `zig build` command to build our project.\n\nSo, when you execute the `zig build` command, the `zig` compiler will search\nfor a Zig module named `build.zig` inside your current directory, which\nshould be your build script, containing the necessary code to compile and\nbuild your project. If the compiler do find this `build.zig` file in your directory,\nthen, the compiler will essentially execute a `zig run` command\nover this `build.zig` file, to compile and execute this build\nscript, which in turn, will compile and build your entire project.\n\n\n```bash\nzig build\n```\n\n\nAfter you execute this \"build project\" command, a `zig-out` directory\nis created in the root of your project directory, where you can find\nthe binary executables and libraries created from your Zig modules\naccordingly to the build commands that you specified at `build.zig`.\nWe will talk more about the build system in Zig latter in this book.\n\nIn the example below, I'm executing the binary executable\nnamed `hello_world` that was generated by the compiler after the\n`zig build` command.\n\n```bash\n./zig-out/bin/hello_world\n```\n\n```\nHello, world!\n```\n\n\n\n## How to learn Zig?\n\nWhat are the best strategies to learn Zig? \nFirst of all, of course this book will help you a lot on your journey through Zig.\nBut you will also need some extra resources if you want to be really good at Zig.\n\nAs a first tip, you can join a community with Zig programmers to get some help\n, when you need it:\n\n- Reddit forum: ;\n- Ziggit community: ;\n- Discord, Slack, Telegram, and others: ;\n\nNow, one of the best ways to learn Zig is to simply read Zig code. Try\nto read Zig code often, and things will become more clear.\nA C/C++ programmer would also probably give you this same tip.\nBecause this strategy really works!\n\nNow, where you can find Zig code to read?\nI personally think that, the best way of reading Zig code is to read the source code of the\nZig 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\nthe official GitHub repository of Zig. Access this folder, and start exploring the Zig modules.\n\nAlso, a great alternative is to read code from other large Zig\ncodebases, such as:\n\n1. the [Javascript runtime Bun](https://github.com/oven-sh/bun)[^bunjs].\n1. the [game engine Mach](https://github.com/hexops/mach)[^mach].\n1. a [LLama 2 LLM model implementation in Zig](https://github.com/cgbur/llama2.zig/tree/main)[^ll2].\n1. the [financial transactions database `tigerbeetle`](https://github.com/tigerbeetle/tigerbeetle)[^tiger].\n1. the [command-line arguments parser `zig-clap`](https://github.com/Hejsil/zig-clap)[^clap].\n1. the [UI framework `capy`](https://github.com/capy-ui/capy)[^capy].\n1. the [Language Protocol implementation for Zig, `zls`](https://github.com/zigtools/zls)[^zls].\n1. the [event-loop library `libxev`](https://github.com/mitchellh/libxev)[^xev].\n\n[^xev]: \n[^zls]: \n[^capy]: \n[^clap]: \n[^tiger]: \n[^ll2]: \n[^mach]: \n[^bunjs]: .\n\nAll these assets are available on GitHub,\nand this is great, because we can use the GitHub search bar in our advantage,\nto find Zig code that fits our description.\nFor example, you can always include `lang:Zig` in the GitHub search bar when you\nare searching for a particular pattern. This will limit the search to only Zig modules.\n\n[^zig-lib-std]: \n\nAlso, a great alternative is to consult online resources and documentations.\nHere is a quick list of resources that I personally use from time to time to learn\nmore about the language each day:\n\n- Zig Language Reference: ;\n- Zig Standard Library Reference: ;\n- Zig Guide: ;\n- Karl Seguin Blog: ;\n- Zig News: ;\n- Read the code written by one of the Zig core team members: ;\n- Some livecoding sessions are transmitted in the Zig Showtime Youtube Channel: ;\n\n\nAnother great strategy to learn Zig, or honestly, to learn any language you want,\nis to practice it by solving exercises. For example, there is a famous repository\nin the Zig community called [Ziglings](https://codeberg.org/ziglings/exercises/)[^ziglings]\n, which contains more than 100 small exercises that you can solve. It is a repository of\ntiny programs written in Zig that are currently broken, and your responsibility is to\nfix these programs, and make them work again.\n\n[^ziglings]: .\n\nA famous tech YouTuber known as *The Primeagen* also posted some videos (at YouTube)\nwhere he solves these exercises from Ziglings. The first video is named\n[\"Trying Zig Part 1\"](https://www.youtube.com/watch?v=OPuztQfM3Fg&t=2524s&ab_channel=TheVimeagen)[^prime1].\n\n[^prime1]: .\n\nAnother great alternative, is to solve the [Advent of Code exercises](https://adventofcode.com/)[^advent-code].\nThere are people that already took the time to learn and solve the exercises, and they posted\ntheir solutions on GitHub as well, so, in case you need some resource to compare while solving\nthe exercises, you can look at these two repositories:\n\n- ;\n- ;\n\n[^advent-code]: \n\n\n\n\n\n\n## Creating new objects in Zig (i.e. identifiers) {#sec-assignments}\n\nLet's talk more about objects in Zig. Readers that have past experience\nwith other programming languages might know this concept through\na different name, such as: \"variable\" or \"identifier\". In this book, I choose\nto use the term \"object\" to refer to this concept.\n\nTo create a new object (or a new \"identifier\") in Zig, we use\nthe keywords `const` or `var`. These keywords specificy if the object\nthat you are creating is mutable or not.\nIf you use `const`, then the object you are\ncreating is a constant (or immutable) object, which means that once you declare this object, you\ncan no longer change the value stored inside this object.\n\nOn the other side, if you use `var`, then, you are creating a variable (or mutable) object.\nYou can change the value of this object as many times you want. Using the\nkeyword `var` in Zig is similar to using the keywords `let mut` in Rust.\n\n### Constant objects vs variable objects\n\nIn the code example below, we are creating a new constant object called `age`.\nThis object stores a number representing the age of someone. However, this code example\ndoes not compiles succesfully. Because on the next line of code, we are trying to change the value\nof the object `age` to 25.\n\nThe `zig` compiler detects that we are trying to change\nthe value of an object/identifier that is constant, and because of that,\nthe compiler will raise a compilation error, warning us about the mistake.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst age = 24;\n// The line below is not valid!\nage = 25;\n```\n:::\n\n\n```\nt.zig:10:5: error: cannot assign to constant\n age = 25;\n ~~^~~\n```\n\nIn contrast, if you use `var`, then, the object created is a variable object.\nWith `var` you can declare this object in your source code, and then,\nchange the value of this object how many times you want over future points\nin your source code.\n\nSo, using the same code example exposed above, if I change the declaration of the\n`age` object to use the `var` keyword, then, the program gets compiled succesfully.\nBecause now, the `zig` compiler detects that we are changing the value of an\nobject that allows this behaviour, because it is an \"variable object\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar age: u8 = 24;\nage = 25;\n```\n:::\n\n\n\n### Declaring without an initial value\n\nBy default, when you declare a new object in Zig, you must give it\nan initial value. In other words, this means\nthat we have to declare, and, at the same time, initialize every object we\ncreate in our source code.\n\nOn the other hand, you can, in fact, declare a new object in your source code,\nand not give it an explicit value. But we need to use a special keyword for that,\nwhich is the `undefined` keyword.\n\nIs important to emphasize that, you should avoid using `undefined` as much as possible.\nBecause when you use this keyword, you leave your object uninitialized, and, as a consequence,\nif for some reason, your code use this object while it is uninitialized, then, you will definitely\nhave undefined behaviour and major bugs in your program.\n\nIn the example below, I'm declaring the `age` object again. But this time,\nI do not give it an initial value. The variable is only initialized at\nthe second line of code, where I store the number 25 in this object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar age: u8 = undefined;\nage = 25;\n```\n:::\n\n\nHaving these points in mind, just remember that you should avoid as much as possible to use `undefined` in your code.\nAlways declare and initialize your objects. Because this gives you much more safety in your program.\nBut in case you really need to declare an object without initializing it... the\n`undefined` keyword is the way to do it in Zig.\n\n\n### There is no such thing as unused objects\n\nEvery object (being constant or variable) that you declare in Zig **must be used in some way**. You can give this object\nto a function call, as a function argument, or, you can use it in another expression\nto calculate the value of another object, or, you can call a method that belongs to this\nparticular object. \n\nIt doesn't matter in which way you use it. As long as you use it.\nIf you try to break this rule, i.e. if your try to declare a object, but not use it,\nthe `zig` compiler will not compile your Zig source code, and it will issue a error\nmessage warning that you have unused objects in your code.\n\nLet's demonstrate this with an example. In the source code below, we declare a constant object\ncalled `age`. If you try to compile a simple Zig program with this line of code below,\nthe compiler will return an error as demonstrated below:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst age = 15;\n```\n:::\n\n\n```\nt.zig:4:11: error: unused local constant\n const age = 15;\n ^~~\n```\n\nEverytime you declare a new object in Zig, you have two choices:\n\n1. you either use the value of this object;\n2. or you explicitly discard the value of the object;\n\nTo explicitly discard the value of any object (constant or variable), all you need to do is to assign\nthis object to an special character in Zig, which is the underscore (`_`).\nWhen you assign an object to a underscore, like in the example below, the `zig` compiler will automatically\ndiscard the value of this particular object.\n\nYou can see in the example below that, this time, the compiler did not\ncomplain about any \"unused constant\", and succesfully compiled our source code.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\n// It compiles!\nconst age = 15;\n_ = age;\n```\n:::\n\n\nNow, remember, everytime you assign a particular object to the underscore, this object\nis essentially destroyed. It is discarded by the compiler. This means that you can no longer\nuse this object further in your code. It doesn't exist anymore.\n\nSo if you try to use the constant `age` in the example below, after we discarded it, you\nwill get a loud error message from the compiler (talking about a \"pointless discard\")\nwarning you about this mistake.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\n// It does not compile.\nconst age = 15;\n_ = age;\n// Using a discarded value!\nstd.debug.print(\"{d}\\n\", .{age + 2});\n```\n:::\n\n\n```\nt.zig:7:5: error: pointless discard\n of local constant\n```\n\n\nThis same rule applies to variable objects. Every variable object must also be used in\nsome way. And if you assign a variable object to the underscore,\nthis object also get's discarded, and you can no longer use this object.\n\n\n\n### You must mutate every variable objects\n\nEvery variable object that you create in your source code must be mutated at some point.\nIn other words, if you declare an object as a variable\nobject, with the keyword `var`, and you do not change the value of this object\nat some point in the future, the `zig` compiler will detect this,\nand it will raise an error warning you about this mistake.\n\nThe concept behind this is that every object you create in Zig should be preferably a\nconstant object, unless you really need an object whose value will\nchange during the execution of your program.\n\nSo, if I try to declare a variable object such as `where_i_live` below,\nand I do not change the value of this object in some way,\nthe `zig` compiler raises an error message with the phrase \"variable is never mutated\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar where_i_live = \"Belo Horizonte\";\n_ = where_i_live;\n```\n:::\n\n\n```\nt.zig:7:5: error: local variable is never mutated\nt.zig:7:5: note: consider using 'const'\n```\n\n## Primitive Data Types {#sec-primitive-data-types}\n\nZig have many different primitive data types available for you to use.\nYou can see the full list of available data types at the official\n[Language Reference page](https://ziglang.org/documentation/master/#Primitive-Types)[^lang-data-types].\n\n[^lang-data-types]: .\n\nBut here is a quick list:\n\n- Unsigned integers: `u8`, 8-bit integer; `u16`, 16-bit integer; `u32`, 32-bit integer; `u64`, 64-bit integer; `u128`, 128-bit integer.\n- Signed integers: `i8`, 8-bit integer; `i16`, 16-bit integer; `i32`, 32-bit integer; `i64`, 64-bit integer; `i128`, 128-bit integer.\n- Float number: `f16`, 16-bit floating point; `f32`, 32-bit floating point; `f64`, 64-bit floating point; `f128`, 128-bit floating point;\n- Boolean: `bool`, represents true or false values.\n- C ABI compatible types: `c_long`, `c_char`, `c_short`, `c_ushort`, `c_int`, `c_uint`, and many others.\n- Pointer sized integers: `isize` and `usize`.\n\n\n\n\n\n\n\n## Arrays {#sec-arrays}\n\nYou create arrays in Zig by using a syntax that resembles the C syntax.\nFirst, you specify the size of the array (i.e. the number of elements that will be stored in the array)\nyou want to create inside a pair of brackets.\n\nThen, you specify the data type of the elements that will be stored inside this array.\nAll elements present in an array in Zig must have the same data type. For example, you cannot mix elements\nof type `f32` with elements of type `i32` in the same array.\n\nAfter that, you simply list the values that you want to store in this array inside\na pair of curly braces.\nIn the example below, I am creating two constant objets that contain different arrays.\nThe first object contains an array of 4 integer values, while the second object,\nan array of 3 floating point values.\n\nNow, you should notice that in the object `ls`, I am\nnot explicitly specifying the size of the array inside of the brackets. Instead\nof using a literal value (like the value 4 that I used in the `ns` object), I am\nusing the special character underscore (`_`). This syntax tells the `zig` compiler\nto fill this field with the number of elements listed inside of the curly braces.\nSo, this syntax `[_]` is for lazy (or smart) programmers who leave the job of\ncounting how many elements there are in the curly braces for the compiler.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\nconst ls = [_]f64{432.1, 87.2, 900.05};\n_ = ns; _ = ls;\n```\n:::\n\n\nIs worth noting that these are static arrays, meaning that\nthey cannot grow in size.\nOnce you declare your array, you cannot change the size of it.\nThis is very commom in low level languages.\nBecause low level languages normally wants to give you (the programmer) full control over memory,\nand the way in which arrays are expanded is tightly related to\nmemory management.\n\n\n### Selecting elements of the array\n\nOne very commom activity is to select specific portions of an array\nyou have in your source code.\nIn Zig, you can select a specific element from your\narray, by simply providing the index of this particular\nelement inside brackets after the object name.\nIn the example below, I am selecting the third element from the\n`ns` array. Notice that Zig is a \"zero-index\" based language,\nlike C, C++, Rust, Python, and many other languages.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\ntry stdout.print(\"{d}\\n\", .{ ns[2] });\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n12\n```\n\n\n:::\n:::\n\n\nIn contrast, you can also select specific slices (or sections) of your array, by using a\nrange selector. Some programmers also call these selectors of \"slice selectors\",\nand they also exist in Rust, and have the exact same syntax as in Zig.\nAnyway, a range selector is a special expression in Zig that defines\na range of indexes, and it have the syntax `start..end`.\n\nIn the example below, at the second line of code,\nthe `sl` object stores a slice (or a portion) of the\n`ns` array. More precisely, the elements at index 1 and 2\nin the `ns` array. \n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\nconst sl = ns[1..3];\n_ = sl;\n```\n:::\n\n\nWhen you use the `start..end` syntax,\nthe \"end tail\" of the range selector is non-inclusive,\nmeaning that, the index at the end is not included in the range that is\nselected from the array.\nTherefore, the syntax `start..end` actually means `start..end - 1` in practice.\n\nYou can for example, create a slice that goes from the first to the\nlast elements of the array, by using `ar[0..ar.len]` syntax\nIn other words, it is a slice that\naccess all elements in the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ar = [4]u8{48, 24, 12, 6};\nconst sl = ar[0..ar.len];\n_ = sl;\n```\n:::\n\n\nYou can also use the syntax `start..` in your range selector.\nWhich tells the `zig` compiler to select the portion of the array\nthat begins at the `start` index until the last element of the array.\nIn the example below, we are selecting the range from index 1\nuntil the end of the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\nconst sl = ns[1..];\n_ = sl;\n```\n:::\n\n\n\n### More on slices\n\nAs we discussed before, in Zig, you can select specific portions of an existing\narray. This is called *slicing* in Zig [@zigguide], because when you select a portion\nof an array, you are creating a slice object from that array.\n\nA slice object is essentially a pointer object accompained by a length number.\nThe pointer object points to the first element in the slice, and the\nlength number tells the `zig` compiler how many elements there are in this slice.\n\n> Slices can be thought of as a pair of `[*]T` (the pointer to the data) and a `usize` (the element count) [@zigguide].\n\nThrough the pointer contained inside the slice you can access the elements (or values)\nthat are inside this range (or portion) that you selected from the original array.\nBut the length number (which you can access through the `len` property of your slice object)\nis the really big improvement (over C arrays for example) that Zig brings to the table here.\n\nBecause with this length number\nthe `zig` compiler can easily check if you are trying to access an index that is out of the bounds of this particular slice,\nor, if you are causing any buffer overflow problems. In the example below,\nwe access the `len` property of the slice `sl`, which tells us that this slice\nhave 2 elements in it.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [4]u8{48, 24, 12, 6};\nconst sl = ns[1..3];\ntry stdout.print(\"{d}\\n\", .{sl.len});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n2\n```\n\n\n:::\n:::\n\n\n\n### Array operators\n\nThere are two array operators available in Zig that are very useful.\nThe array concatenation operator (`++`), and the array multiplication operator (`**`). As the name suggests,\nthese are array operators.\n\nOne important detail about these two operators is that they work\nonly when both operands have a size (or \"length\") that is compile-time known.\nWe are going to talk more about\nthe differences between \"compile-time known\" and \"runtime known\" at @sec-compile-time.\nBut for now, keep this information in mind, that you cannot use these operators in every situation.\n\nIn summary, the `++` operator creates a new array that is the concatenation,\nof both arrays provided as operands. So, the expression `a ++ b` produces\na new array which contains all the elements from arrays `a` and `b`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst a = [_]u8{1,2,3};\nconst b = [_]u8{4,5};\nconst c = a ++ b;\ntry stdout.print(\"{any}\\n\", .{c});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n{ 1, 2, 3, 4, 5 }\n```\n\n\n:::\n:::\n\n\nThis `++` operator is particularly useful to concatenate strings together.\nStrings in Zig are described in depth at @sec-zig-strings. In summary, a string object in Zig\nis essentially an arrays of bytes. So, you can use this array concatenation operator\nto effectively concatenate strings together.\n\nIn contrast, the `**` operator is used to replicate an array multiple\ntimes. In other words, the expression `a ** 3` creates a new array\nwhich contains the elements of the array `a` repeated 3 times.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst a = [_]u8{1,2,3};\nconst c = a ** 2;\ntry stdout.print(\"{any}\\n\", .{c});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n{ 1, 2, 3, 1, 2, 3 }\n```\n\n\n:::\n:::\n\n\n\n## Blocks and scopes {#sec-blocks}\n\nBlocks are created in Zig by a pair of curly braces. A block is just a group of\nexpressions (or statements) contained inside of a pair of curly braces. All of these expressions that\nare contained inside of this pair of curly braces belongs to the same scope.\n\nIn other words, a block just delimits a scope in your code.\nThe objects that you define inside the same block belongs to the same\nscope, and, therefore, are accessible from within this scope.\nAt the same time, these objects are not accessible outside of this scope.\nSo, you could also say that blocks are used to limit the scope of the objects that you create in\nyour source code. In less technical terms, blocks are used to specify where in your source code\nyou can access whatever object you have in your source code.\n\nSo, a block is just a group of expressions contained inside a pair of curly braces.\nAnd every block have it's own scope separated from the others.\nThe body of a function is a classic example of a block. If statements, for and while loops\n(and any other structure in the language that uses the pair of curly braces)\nare also examples of blocks.\n\nThis means that, every if statement, or for loop,\netc., that you create in your source code have it's own separate scope.\nThat is why you can't access the objects that you defined inside\nof your for loop (or if statement) in an outer scope, i.e. a scope outside of the for loop.\nBecause you are trying to access an object that belongs to a scope that is different\nthan your current scope.\n\n\nYou can create blocks within blocks, with multiple levels of nesting.\nYou can also (if you want to) give a label to a particular block, with the colon character (`:`).\nJust write `label:` before you open the pair of curly braces that delimits your block. When you label a block\nin Zig, you can use the `break` keyword to return a value from this block, like as if it\nwas a function's body. You just write the `break` keyword, followed by the block label in the format `:label`,\nand the expression that defines the value that you want to return.\n\nLike in the example below, where we are returning the value from the `y` object\nfrom the block `add_one`, and saving the result inside the `x` object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar y: i32 = 123;\nconst x = add_one: {\n y += 1;\n break :add_one y;\n};\nif (x == 124 and y == 124) {\n try stdout.print(\"Hey!\", .{});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHey!\n```\n\n\n:::\n:::\n\n\n\n\n\n\n## How strings work in Zig? {#sec-zig-strings}\n\nThe first project that we are going to build and discuss in this book is a base64 encoder/decoder (@sec-base64).\nBut in order for us to build such a thing, we need to get a better understanding on how strings work in Zig.\nSo let's discuss this specific aspect of Zig.\n\nIn Zig, a string literal (or a string object if you prefer) is a pointer to a null-terminated array\nof bytes. Each byte in this array is represented by an `u8` value, which is an unsigned 8 bit integer,\nso, it is equivalent to the C data type `unsigned char`.\n\nZig always assumes that this sequence of bytes is UTF-8 encoded. This might not be true for every\nsequence of bytes you have it, but is not really Zig's job to fix the encoding of your strings\n(you can use [`iconv`](https://www.gnu.org/software/libiconv/)[^libiconv] for that).\nToday, most of the text in our modern world, specially on the web, should be UTF-8 encoded.\nSo if your string literal is not UTF-8 encoded, then, you will likely\nhave problems in Zig.\n\n[^libiconv]: \n\nLet’s take for example the word \"Hello\". In UTF-8, this sequence of characters (H, e, l, l, o)\nis represented by the sequence of decimal numbers 72, 101, 108, 108, 111. In xecadecimal, this\nsequence is `0x48`, `0x65`, `0x6C`, `0x6C`, `0x6F`. So if I take this sequence of hexadecimal values,\nand ask Zig to print this sequence of bytes as a sequence of characters (i.e. a string), then,\nthe text \"Hello\" will be printed into the terminal:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\n\npub fn main() !void {\n const bytes = [_]u8{0x48, 0x65, 0x6C, 0x6C, 0x6F};\n try stdout.print(\"{s}\\n\", .{bytes});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHello\n```\n\n\n:::\n:::\n\n\n\nIf you want to see the actual bytes that represents a string in Zig, you can use\na `for` loop to iterate trough each byte in the string, and ask Zig to print each byte as an hexadecimal\nvalue to the terminal. You do that by using a `print()` statement with the `X` formatting specifier,\nlike you would normally do with the [`printf()` function](https://cplusplus.com/reference/cstdio/printf/)[^printfs] in C.\n\n[^printfs]: \n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n const string_literal = \"This is an example of string literal in Zig\";\n try stdout.print(\"Bytes that represents the string object: \", .{});\n for (string_literal) |byte| {\n try stdout.print(\"{X} \", .{byte});\n }\n try stdout.print(\"\\n\", .{});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nBytes that represents the string object: 54 68 69 \n 73 20 69 73 20 61 6E 20 65 78 61 6D 70 6C 65 20 6F\n F 66 20 73 74 72 69 6E 67 20 6C 69 74 65 72 61 6C 2\n 20 69 6E 20 5A 69 67 \n```\n\n\n:::\n:::\n\n\n### Strings in C\n\nAt first glance, this looks very similar to how C treats strings as well. That is, string values\nin C are also treated internally as an array of bytes, and this array is also null-terminated.\n\nBut one key difference between a Zig string and a C string, is that Zig also stores the length of\nthe array inside the string object. This small detail makes your code safer, because is much\neasier for the Zig compiler to check if you are trying to access an element that is \"out of bounds\", i.e. if\nyour trying to access memory that does not belong to you.\n\nTo achieve this same kind of safety in C, you have to do a lot of work that kind of seems pointless.\nSo getting this kind of safety is not automatic and much harder to do in C. For example, if you want\nto track the length of your string troughout your program in C, then, you first need to loop through\nthe array of bytes that represents this string, and find the null element (`'\\0'`) position to discover\nwhere exactly the array ends, or, in other words, to find how much elements the array of bytes contain.\n\nTo do that, you would need something like this in C. In this example, the C string stored in\nthe object `array` is 25 bytes long:\n\n```c\n#include \nint main() {\n char* array = \"An example of string in C\";\n int index = 0;\n while (1) {\n if (array[index] == '\\0') {\n break;\n }\n index++;\n }\n printf(\"Number of elements in the array: %d\\n\", index);\n}\n```\n\n```\nNumber of elements in the array: 25\n```\n\nBut in Zig, you do not have to do this, because the object already contains a `len`\nfield which stores the length information of the array. As an example, the `string_literal` object below is 43 bytes long:\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n const string_literal = \"This is an example of string literal in Zig\";\n try stdout.print(\"{d}\\n\", .{string_literal.len});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n43\n```\n\n\n:::\n:::\n\n\n\n### A better look at the object type\n\nNow, we can inspect better the type of objects that Zig create. To check the type of any object in Zig, you can use the\n`@TypeOf()` function. If we look at the type of the `simple_array` object below, you will find that this object\nis a array of 4 elements. Each element is a signed integer of 32 bits which corresponds to the data type `i32` in Zig.\nThat is what an object of type `[4]i32` is.\n\nBut if we look closely at the type of the `string_literal` object below, you will find that this object is a\nconstant pointer (hence the `*const` annotation) to an array of 43 elements (or 43 bytes). Each element is a\nsingle byte (more precisely, an unsigned 8 bit integer - `u8`), that is why we have the `[43:0]u8` portion of the type below.\nIn other words, the string stored inside the `string_literal` object is 43 bytes long.\nThat is why you have the type `*const [43:0]u8` below.\n\nIn the case of `string_literal`, it is a constant pointer (`*const`) because the object `string_literal` is declared\nas constant in the source code (in the line `const string_literal = ...`). So, if we changed that for some reason, if\nwe declare `string_literal` as a variable object (i.e. `var string_literal = ...`), then, `string_literal` would be\njust a normal pointer to an array of unsigned 8-bit integers (i.e. `* [43:0]u8`).\n\nNow, if we create an pointer to the `simple_array` object, then, we get a constant pointer to an array of 4 elements (`*const [4]i32`),\nwhich is very similar to the type of the `string_literal` object. This demonstrates that a string object (or a string literal)\nin Zig is already a pointer to an array.\n\nJust remember that a \"pointer to an array\" is different than an \"array\". So a string object in Zig is a pointer to an array\nof bytes, and not simply an array of bytes.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n const string_literal = \"This is an example of string literal in Zig\";\n const simple_array = [_]i32{1, 2, 3, 4};\n try stdout.print(\"Type of array object: {}\", .{@TypeOf(simple_array)});\n try stdout.print(\n \"Type of string object: {}\",\n .{@TypeOf(string_literal)}\n );\n try stdout.print(\n \"Type of a pointer that points to the array object: {}\",\n .{@TypeOf(&simple_array)}\n );\n}\n```\n:::\n\n\n```\nType of array object: [4]i32\nType of string object: *const [43:0]u8\nType of a pointer that points to\n the array object: *const [4]i32\n```\n\n\n### Byte vs unicode points\n\nIs important to point out that each byte in the array is not necessarily a single character.\nThis fact arises from the difference between a single byte and a single unicode point.\n\nThe encoding UTF-8 works by assigning a number (which is called a unicode point) to each character in\nthe string. For example, the character \"H\" is stored in UTF-8 as the decimal number 72. This means that\nthe number 72 is the unicode point for the character \"H\". Each possible character that can appear in a\nUTF-8 encoded string have its own unicode point.\n\nFor example, the Latin Capital Letter A With Stroke (Ⱥ) is represented by the number (or the unicode point)\n570. However, this decimal number (570) is higher than the maximum number stored inside a single byte, which\nis 255. In other words, the maximum decimal number that can be represented with a single byte is 255. That is why,\nthe unicode point 570 is actually stored inside the computer’s memory as the bytes `C8 BA`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n const string_literal = \"Ⱥ\";\n try stdout.print(\"Bytes that represents the string object: \", .{});\n for (string_literal) |char| {\n try stdout.print(\"{X} \", .{char});\n }\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nBytes that represents the string object: C8 BA \n```\n\n\n:::\n:::\n\n\n\nThis means that to store the character Ⱥ in an UTF-8 encoded string, we need to use two bytes together\nto represent the number 570. That is why the relationship between bytes and unicode points is not always\n1 to 1. Each unicode point is a single character in the string, but not always a single byte corresponds\nto a single unicode point.\n\nAll of this means that if you loop trough the elements of a string in Zig, you will be looping through the\nbytes that represents that string, and not through the characters of that string. In the Ⱥ example above,\nthe for loop needed two iterations (instead of a single iteration) to print the two bytes that represents this Ⱥ letter.\n\nNow, all english letters (or ASCII letters if you prefer) can be represented by a single byte in UTF-8. As a\nconsequence, if your UTF-8 string contains only english letters (or ASCII letters), then, you are lucky. Because\nthe number of bytes will be equal to the number of characters in that string. In other words, in this specific\nsituation, the relationship between bytes and unicode points is 1 to 1.\n\nBut on the other side, if your string contains other types of letters… for example, you might be working with\ntext data that contains, chinese, japanese or latin letters, then, the number of bytes necessary to represent\nyour UTF-8 string will likely be much higher than the number of characters in that string.\n\nIf you need to iterate through the characters of a string, instead of its bytes, then, you can use the\n`std.unicode.Utf8View` struct to create an iterator that iterates through the unicode points of your string.\n\nIn the example below, we loop through the japanese characters “アメリカ”. Each of the four characters in\nthis string is represented by three bytes. But the for loop iterates four times, one iteration for each\ncharacter/unicode point in this string:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\npub fn main() !void {\n var utf8 = (\n (try std.unicode.Utf8View.init(\"アメリカ\"))\n .iterator()\n );\n while (utf8.nextCodepointSlice()) |codepoint| {\n try stdout.print(\n \"got codepoint {}\\n\",\n .{std.fmt.fmtSliceHexUpper(codepoint)}\n );\n }\n}\n```\n:::\n\n\n```\ngot codepoint E382A2\ngot codepoint E383A1\ngot codepoint E383AA\ngot codepoint E382AB\n```\n\n\n\n## Safety in Zig\n\nA general trend in modern low-level programming languages is safety. As our modern world\nbecome more interconnected with techology and computers,\nthe data produced by all of this technology becomes one of the most important\n(and also, one of the most dangerous) assets that we have.\n\nThis is probably the main reason why modern low-level programming languages\nhave been giving great attention to safety, specially memory safety, because\nmemory corruption is still the main target for hackers to exploit.\nThe reality is that we don't have an easy solution for this problem.\nFor now, we only have techniques and strategies that mitigates these\nproblems.\n\nAs Richard Feldman explains on his [most recent GOTO conference talk](https://www.youtube.com/watch?v=jIZpKpLCOiU&ab_channel=GOTOConferences)[^gotop]\n, we haven't figured it out yet a way to achieve **true safety in technology**.\nIn other words, we haven't found a way to build software that won't be exploited\nwith 100% certainty. We can greatly reduce the risks of our software being\nexploited, by ensuring memory safety for example. But this is not enough\nto achieve \"true safety\" territory.\n\nBecause even if you write your program in a \"safe language\", hackers can still\nexploit failures in the operational system where your program is running (e.g. maybe the\nsystem where your code is running have a \"backdoor exploit\" that can still\naffect your code in unexpected ways), or also, they can exploit the features\nfrom the architecture of your computer. A recently found exploit\nthat involves memory invalidation through a feature of \"memory tags\"\npresent in ARM chips is an example of that [@exploit1].\n\n[^gotop]: \n\nThe question is: what Zig and other languages have been doing to mitigate this problem?\nIf we take Rust as an example, Rust is, for the most part[^rust-safe], a memory safe\nlanguage by enforcing specific rules to the developer. In other words, the key feature\nof Rust, the *borrow checker*, forces you to follow a specific logic when you are writing\nyour Rust code, and the Rust compiler will always complain everytime you try to go out of this\npattern.\n\n[^rust-safe]: Actually, a lot of existing Rust code is still memory unsafe, because they communicate with external libraries through FFI (*foreign function interface*), which disables the borrow-checker features through the `unsafe` keyword.\n\n\nIn contrast, the Zig language is not a memory safe language by default.\nInstead of forcing the developer to follow a specific rule, the Zig language\nachieves memory safety by offering tools that the developer can use for this purpose.\nIn other words, the `zig` compiler does not obligates you to use such tools.\nBut there is often no reason to not use these tools in your Zig code,\nso you often achieve a similar level of memory safety of Rust in Zig\nby simply using these tools.\n\nThe tools listed below are related to memory safety in Zig. That is, they help you to achieve\nmemory safety in your Zig code:\n\n- `defer` allows you to keep free operations phisically close to allocations. This helps you to avoid memory leaks, \"use after free\", and also \"double-free\" problems. Furthermore, it also keeps free operations logically tied to the end of the current scope, which greatly reduces the mental overhead about object lifetime.\n- `errdefer` helps you to garantee that your program frees the allocated memory, even if a runtime error occurs.\n- pointers and object are non-nullable by default. This helps you to avoid memory problems that might arise from de-referencing null pointers.\n- Zig offers some native types of allocators (called \"testing allocators\") that can detect memory leaks and double-frees. These types of allocators are widely used on unit tests, so they make your unit tests a weapon that you can use to detect memory problems in your code.\n- arrays and slices in Zig have their lengths embedded in the object itself, which makes the `zig` compiler very effective on detecting \"index out-of-range\" type of errors, and avoiding buffer overflows.\n\n\nDespite these features that Zig offers that are related to memory safety issues, the language\nalso have some rules that help you to achieve another type of safety, which is more related to\nprogram logic safety. These rules are:\n\n- pointers and objects are non-nullable by default. Which eliminates an edge case that might break the logic of your program.\n- switch statements must exaust all possible options.\n- the `zig` compiler forces you to handle every possible error.\n\n\n## Other parts of Zig\n\nWe already learned a lot about Zig's syntax, and also, some pretty technical\ndetails about it. Just as a quick recap:\n\n- We talked about how functions are written in Zig at @sec-root-file and @sec-main-file.\n- How to create new objects/identifiers at @sec-root-file and specially at @sec-assignments.\n- How strings work in Zig at @sec-zig-strings.\n- How to use arrays and slices at @sec-arrays.\n- How to import functionality from other Zig modules at @sec-root-file.\n\n\nBut, for now, this amount of knowledge is enough for us to continue with this book.\nLater, over the next chapters we will still talk more about other parts of\nZig's syntax that are also equally important as the other parts. Such as:\n\n\n- How Object-Oriented programming can be done in Zig through *struct declarations* at @sec-structs-and-oop.\n- Basic control flow syntax at @sec-zig-control-flow.\n- Enums at @sec-enum;\n- Pointers and Optionals at @sec-pointer;\n- Error handling with `try` and `catch` at @sec-error-handling;\n- Unit tests at @sec-unittests;\n- Vectors;\n- Build System at @sec-build-system;\n\n\n\n\n", + "supporting": [ + "01-zig-weird_files" + ], "filters": [ "rmarkdown/pagebreak.lua" ], diff --git a/_freeze/Chapters/03-structs/execute-results/html.json b/_freeze/Chapters/03-structs/execute-results/html.json index bd270cc..98912c7 100644 --- a/_freeze/Chapters/03-structs/execute-results/html.json +++ b/_freeze/Chapters/03-structs/execute-results/html.json @@ -1,9 +1,11 @@ { - "hash": "e2c0fdab9eab231f5e5fd3c47453361c", + "hash": "f4ef3a169c3185b855e4efe30ac406eb", "result": { "engine": "knitr", - "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n# Structs, Modules and Control Flow\n\nI introduced a lot of the Zig's syntax to you in the last chapter,\nspecially at @sec-root-file and @sec-main-file.\nBut we still need to discuss about some other very important\nelements of the language that you will use constantly on your day-to-day\nroutine.\n\nWe begin this chapter by talking about the different keywords and structures\nin Zig related to control flow (e.g. loops and if statements).\nThen, we talk about structs and how they can be used to do some\nbasic Object-Oriented (OOP) patterns in Zig. We also talk about\ntype inference, which help us to write less code and achieve the same results.\nFinally, we end this chapter by discussing modules, and how they relate\nto structs.\n\n\n\n## Control flow {#sec-zig-control-flow}\n\nSometimes, you need to make decisions in your program. Maybe you need to decide\nwether to execute or not a specific piece of code. Or maybe,\nyou need to apply the same operation over a sequence of values. These kinds of tasks,\ninvolve using structures that are capable of changing the \"control flow\" of our program.\n\nIn computer science, the term \"control flow\" usually refers to the order in which expressions (or commands)\nare evaluated in a given language or program. But this term is also used to refer\nto structures that are capable of changing this \"evaluation order\" of the commands\nexecuted by a given language/program.\n\nThese structures are better known\nby a set of terms, such as: loops, if/else statements, switch statements, among others. So,\nloops and if/else statements are examples of structures that can change the \"control\nflow\" of our program. The keywords `continue` and `break` are also examples of symbols\nthat can change the order of evaluation, since they can move our program to the next iteration\nof a loop, or make the loop stop completely.\n\n\n### If/else statements\n\nAn if/else statement performs an \"conditional flow operation\".\nA conditional flow control (or choice control) allows you to execute\nor ignore a certain block of commands based on a logical condition.\nMany programmers and computer science professionals also use\nthe term \"branching\" in this case.\nIn essence, we use if/else statements to use the result of a logical test\nto decide whether or not to execute a given block of commands.\n\nIn Zig, we write if/else statements by using the keywords `if` and `else`.\nWe start with the `if` keyword followed by a logical test inside a pair\nof parentheses, and then, a pair of curly braces with contains the lines\nof code to be executed in case the logical test returns the value `true`.\n\nAfter that, you can optionally add an `else` statement. Just add the `else`\nkeyword followed by a pair of curly braces, with the lines of code\nto executed in case the logical test defined in the `if`\nreturns `false`.\n\nIn the example below, we are testing if the object `x` contains a number\nthat is greater than 10. Judging by the output printed to the console,\nwe know that this logical test returned `false`. Because the output\nin the console is compatible with the line of code present in the\n`else` branch of the if/else statement.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst x = 5;\nif (x > 10) {\n try stdout.print(\n \"x > 10!\\n\", .{}\n );\n} else {\n try stdout.print(\n \"x <= 10!\\n\", .{}\n );\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nx <= 10!\n```\n\n\n:::\n:::\n\n\n\n\n### Swith statements {#sec-switch}\n\nSwitch statements are also available in Zig.\nA switch statement in Zig have a similar syntax to a switch statement in Rust.\nAs you would expect, to write a switch statement in Zig we use the `switch` keyword.\nWe provide the value that we want to \"switch over\" inside a\npair of parentheses. Then, we list the possible combinations (or \"branchs\")\ninside a pair of curly braces.\n\nLet's take a look at the code example below. You can see in this example that,\nI'm creating an enum type called `Role`. We talk more about enums at @sec-enum.\nBut in essence, this `Role` type is listing different types of roles in a fictitious\ncompany, like `SE` for Software Engineer, `DE` for Data Engineer, `PM` for Product Manager,\netc.\n\nNotice that we are using the value from the `role` object in the\nswitch statement, to discover which exact area we need to store in the `area` variable object.\nAlso notice that we are using type inference inside the switch statement, with the dot character,\nas we described at @sec-type-inference.\nThis makes the `zig` compiler infer the correct data type of the values (`PM`, `SE`, etc.) for us.\n\nAlso notice that, we are grouping multiple values in the same branch of the switch statement.\nWe just separate each possible value with a comma. So, for example, if `role` contains either `DE` or `DA`,\nthe `area` variable would contain the value `\"Data & Analytics\"`, instead of `\"Platform\"`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst Role = enum {\n SE, DPE, DE, DA, PM, PO, KS\n};\n\npub fn main() !void {\n var area: []const u8 = undefined;\n const role = Role.SE;\n switch (role) {\n .PM, .SE, .DPE, .PO => {\n area = \"Platform\";\n },\n .DE, .DA => {\n area = \"Data & Analytics\";\n },\n .KS => {\n area = \"Sales\";\n },\n }\n try stdout.print(\"{s}\\n\", .{area});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nPlatform\n```\n\n\n:::\n:::\n\n\nNow, one very important aspect about this switch statement presented\nin the code example above, is that it exhaust all existing possibilities.\nIn other words, all possible values that could be found inside the `order`\nobject are explicitly handled in this switch statement.\n\nSince the `role` object have type `Role`, the only possible values to\nbe found inside this object are `PM`, `SE`, `DPE`, `PO`, `DE`, `DA` and `KS`.\nThere is no other possible value to be stored in this `role` object.\nThis what \"exhaust all existing possibilities\" means. The switch statement covers\nevery possible case.\n\nIn Zig, switch statements must exhaust all existing possibilities. You cannot write\na switch statement, and leave an edge case with no expliciting action to be taken.\nThis is a similar behaviour to switch statements in Rust, which also have to\nhandle all possible cases.\n\nTake a look at the `dump_hex_fallible()` function below as an example. This function\nalso comes from the Zig Standard Library, but this time, it comes from the [`debug.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/debug.zig)[^debug-mod].\nThere are multiple lines in this function, but I omitted them to focus solely on the\nswitch statement found in this function. Notice that this switch statement have four\npossible cases, or four explicit branches. Also, notice that we used an `else` branch\nin this case. Whenever you have multiple possible cases in your switch statement\nwhich you want to apply the same exact action, you can use an `else` branch to do that.\n\n[^debug-mod]: \n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub fn dump_hex_fallible(bytes: []const u8) !void {\n // Many lines ...\n switch (byte) {\n '\\n' => try writer.writeAll(\"␊\"),\n '\\r' => try writer.writeAll(\"␍\"),\n '\\t' => try writer.writeAll(\"␉\"),\n else => try writer.writeByte('.'),\n }\n}\n```\n:::\n\n\nMany users would also use an `else` branch to handle a \"not supported\" case.\nThat is, a case that cannot be properly handled by your code, or, just a case that\nshould not be \"fixed\". So many programmers use an `else` branch to panic (or raise an error) to stop\nthe current execution.\n\nTake the code example below as an example. We can see that, we are handling the cases\nfor the `level` object being either 1, 2, or 3. All other possible cases are not supported by default,\nand, as consequence, we raise an runtime error in these cases, through the `@panic()` built-in function.\n\nAlso notice that, we are assigning the result of the switch statement to a new object called `category`.\nThis is another thing that you can do with switch statements in Zig. If the branchs in this switch\nstatement output some value as result, you can store the result value of the switch statement into\na new object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 1, 2 => \"beginner\",\n 3 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n:::\n\n\n```\nthread 13103 panic: Not supported level!\nt.zig:9:13: 0x1033c58 in main (switch2)\n @panic(\"Not supported level!\");\n ^\n```\n\nFurthermore, you can also use ranges of values in switch statements.\nThat is, you can create a branch in your switch statement that is used\nwhenever the input value is contained in a range. These range\nexpressions are created with the operator `...`. Is important\nto emphasize that the ranges created by this operator are\ninclusive on both ends.\n\nFor example, I could easily change the code example above to support all\nlevels between 0 and 100. Like this:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 0...25 => \"beginner\",\n 26...75 => \"intermediary\",\n 76...100 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nbeginner\n```\n\n\n:::\n:::\n\n\nThis is neat, and it works with character ranges too. That is, I could\nsimply write `'a'...'z'`, to match any character value that is a\nlowercase letter, and it would work fine.\n\n\n\n### The `defer` keyword {#sec-defer}\n\nWith the `defer` keyword you can execute expressions at the end of the current scope.\nTake the `foo()` function below as an example. When we execute this function, the expression\nthat prints the message \"Exiting function ...\" get's executed only at\nthe end of the function scope.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nfn foo() !void {\n defer std.debug.print(\n \"Exiting function ...\\n\", .{}\n );\n try stdout.print(\"Adding some numbers ...\\n\", .{});\n const x = 2 + 2; _ = x;\n try stdout.print(\"Multiplying ...\\n\", .{});\n const y = 2 * 8; _ = y;\n}\n\npub fn main() !void {\n try foo();\n}\n```\n:::\n\n\n```\nAdding some numbers ...\nMultiplying ...\nExiting function ...\n```\n\nIt doesn't matter how the function exits (i.e. because\nof an error, or, because of an return statement, or whatever),\njust remember, this expression get's executed when the function exits.\n\n\n\n\n### For loops\n\nA loop allows you to execute the same lines of code multiple times,\nthus, creating a \"repetition space\" in the execution flow of your program.\nLoops are particularly useful when we want to replicate the same function\n(or the same set of commands) over several different inputs.\n\nThere are different types of loops available in Zig. But the most\nessential of them all is probably the *for loop*. A for loop is\nused to apply the same piece of code over the elements of a slice or an array.\n\nFor loops in Zig have a slightly different syntax that you are\nprobably used to see in other languages. You start with the `for` keyword, then, you\nlist the items that you want to iterate\nover inside a pair of parentheses. Then, inside of a pair of pipes (`|`)\nyou should declare an identifier that will serve as your iterator, or,\nthe \"repetition index of the loop\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (items) |value| {\n // code to execute\n}\n```\n:::\n\n\nInstead of using a `(value in items)` syntax,\nin Zig, for loops use the syntax `(items) |value|`. In the example\nbelow, you can see that we are looping through the items\nof the array stored at the object `name`, and printing to the\nconsole the decimal representation of each character in this array.\n\nIf we wanted, we could also iterate through a slice (or a portion) of\nthe array, instead of iterating through the entire array stored in the `name` object.\nJust use a range selector to select the section you want. For example,\nI could provide the expression `name[0..3]` to the for loop, to iterate\njust through the first 3 elements in the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst name = [_]u8{'P','e','d','r','o'};\nfor (name) |char| {\n try stdout.print(\"{d} | \", .{char});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n80 | 101 | 100 | 114 | 111 | \n```\n\n\n:::\n:::\n\n\nIn the above example we are using the value itself of each\nelement in the array as our iterator. But there are many situations where\nwe need to use an index instead of the actual values of the items.\n\nYou can do that by providing a second set of items to iterate over.\nMore precisely, you provide the range selector `0..` to the for loop. So,\nyes, you can use two different iterators at the same time in a for\nloop in Zig.\n\nBut remember from @sec-assignments that, every object\nyou create in Zig must be used in some way. So if you declare two iterators\nin your for loop, you must use both iterators inside the for loop body.\nBut if you want to use just the index iterator, and not use the \"value iterator\",\nthen, you can discard the value iterator by maching the\nvalue items to the underscore character, like in the example below:\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (name, 0..) |_, i| {\n try stdout.print(\"{d} | \", .{i});\n}\n```\n:::\n\n\n```\n0 | 1 | 2 | 3 | 4 |\n```\n\n\n### While loops\n\nA while loop is created from the `while` keyword. A `for` loop\niterates through the items of an array, but a `while` loop\nwill loop continuously, and infinitely, until a logical test\n(specified by you) becomes false.\n\nYou start with the `while` keyword, then, you define a logical\nexpression inside a pair of parentheses, and the body of the\nloop is provided inside a pair of curly braces, like in the example below:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: u8 = 1;\nwhile (i < 5) {\n try stdout.print(\"{d} | \", .{i});\n i += 1;\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 2 | 3 | 4 | \n```\n\n\n:::\n:::\n\n\n\n\n### Using `break` and `continue`\n\nIn Zig, you can explicitly stop the execution of a loop, or, jump to the next iteration of the loop, using\nthe keywords `break` and `continue`, respectively. The `while` loop present in the example below, is\nat first sight, an infinite loop. Because the logical value inside the parenthese will always be equal to `true`.\nWhat makes this `while` loop stop when the `i` object reaches the count\n10? Is the `break` keyword!\n\nInside the while loop, we have an if statement that is constantly checking if the `i` variable\nis equal to 10. Since we are increasing the value of this `i` variable at each iteration of the\nwhile loop. At some point, this `i` variable will be equal to 10, and when it does, the if statement\nwill execute the `break` expression, and, as a result, the execution of the while loop is stopped.\n\nNotice the `expect()` function from the Zig standard library after the while loop.\nThis `expect()` function is an \"assert\" type of function.\nThis function checks if the logical test provided is equal to true. If this logical test is false,\nthe function raises an assertion error. But it is equal to true, then, the function will do nothing.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: usize = 0;\nwhile (true) {\n if (i == 10) {\n break;\n }\n i += 1;\n}\ntry std.testing.expect(i == 10);\ntry stdout.print(\"Everything worked!\", .{});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nEverything worked!\n```\n\n\n:::\n:::\n\n\nSince this code example was executed succesfully by the `zig` compiler,\nwithout raising any errors, then, we known that, after the execution of while loop,\nthe `i` variable is equal to 10. Because if it wasn't equal to 10, then, an error would\nbe raised by `expect()`.\n\nNow, in the next example, we have an use case for\nthe `continue` keyword. The if statement is constantly\nchecking if the current index is a multiple of 2. If\nit is, then we jump to the next iteration of the loop\ndirectly. But it the current index is not a multiple of 2,\nthen, the loop will simply print this index to the console.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [_]u8{1,2,3,4,5,6};\nfor (ns) |i| {\n if ((i % 2) == 0) {\n continue;\n }\n try stdout.print(\"{d} | \", .{i});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 3 | 5 | \n```\n\n\n:::\n:::\n\n\n\n## Structs and OOP {#sec-structs-and-oop}\n\nZig is a language more closely related to C (which is a procedural language),\nthan it is to C++ or Java (which are object-oriented languages). Because of that, you do not\nhave advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or\nclass inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\n\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C.\nYou give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can\nalso register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object\nthat you create with this new type, will always have these methods available and associated with them.\n\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object\nof this particular class, and you also have a destructor method (or a destructor function) that\nis the function responsible for destroying every object of this class.\n\nIn Zig, we normally declare the constructor and the destructor methods\nof our structs, by declaring an `init()` and a `deinit()` methods inside the struct.\nThis is just a naming convention that you will find across the entire Zig standard library.\nSo, in Zig, the `init()` method of a struct is normally the constructor method of the class represented by this struct.\nWhile the `deinit()` method is the method used for destroying an existing instance of that struct.\n\nThe `init()` and `deinit()` methods are both used extensively in Zig code, and you will see both of\nthem being used when we talk about allocators at @sec-allocators.\nBut, as another example, let's build a simple `User` struct to represent an user of some sort of system.\nIf you look at the `User` struct below, you can see the `struct` keyword, and inside of a\npair of curly braces, we write the struct's body.\n\nNotice the data members of this struct, `id`, `name` and `email`. Every data member have it's\ntype explicitly annotated, with the colon character (`:`) syntax that we described earlier at @sec-root-file.\nBut also notice that every line in the struct body that describes a data member, ends with a comma character (`,`).\nSo every time you declare a data member in your Zig code, always end the line with a comma character, instead\nof ending it with the traditional semicolon character (`;`).\n\nNext, also notice in this example, that we registrated an `init()` function as a method\nof this `User` struct. This `init()` method is the constructor method that you use to instantiate\nevery new `User` object. That is why this `init()` function return an `User` object as result.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\npedro\n```\n\n\n:::\n:::\n\n\nThe `pub` keyword plays an important role in struct declarations, and OOP in Zig.\nEvery method that you declare in your struct that is marked with the keyword `pub`,\nbecomes a public method of this particular struct.\n\nSo every method that you create in your struct, is, at first, a private method\nof that struct. Meaning that, this method can only be called from within this\nstruct. But, if you mark this method as public, with the keyword `pub`, then,\nyou can call the method directly from the `User` object you have\nin your code.\n\nIn other words, the functions marked by the keyword `pub`\nare members of the public API of that struct.\nFor example, if I did not marked the `print_name()` method as public,\nthen, I could not execute the line `u.print_name()`. Because I would\nnot be authorized to call this method directly in my code.\n\n\n\n### Anonymous struct literals {#sec-anonymous-struct-literals}\n\nYou can declare a struct object as a literal value. When we do that, we normally specify the\ndata type of this struct literal by writing it's data type just before the opening curly braces.\nFor example, I could write a struct literal of type `User` that we defined in the previous section like\nthis:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n```\n:::\n\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a\nstruct literal, but not especify explicitly the type of this particular struct.\nAn anonymous struct is written by using the syntax `.{}`. So, we essentially\nreplaced the explicit type of the struct literal with a dot character (`.`).\n\nAs we described at @sec-type-inference, when you put a dot before a struct literal,\nthe type of this struct literal is automatically inferred by the `zig` compiler.\nIn essence, the `zig` compiler will look for some hint of what is the type of that struct.\nIt can be the type annotation of an function argument,\nor the return type annotation of the function that you are using, or the type annotation\nof a variable.\nIf the compiler do find such type annotation, then, it will use this\ntype in your literal struct. \n\nAnonymous structs are very commom to use in function arguments in Zig.\nOne example that you have seen already constantly, is the `print()`\nfunction from the `stdout` object.\nThis function takes two arguments.\nThe first argument, is a template string, which should\ncontain string format specifiers in it, which tells how the values provided\nin the second argument should be printed into the message.\n\nWhile the second argument is a struct literal that lists the values\nto be printed into the template message specified in the first argument.\nYou normally want to use an anonymous struct literal here, so that, the\n`zig` compiler do the job of specifying the type of this particular\nanonymous struct for you.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHello, world!\n```\n\n\n:::\n:::\n\n\n\n\n### Struct declarations must be constant\n\nTypes in Zig must be `const` or `comptime` (we are going to talk more about comptime at @sec-comptime).\nWhat this means is that you cannot create a new data type, and mark it as variable with the `var` keyword.\nSo struct declarations are always constant. You cannot declare a new struct using the `var` keyword.\nIt must be `const`.\n\nIn the `Vec3` example below, this declaration is allowed because I'm using the `const` keyword\nto declare this new data type.\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n```\n:::\n\n\n\n### The `self` method argument\n\nIn every language that have OOP, when we declare a method of some class or struct, we\nusually declare this method as a function that have a `self` argument.\nThis `self` argument is the reference to the object itself from which the method\nis being called from.\n\nIs not mandatory to use this `self` argument. But why would you not use this `self` argument?\nThere is no reason to not use it. Because the only way to get access to the data stored in the\ndata members of your struct is to access them through this `self` argument.\nIf you don't need to use the data in the data members of your struct inside your method, then, you very likely don't need\na method, you can just simply declare this logic as a simple function, outside of your\nstruct declaration.\n\n\nTake the `Vec3` struct below. Inside this `Vec3` struct we declared a method named `distance()`.\nThis method calculates the distance between two `Vec3` objects, by following the distance\nformula in euclidean space. Notice that this `distance()` method takes two `Vec3` objects\nas input, `self` and `other`.\n\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n```\n:::\n\n\n\nThe `self` argument corresponds to the `Vec3` object from which this `distance()` method\nis being called from. While the `other` is a separate `Vec3` object that is given as input\nto this method. In the example below, the `self` argument corresponds to the object\n`v1`, because the `distance()` method is being called from the `v1` object,\nwhile the `other` argument corresponds to the object `v2`.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n```\n:::\n\n\n```\nDistance: 3.3970575502926055\n```\n\n\n\n### About the struct state\n\nSometimes you don't need to care about the state of your struct object. Sometimes, you just need\nto instantiate and use the objects, without altering their state. You can notice that when you have methods\ninside this struct object that might use the values that are present the data members, but they\ndo not alter the values in the data members of structs in anyway.\n\nThe `Vec3` struct that we presented in the previous section is an example of that.\nThis struct have a single method named `distance()`, and this method do use the values\npresent in all three data members of the struct (`x`, `y` and `z`). But at the same time,\nthis method do not change the values of these data members in any point.\n\nAs a result of that, when we create `Vec3` objects we usually create them as\nconstant objects, like the `v1` and `v2` objects presented in the previous\ncode example. We can create them as variable objects with the `var` keyword,\nif we want to. But because the methods of this `Vec3` struct do not change\nthe state of the objects in any point, is unnecessary to mark them\nas variable objects.\n\nBut why? Why am I talkin about this here? Is because the `self` argument\nin the methods is affected depending on whether the\nmethods present in a struct change or not the state of the object itself.\nMore specifically, when you have a method in a struct that changes the state\nof the object (i.e. change the value of a data member), the `self` argument\nin this method must be annotated in a different manner.\n\nAs I described in the previous section, the `self` argument in methods of\na struct is the argument that receives as input the object from which the method\nwas called from. We usually annotate this argument in the methods by writing `self`,\nfollowed by the colon character (`:`), and the data type of the struct to which\nthe method belongs to (e.g. `User`, `Vec3`, etc.).\n\nIf we take the `Vec3` struct that we defined in the previous section as an example,\nwe can see in the `distance()` method that this `self` argument is annotated as\n`self: Vec3`. Because the state of the `Vec3` object is never altered by this\nmethod.\n\nBut what if we do have a method that alters the state of the object, by altering the\nvalues of it's data members. How should we annotate `self` in this instance? The answer is:\n\"we should pass a pointer of `x` to `self`, and not simply a copy of `x` to `self`\".\nIn other words, you should annotate `self` as `self: *x`, instead of annotating it\nas `self: x`.\n\nIf we create a new method inside the `Vec3` object that, for example, expands the\nvector by multiplying it's coordinates by a factor o two, then, we need to follow\nthis rule specified in the previous paragraph. The code example below demonstrates\nthis idea:\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n```\n:::\n\n\nNotice in the code example above that we have added a new method\nto our `Vec3` struct named `double()`. This method essentially doubles the\ncoordinate values of our vector object. Also notice that, in the\ncase of the `double()` method, we annotated the `self` argument as `*Vec3`,\nindicating that this argument receives a pointer (or a reference, if you prefer to call it this way)\nto a `Vec3` object, instead of receiving a copy of the object directly, as input.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n```\n:::\n\n\n```\nDoubled: 8.4\n```\n\n\n\nIf you change the `self` argument in this `double()` method to `self: Vec3`, like in the\n`distance()` method, you will get the error exposed below as result. Notice that this\nerror message is indicating a line from the `double()` method body,\nindicating that you cannot alter the value of the `x` data member.\n\n```zig\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\n```\n\nThis error message indicates that the `x` data member belongs to a constant object,\nand, because of that, it cannot be changed. Even though we marked the `v3` object\nthat we have created in the previous code example as a variable object.\nSo even though this `x` data member belongs to a variable object in our code, this error\nmessage is pointing to the opposite direction.\n\n```\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\n```\n\nBut this error message is misleading, because the only thing that we have changed\nis the `self` argument signature from `*Vec3` to `Vec3` in the `double()` method. So, just remember this\ngeneral rule below about your method declarations:\n\n::: {.callout-note}\nIf a method of your `x` struct alters the state of the object, by\nchanging the value of any data member, then, remember to use `self: *x`,\ninstead of `self: x` in the function signature of this method.\n:::\n\nYou could also interpret the content discussed in this section as:\n\"if you need to alter the state of your `x` struct object in one of it's methods,\nyou must pass the `x` struct object by reference to the `self` argument of this method,\ninstead of passing it by value\".\n\n\n\n## Type inference {#sec-type-inference}\n\nZig is kind of a strongly typed language. I say \"kind of\" because there are situations\nwhere you don't have to explicitly write the type of every single object in your source code,\nas you would expect from a traditional strongly typed language, such as C and C++.\n\nIn some situations, the `zig` compiler can use type inference to solves the data types for you, easing some of\nthe burden that you carry as a developer.\nThe most commom way this happens is through function arguments that receives struct objects\nas input.\n\nIn general, type inference in Zig is done by using the dot character (`.`).\nEverytime you see a dot character written before a struct literal, or before a enum value, or something like that,\nyou know that this dot character is playing a special party in this place. More specifically, it is\ntelling the `zig` compiler something on the lines of: \"Hey! Can you infer the type of this\nvalue for me? Please!\". In other words, this dot character is playing a role similar to the `auto` keyword in C++.\n\nI give you some examples of this at @sec-anonymous-struct-literals, where we present anonymous struct literals.\nAnonymous struct literals are, essentially, struct literals that use type inference to\ninfer the exact type of this particular struct literal.\nThis type inference is done by looking for some minimal hint of the correct data type to be used.\nYou could say that the `zig` compiler looks for any neighbouring type annotation that might tell him\nwhat would be the correct type.\n\nAnother commom place where we use type inference in Zig is at switch statements (which we talk about at @sec-switch).\nSo I also gave some other examples of type inference at @sec-switch, where we were inferring the data types of enum values listed inside\nof switch statements (e.g. `.DE`).\nBut as another example, take a look at this `fence()` function reproduced below,\nwhich comes from the [`atomic.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/atomic.zig)[^fence-fn]\nof the Zig Standard Library.\n\n[^fence-fn]: .\n\nThere are a lot of things in this function that we haven't talked about yet, such as:\nwhat `comptime` means? `inline`? `extern`? What is this star symbol before `Self`?\nLet's just ignore all of these things, and focus solely on the switch statement\nthat is inside this function.\n\nWe can see that this switch statement uses the `order` object as input. This `order`\nobject is one of the inputs of this `fence()` function, and we can see in the type annotation,\nthat this object is of type `AtomicOrder`. We can also see a bunch of values inside the\nswitch statements that begins with a dot character, such as `.release` and `.acquire`.\n\nBecause these weird values contain a dot character before them, we are asking the `zig`\ncompiler to infer the types of these values inside the switch statement. Then, the `zig`\ncompiler is looking into the current context where these values are being used, and it is\ntrying to infer the types of these values.\n\nSince they are being used inside a switch statement, the `zig` compiler looks into the type\nof the input object given to the switch statement, which is the `order` object in this case.\nBecause this object have type `AtomicOrder`, the `zig` compiler infers that these values\nare data members from this type `AtomicOrder`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub inline fn fence(self: *Self, comptime order: AtomicOrder) void {\n // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.\n if (builtin.sanitize_thread) {\n const tsan = struct {\n extern \"c\" fn __tsan_acquire(addr: *anyopaque) void;\n extern \"c\" fn __tsan_release(addr: *anyopaque) void;\n };\n\n const addr: *anyopaque = self;\n return switch (order) {\n .unordered, .monotonic => @compileError(@tagName(order) ++ \" only applies to atomic loads and stores\"),\n .acquire => tsan.__tsan_acquire(addr),\n .release => tsan.__tsan_release(addr),\n .acq_rel, .seq_cst => {\n tsan.__tsan_acquire(addr);\n tsan.__tsan_release(addr);\n },\n };\n }\n\n return @fence(order);\n}\n```\n:::\n\n\nThis is how basic type inference is done in Zig. If we didn't use the dot character before\nthe values inside this switch statement, then, we would be forced to write explicitly\nthe data types of these values. For example, instead of writing `.release` we would have to\nwrite `AtomicOrder.release`. We would have to do this for every single value\nin this switch statement, and this is a lot of work. That is why type inference\nis commonly used on switch statements in Zig.\n\n\n\n## Modules\n\nWe already talked about what modules are, and also, how to import other modules into\nyou current module through *import statements*, so that you can use functionality from these other modules in\nyour current module.\nBut in this section, I just want to make it clear that modules are actually structs in Zig.\n\nIn other words, every Zig module (i.e. a `.zig` file) that you write in your project\nis internally stored as a struct object.\nTake the line exposed below as an example. In this line we are importing the\nZig Standard Library into our current module.\n\n```zig\nconst std = @import(\"std\");\n```\n\nWhen we want to access the functions and objects from the standard library, we\nare basically accessing the data members of the struct stored in the `std`\nobject. That is why we use the same syntax that we use in normal structs, with the dot operator (`.`)\nto access the data members and methods of the struct.\n\nWhen this \"import statement\" get's executed, the result of this expression is a struct\nobject that contains the Zig Standard Library modules, global variables, functions, etc.\nAnd this struct object get's saved (or stored) inside the constant object named `std`.\n\n\nTake the [`thread_pool.zig` module from the project `zap`](https://github.com/kprotty/zap/blob/blog/src/thread_pool.zig)[^thread]\nas an example. This module is written as if it was\na big struct. That is why we have a top-level and public `init()` method\nwritten in this module. The idea is that all top-level functions written in this\nmodule are methods from the struct, and all top-level objects and struct declarations\nare data members of this struct. The module is the struct itself.\n\n[^thread]: \n\n\nSo you would import and use this module by doing something like this:\n\n```zig\nconst std = @import(\"std\");\nconst ThreadPool = @import(\"thread_pool.zig\");\nconst num_cpus = std.Thread.getCpuCount()\n catch @panic(\"failed to get cpu core count\");\nconst num_threads = std.math.cast(u16, num_cpus)\n catch std.math.maxInt(u16);\nconst pool = ThreadPool.init(\n .{ .max_threads = num_threads }\n);\n```\n", - "supporting": [], + "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n# Structs, Modules and Control Flow\n\nI introduced a lot of the Zig's syntax to you in the last chapter,\nspecially at @sec-root-file and @sec-main-file.\nBut we still need to discuss about some other very important\nelements of the language that you will use constantly on your day-to-day\nroutine.\n\nWe begin this chapter by talking about the different keywords and structures\nin Zig related to control flow (e.g. loops and if statements).\nThen, we talk about structs and how they can be used to do some\nbasic Object-Oriented (OOP) patterns in Zig. We also talk about\ntype inference, which help us to write less code and achieve the same results.\nFinally, we end this chapter by discussing modules, and how they relate\nto structs.\n\n\n\n## Control flow {#sec-zig-control-flow}\n\nSometimes, you need to make decisions in your program. Maybe you need to decide\nwether to execute or not a specific piece of code. Or maybe,\nyou need to apply the same operation over a sequence of values. These kinds of tasks,\ninvolve using structures that are capable of changing the \"control flow\" of our program.\n\nIn computer science, the term \"control flow\" usually refers to the order in which expressions (or commands)\nare evaluated in a given language or program. But this term is also used to refer\nto structures that are capable of changing this \"evaluation order\" of the commands\nexecuted by a given language/program.\n\nThese structures are better known\nby a set of terms, such as: loops, if/else statements, switch statements, among others. So,\nloops and if/else statements are examples of structures that can change the \"control\nflow\" of our program. The keywords `continue` and `break` are also examples of symbols\nthat can change the order of evaluation, since they can move our program to the next iteration\nof a loop, or make the loop stop completely.\n\n\n### If/else statements\n\nAn if/else statement performs an \"conditional flow operation\".\nA conditional flow control (or choice control) allows you to execute\nor ignore a certain block of commands based on a logical condition.\nMany programmers and computer science professionals also use\nthe term \"branching\" in this case.\nIn essence, we use if/else statements to use the result of a logical test\nto decide whether or not to execute a given block of commands.\n\nIn Zig, we write if/else statements by using the keywords `if` and `else`.\nWe start with the `if` keyword followed by a logical test inside a pair\nof parentheses, and then, a pair of curly braces with contains the lines\nof code to be executed in case the logical test returns the value `true`.\n\nAfter that, you can optionally add an `else` statement. Just add the `else`\nkeyword followed by a pair of curly braces, with the lines of code\nto executed in case the logical test defined in the `if`\nreturns `false`.\n\nIn the example below, we are testing if the object `x` contains a number\nthat is greater than 10. Judging by the output printed to the console,\nwe know that this logical test returned `false`. Because the output\nin the console is compatible with the line of code present in the\n`else` branch of the if/else statement.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst x = 5;\nif (x > 10) {\n try stdout.print(\n \"x > 10!\\n\", .{}\n );\n} else {\n try stdout.print(\n \"x <= 10!\\n\", .{}\n );\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nx <= 10!\n```\n\n\n:::\n:::\n\n\n\n\n### Swith statements {#sec-switch}\n\nSwitch statements are also available in Zig.\nA switch statement in Zig have a similar syntax to a switch statement in Rust.\nAs you would expect, to write a switch statement in Zig we use the `switch` keyword.\nWe provide the value that we want to \"switch over\" inside a\npair of parentheses. Then, we list the possible combinations (or \"branchs\")\ninside a pair of curly braces.\n\nLet's take a look at the code example below. You can see in this example that,\nI'm creating an enum type called `Role`. We talk more about enums at @sec-enum.\nBut in essence, this `Role` type is listing different types of roles in a fictitious\ncompany, like `SE` for Software Engineer, `DE` for Data Engineer, `PM` for Product Manager,\netc.\n\nNotice that we are using the value from the `role` object in the\nswitch statement, to discover which exact area we need to store in the `area` variable object.\nAlso notice that we are using type inference inside the switch statement, with the dot character,\nas we described at @sec-type-inference.\nThis makes the `zig` compiler infer the correct data type of the values (`PM`, `SE`, etc.) for us.\n\nAlso notice that, we are grouping multiple values in the same branch of the switch statement.\nWe just separate each possible value with a comma. So, for example, if `role` contains either `DE` or `DA`,\nthe `area` variable would contain the value `\"Data & Analytics\"`, instead of `\"Platform\"`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst Role = enum {\n SE, DPE, DE, DA, PM, PO, KS\n};\n\npub fn main() !void {\n var area: []const u8 = undefined;\n const role = Role.SE;\n switch (role) {\n .PM, .SE, .DPE, .PO => {\n area = \"Platform\";\n },\n .DE, .DA => {\n area = \"Data & Analytics\";\n },\n .KS => {\n area = \"Sales\";\n },\n }\n try stdout.print(\"{s}\\n\", .{area});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nPlatform\n```\n\n\n:::\n:::\n\n\nNow, one very important aspect about this switch statement presented\nin the code example above, is that it exhaust all existing possibilities.\nIn other words, all possible values that could be found inside the `order`\nobject are explicitly handled in this switch statement.\n\nSince the `role` object have type `Role`, the only possible values to\nbe found inside this object are `PM`, `SE`, `DPE`, `PO`, `DE`, `DA` and `KS`.\nThere is no other possible value to be stored in this `role` object.\nThis what \"exhaust all existing possibilities\" means. The switch statement covers\nevery possible case.\n\nIn Zig, switch statements must exhaust all existing possibilities. You cannot write\na switch statement, and leave an edge case with no expliciting action to be taken.\nThis is a similar behaviour to switch statements in Rust, which also have to\nhandle all possible cases.\n\nTake a look at the `dump_hex_fallible()` function below as an example. This function\nalso comes from the Zig Standard Library, but this time, it comes from the [`debug.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/debug.zig)[^debug-mod].\nThere are multiple lines in this function, but I omitted them to focus solely on the\nswitch statement found in this function. Notice that this switch statement have four\npossible cases, or four explicit branches. Also, notice that we used an `else` branch\nin this case. Whenever you have multiple possible cases in your switch statement\nwhich you want to apply the same exact action, you can use an `else` branch to do that.\n\n[^debug-mod]: \n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub fn dump_hex_fallible(bytes: []const u8) !void {\n // Many lines ...\n switch (byte) {\n '\\n' => try writer.writeAll(\"␊\"),\n '\\r' => try writer.writeAll(\"␍\"),\n '\\t' => try writer.writeAll(\"␉\"),\n else => try writer.writeByte('.'),\n }\n}\n```\n:::\n\n\nMany users would also use an `else` branch to handle a \"not supported\" case.\nThat is, a case that cannot be properly handled by your code, or, just a case that\nshould not be \"fixed\". So many programmers use an `else` branch to panic (or raise an error) to stop\nthe current execution.\n\nTake the code example below as an example. We can see that, we are handling the cases\nfor the `level` object being either 1, 2, or 3. All other possible cases are not supported by default,\nand, as consequence, we raise an runtime error in these cases, through the `@panic()` built-in function.\n\nAlso notice that, we are assigning the result of the switch statement to a new object called `category`.\nThis is another thing that you can do with switch statements in Zig. If the branchs in this switch\nstatement output some value as result, you can store the result value of the switch statement into\na new object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 1, 2 => \"beginner\",\n 3 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n:::\n\n\n```\nthread 13103 panic: Not supported level!\nt.zig:9:13: 0x1033c58 in main (switch2)\n @panic(\"Not supported level!\");\n ^\n```\n\nFurthermore, you can also use ranges of values in switch statements.\nThat is, you can create a branch in your switch statement that is used\nwhenever the input value is contained in a range. These range\nexpressions are created with the operator `...`. Is important\nto emphasize that the ranges created by this operator are\ninclusive on both ends.\n\nFor example, I could easily change the code example above to support all\nlevels between 0 and 100. Like this:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 0...25 => \"beginner\",\n 26...75 => \"intermediary\",\n 76...100 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nbeginner\n```\n\n\n:::\n:::\n\n\nThis is neat, and it works with character ranges too. That is, I could\nsimply write `'a'...'z'`, to match any character value that is a\nlowercase letter, and it would work fine.\n\n\n\n### The `defer` keyword {#sec-defer}\n\nWith the `defer` keyword you can execute expressions at the end of the current scope.\nTake the `foo()` function below as an example. When we execute this function, the expression\nthat prints the message \"Exiting function ...\" get's executed only at\nthe end of the function scope.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nfn foo() !void {\n defer std.debug.print(\n \"Exiting function ...\\n\", .{}\n );\n try stdout.print(\"Adding some numbers ...\\n\", .{});\n const x = 2 + 2; _ = x;\n try stdout.print(\"Multiplying ...\\n\", .{});\n const y = 2 * 8; _ = y;\n}\n\npub fn main() !void {\n try foo();\n}\n```\n:::\n\n\n```\nAdding some numbers ...\nMultiplying ...\nExiting function ...\n```\n\nIt doesn't matter how the function exits (i.e. because\nof an error, or, because of an return statement, or whatever),\njust remember, this expression get's executed when the function exits.\n\n\n\n\n### For loops\n\nA loop allows you to execute the same lines of code multiple times,\nthus, creating a \"repetition space\" in the execution flow of your program.\nLoops are particularly useful when we want to replicate the same function\n(or the same set of commands) over several different inputs.\n\nThere are different types of loops available in Zig. But the most\nessential of them all is probably the *for loop*. A for loop is\nused to apply the same piece of code over the elements of a slice or an array.\n\nFor loops in Zig have a slightly different syntax that you are\nprobably used to see in other languages. You start with the `for` keyword, then, you\nlist the items that you want to iterate\nover inside a pair of parentheses. Then, inside of a pair of pipes (`|`)\nyou should declare an identifier that will serve as your iterator, or,\nthe \"repetition index of the loop\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (items) |value| {\n // code to execute\n}\n```\n:::\n\n\nInstead of using a `(value in items)` syntax,\nin Zig, for loops use the syntax `(items) |value|`. In the example\nbelow, you can see that we are looping through the items\nof the array stored at the object `name`, and printing to the\nconsole the decimal representation of each character in this array.\n\nIf we wanted, we could also iterate through a slice (or a portion) of\nthe array, instead of iterating through the entire array stored in the `name` object.\nJust use a range selector to select the section you want. For example,\nI could provide the expression `name[0..3]` to the for loop, to iterate\njust through the first 3 elements in the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst name = [_]u8{'P','e','d','r','o'};\nfor (name) |char| {\n try stdout.print(\"{d} | \", .{char});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n80 | 101 | 100 | 114 | 111 | \n```\n\n\n:::\n:::\n\n\nIn the above example we are using the value itself of each\nelement in the array as our iterator. But there are many situations where\nwe need to use an index instead of the actual values of the items.\n\nYou can do that by providing a second set of items to iterate over.\nMore precisely, you provide the range selector `0..` to the for loop. So,\nyes, you can use two different iterators at the same time in a for\nloop in Zig.\n\nBut remember from @sec-assignments that, every object\nyou create in Zig must be used in some way. So if you declare two iterators\nin your for loop, you must use both iterators inside the for loop body.\nBut if you want to use just the index iterator, and not use the \"value iterator\",\nthen, you can discard the value iterator by maching the\nvalue items to the underscore character, like in the example below:\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (name, 0..) |_, i| {\n try stdout.print(\"{d} | \", .{i});\n}\n```\n:::\n\n\n```\n0 | 1 | 2 | 3 | 4 |\n```\n\n\n### While loops\n\nA while loop is created from the `while` keyword. A `for` loop\niterates through the items of an array, but a `while` loop\nwill loop continuously, and infinitely, until a logical test\n(specified by you) becomes false.\n\nYou start with the `while` keyword, then, you define a logical\nexpression inside a pair of parentheses, and the body of the\nloop is provided inside a pair of curly braces, like in the example below:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: u8 = 1;\nwhile (i < 5) {\n try stdout.print(\"{d} | \", .{i});\n i += 1;\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 2 | 3 | 4 | \n```\n\n\n:::\n:::\n\n\n\n\n### Using `break` and `continue`\n\nIn Zig, you can explicitly stop the execution of a loop, or, jump to the next iteration of the loop, using\nthe keywords `break` and `continue`, respectively. The `while` loop present in the example below, is\nat first sight, an infinite loop. Because the logical value inside the parenthese will always be equal to `true`.\nWhat makes this `while` loop stop when the `i` object reaches the count\n10? Is the `break` keyword!\n\nInside the while loop, we have an if statement that is constantly checking if the `i` variable\nis equal to 10. Since we are increasing the value of this `i` variable at each iteration of the\nwhile loop. At some point, this `i` variable will be equal to 10, and when it does, the if statement\nwill execute the `break` expression, and, as a result, the execution of the while loop is stopped.\n\nNotice the `expect()` function from the Zig standard library after the while loop.\nThis `expect()` function is an \"assert\" type of function.\nThis function checks if the logical test provided is equal to true. If this logical test is false,\nthe function raises an assertion error. But it is equal to true, then, the function will do nothing.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: usize = 0;\nwhile (true) {\n if (i == 10) {\n break;\n }\n i += 1;\n}\ntry std.testing.expect(i == 10);\ntry stdout.print(\"Everything worked!\", .{});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nEverything worked!\n```\n\n\n:::\n:::\n\n\nSince this code example was executed succesfully by the `zig` compiler,\nwithout raising any errors, then, we known that, after the execution of while loop,\nthe `i` variable is equal to 10. Because if it wasn't equal to 10, then, an error would\nbe raised by `expect()`.\n\nNow, in the next example, we have an use case for\nthe `continue` keyword. The if statement is constantly\nchecking if the current index is a multiple of 2. If\nit is, then we jump to the next iteration of the loop\ndirectly. But it the current index is not a multiple of 2,\nthen, the loop will simply print this index to the console.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [_]u8{1,2,3,4,5,6};\nfor (ns) |i| {\n if ((i % 2) == 0) {\n continue;\n }\n try stdout.print(\"{d} | \", .{i});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 3 | 5 | \n```\n\n\n:::\n:::\n\n\n\n\n## Function parameters are immutable {#sec-fun-pars}\n\nWe have already discussed a lot of the syntax behind function declarations at @sec-root-file and @sec-main-file.\nBut I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig.\nIn summary, function parameters are immutable in Zig.\n\nTake the code example below, where we declare a simple function that just tries to add\nsome amount to the input integer, and returns the result back. But if you look closely\nat the body of this `add2()` function, you will notice that we try\nto save the result back into the `x` function argument.\n\nIn other words, this function not only use the value that it received through the function argument\n`x`, but it also tries to change the value of this function argument, by assigning the addition result\ninto `x`. However, function arguments in Zig are immutable. You cannot change their values, or, you\ncannot assign values to them inside the body's function.\n\nThis is the reason why, the code example below do not compile successfully. If you try to compile\nthis code example, you get a compile error warning you that you are trying to change the value of a\nimmutable (i.e. constant) object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: u32) u32 {\n x = x + 2;\n return x;\n}\n\npub fn main() !void {\n const y = add2(4);\n std.debug.print(\"{d}\\n\", .{y});\n}\n```\n:::\n\n\n```\nt.zig:3:5: error: cannot assign to constant\n x = x + 2;\n ^\n```\n\n\nIf a function argument receives as input a object whose data type is\nany of the primitive types that we have listed at @sec-primitive-data-types\nthis object is always passed by value to the function. In other words, this object\nis copied to the function stack frame.\n\nHowever, if the input object have a more complex data type, for example, it might\nbe a struct instance, or an array, or a union, etc., in cases like that, the `zig` compiler\nwill take the liberty of deciding for you which strategy is best. The `zig` compiler will\npass your object to the function either by value, or by reference. The compiler will always\nchoose the strategy that is faster for you.\nThis optimization that you get for free is possible only because function arguments are\nimmutable in Zig.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: *u32) void {\n x = x + 2;\n}\n\npub fn main() !void {\n var x: u32 = @intCast(4);\n add2(&x);\n std.debug.print(\"{d}\\n\", .{x});\n}\n```\n:::\n\n\n\n\n## Structs and OOP {#sec-structs-and-oop}\n\nZig is a language more closely related to C (which is a procedural language),\nthan it is to C++ or Java (which are object-oriented languages). Because of that, you do not\nhave advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or\nclass inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\n\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C.\nYou give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can\nalso register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object\nthat you create with this new type, will always have these methods available and associated with them.\n\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object\nof this particular class, and you also have a destructor method (or a destructor function) that\nis the function responsible for destroying every object of this class.\n\nIn Zig, we normally declare the constructor and the destructor methods\nof our structs, by declaring an `init()` and a `deinit()` methods inside the struct.\nThis is just a naming convention that you will find across the entire Zig standard library.\nSo, in Zig, the `init()` method of a struct is normally the constructor method of the class represented by this struct.\nWhile the `deinit()` method is the method used for destroying an existing instance of that struct.\n\nThe `init()` and `deinit()` methods are both used extensively in Zig code, and you will see both of\nthem being used when we talk about allocators at @sec-allocators.\nBut, as another example, let's build a simple `User` struct to represent an user of some sort of system.\nIf you look at the `User` struct below, you can see the `struct` keyword, and inside of a\npair of curly braces, we write the struct's body.\n\nNotice the data members of this struct, `id`, `name` and `email`. Every data member have it's\ntype explicitly annotated, with the colon character (`:`) syntax that we described earlier at @sec-root-file.\nBut also notice that every line in the struct body that describes a data member, ends with a comma character (`,`).\nSo every time you declare a data member in your Zig code, always end the line with a comma character, instead\nof ending it with the traditional semicolon character (`;`).\n\nNext, also notice in this example, that we registrated an `init()` function as a method\nof this `User` struct. This `init()` method is the constructor method that you use to instantiate\nevery new `User` object. That is why this `init()` function return an `User` object as result.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\npedro\n```\n\n\n:::\n:::\n\n\nThe `pub` keyword plays an important role in struct declarations, and OOP in Zig.\nEvery method that you declare in your struct that is marked with the keyword `pub`,\nbecomes a public method of this particular struct.\n\nSo every method that you create in your struct, is, at first, a private method\nof that struct. Meaning that, this method can only be called from within this\nstruct. But, if you mark this method as public, with the keyword `pub`, then,\nyou can call the method directly from the `User` object you have\nin your code.\n\nIn other words, the functions marked by the keyword `pub`\nare members of the public API of that struct.\nFor example, if I did not marked the `print_name()` method as public,\nthen, I could not execute the line `u.print_name()`. Because I would\nnot be authorized to call this method directly in my code.\n\n\n\n### Anonymous struct literals {#sec-anonymous-struct-literals}\n\nYou can declare a struct object as a literal value. When we do that, we normally specify the\ndata type of this struct literal by writing it's data type just before the opening curly braces.\nFor example, I could write a struct literal of type `User` that we defined in the previous section like\nthis:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n```\n:::\n\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a\nstruct literal, but not especify explicitly the type of this particular struct.\nAn anonymous struct is written by using the syntax `.{}`. So, we essentially\nreplaced the explicit type of the struct literal with a dot character (`.`).\n\nAs we described at @sec-type-inference, when you put a dot before a struct literal,\nthe type of this struct literal is automatically inferred by the `zig` compiler.\nIn essence, the `zig` compiler will look for some hint of what is the type of that struct.\nIt can be the type annotation of an function argument,\nor the return type annotation of the function that you are using, or the type annotation\nof a variable.\nIf the compiler do find such type annotation, then, it will use this\ntype in your literal struct. \n\nAnonymous structs are very commom to use in function arguments in Zig.\nOne example that you have seen already constantly, is the `print()`\nfunction from the `stdout` object.\nThis function takes two arguments.\nThe first argument, is a template string, which should\ncontain string format specifiers in it, which tells how the values provided\nin the second argument should be printed into the message.\n\nWhile the second argument is a struct literal that lists the values\nto be printed into the template message specified in the first argument.\nYou normally want to use an anonymous struct literal here, so that, the\n`zig` compiler do the job of specifying the type of this particular\nanonymous struct for you.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHello, world!\n```\n\n\n:::\n:::\n\n\n\n\n### Struct declarations must be constant\n\nTypes in Zig must be `const` or `comptime` (we are going to talk more about comptime at @sec-comptime).\nWhat this means is that you cannot create a new data type, and mark it as variable with the `var` keyword.\nSo struct declarations are always constant. You cannot declare a new struct using the `var` keyword.\nIt must be `const`.\n\nIn the `Vec3` example below, this declaration is allowed because I'm using the `const` keyword\nto declare this new data type.\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n```\n:::\n\n\n\n### The `self` method argument {#sec-self-arg}\n\nIn every language that have OOP, when we declare a method of some class or struct, we\nusually declare this method as a function that have a `self` argument.\nThis `self` argument is the reference to the object itself from which the method\nis being called from.\n\nIs not mandatory to use this `self` argument. But why would you not use this `self` argument?\nThere is no reason to not use it. Because the only way to get access to the data stored in the\ndata members of your struct is to access them through this `self` argument.\nIf you don't need to use the data in the data members of your struct inside your method, then, you very likely don't need\na method, you can just simply declare this logic as a simple function, outside of your\nstruct declaration.\n\n\nTake the `Vec3` struct below. Inside this `Vec3` struct we declared a method named `distance()`.\nThis method calculates the distance between two `Vec3` objects, by following the distance\nformula in euclidean space. Notice that this `distance()` method takes two `Vec3` objects\nas input, `self` and `other`.\n\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n```\n:::\n\n\n\nThe `self` argument corresponds to the `Vec3` object from which this `distance()` method\nis being called from. While the `other` is a separate `Vec3` object that is given as input\nto this method. In the example below, the `self` argument corresponds to the object\n`v1`, because the `distance()` method is being called from the `v1` object,\nwhile the `other` argument corresponds to the object `v2`.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n```\n:::\n\n\n```\nDistance: 3.3970575502926055\n```\n\n\n\n### About the struct state\n\nSometimes you don't need to care about the state of your struct object. Sometimes, you just need\nto instantiate and use the objects, without altering their state. You can notice that when you have methods\ninside your struct declaration that might use the values that are present in the data members, but they\ndo not alter the values in the data members of the struct in anyway.\n\nThe `Vec3` struct that was presented at @sec-self-arg is an example of that.\nThis struct have a single method named `distance()`, and this method do use the values\npresent in all three data members of the struct (`x`, `y` and `z`). But at the same time,\nthis method do not change the values of these data members in any point.\n\nAs a result of that, when we create `Vec3` objects we usually create them as\nconstant objects, like the `v1` and `v2` objects presented at @sec-self-arg.\nWe can create them as variable objects with the `var` keyword,\nif we want to. But because the methods of this `Vec3` struct do not change\nthe state of the objects in any point, is unnecessary to mark them\nas variable objects.\n\nBut why? Why am I talkin about this here? Is because the `self` argument\nin the methods is affected depending on whether the\nmethods present in a struct change or not the state of the object itself.\nMore specifically, when you have a method in a struct that changes the state\nof the object (i.e. change the value of a data member), the `self` argument\nin this method must be annotated in a different manner.\n\nAs I described at @sec-self-arg, the `self` argument in methods of\na struct is the argument that receives as input the object from which the method\nwas called from. We usually annotate this argument in the methods by writing `self`,\nfollowed by the colon character (`:`), and the data type of the struct to which\nthe method belongs to (e.g. `User`, `Vec3`, etc.).\n\nIf we take the `Vec3` struct that we defined in the previous section as an example,\nwe can see in the `distance()` method that this `self` argument is annotated as\n`self: Vec3`. Because the state of the `Vec3` object is never altered by this\nmethod.\n\nBut what if we do have a method that alters the state of the object, by altering the\nvalues of it's data members. How should we annotate `self` in this instance? The answer is:\n\"we should annotate `self` as a pointer of `x`, instead of just `x`\".\nIn other words, you should annotate `self` as `self: *x`, instead of annotating it\nas `self: x`.\n\nIf we create a new method inside the `Vec3` object that, for example, expands the\nvector by multiplying it's coordinates by a factor o two, then, we need to follow\nthis rule specified in the previous paragraph. The code example below demonstrates\nthis idea:\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n```\n:::\n\n\nNotice in the code example above that we have added a new method\nto our `Vec3` struct named `double()`. This method essentially doubles the\ncoordinate values of our vector object. Also notice that, in the\ncase of the `double()` method, we annotated the `self` argument as `*Vec3`,\nindicating that this argument receives a pointer (or a reference, if you prefer to call it this way)\nto a `Vec3` object as input.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n```\n:::\n\n\n```\nDoubled: 8.4\n```\n\n\n\nNow, if you change the `self` argument in this `double()` method to `self: Vec3`, like in the\n`distance()` method, you will get the compiler error exposed below as result. Notice that this\nerror message is indicating a line from the `double()` method body,\nindicating that you cannot alter the value of the `x` data member.\n\n```zig\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\n```\n\nThis error message indicates that the `x` data member belongs to a constant object,\nand, because of that, it cannot be changed. Ultimately, this error message\nis telling us that the `self` argument is constant.\n\n```\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\n```\n\nIf you take some time, and think hard about this error message, you will understand it.\nYou already have the tools to understand why we are getting this error message.\nWe have talked about it already at @sec-fun-pars.\n\nSo remember, every function argument is immutable in Zig.\nIt does not matter if we marked the `v3` object as a variable object.\nBecause we are trying to alter `self` directly, which is a function argument,\nand, every function argument is immutable by default.\nWe overcome this barrier, by explicitly marking the `self` argument as a pointer.\n\n\n::: {.callout-note}\nIf a method of your `x` struct alters the state of the object, by\nchanging the value of any data member, then, remember to use `self: *x`,\ninstead of `self: x` in the function signature of this method.\n:::\n\n\nYou could also interpret the content discussed in this section as:\n\"if you need to alter the state of your `x` struct object in one of it's methods,\nyou must explicitly pass the `x` struct object by reference to the `self` argument of this method\".\n\n\n\n## Type inference {#sec-type-inference}\n\nZig is kind of a strongly typed language. I say \"kind of\" because there are situations\nwhere you don't have to explicitly write the type of every single object in your source code,\nas you would expect from a traditional strongly typed language, such as C and C++.\n\nIn some situations, the `zig` compiler can use type inference to solves the data types for you, easing some of\nthe burden that you carry as a developer.\nThe most commom way this happens is through function arguments that receives struct objects\nas input.\n\nIn general, type inference in Zig is done by using the dot character (`.`).\nEverytime you see a dot character written before a struct literal, or before a enum value, or something like that,\nyou know that this dot character is playing a special party in this place. More specifically, it is\ntelling the `zig` compiler something on the lines of: \"Hey! Can you infer the type of this\nvalue for me? Please!\". In other words, this dot character is playing a role similar to the `auto` keyword in C++.\n\nI give you some examples of this at @sec-anonymous-struct-literals, where we present anonymous struct literals.\nAnonymous struct literals are, essentially, struct literals that use type inference to\ninfer the exact type of this particular struct literal.\nThis type inference is done by looking for some minimal hint of the correct data type to be used.\nYou could say that the `zig` compiler looks for any neighbouring type annotation that might tell him\nwhat would be the correct type.\n\nAnother commom place where we use type inference in Zig is at switch statements (which we talk about at @sec-switch).\nSo I also gave some other examples of type inference at @sec-switch, where we were inferring the data types of enum values listed inside\nof switch statements (e.g. `.DE`).\nBut as another example, take a look at this `fence()` function reproduced below,\nwhich comes from the [`atomic.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/atomic.zig)[^fence-fn]\nof the Zig Standard Library.\n\n[^fence-fn]: .\n\nThere are a lot of things in this function that we haven't talked about yet, such as:\nwhat `comptime` means? `inline`? `extern`? What is this star symbol before `Self`?\nLet's just ignore all of these things, and focus solely on the switch statement\nthat is inside this function.\n\nWe can see that this switch statement uses the `order` object as input. This `order`\nobject is one of the inputs of this `fence()` function, and we can see in the type annotation,\nthat this object is of type `AtomicOrder`. We can also see a bunch of values inside the\nswitch statements that begins with a dot character, such as `.release` and `.acquire`.\n\nBecause these weird values contain a dot character before them, we are asking the `zig`\ncompiler to infer the types of these values inside the switch statement. Then, the `zig`\ncompiler is looking into the current context where these values are being used, and it is\ntrying to infer the types of these values.\n\nSince they are being used inside a switch statement, the `zig` compiler looks into the type\nof the input object given to the switch statement, which is the `order` object in this case.\nBecause this object have type `AtomicOrder`, the `zig` compiler infers that these values\nare data members from this type `AtomicOrder`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub inline fn fence(self: *Self, comptime order: AtomicOrder) void {\n // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.\n if (builtin.sanitize_thread) {\n const tsan = struct {\n extern \"c\" fn __tsan_acquire(addr: *anyopaque) void;\n extern \"c\" fn __tsan_release(addr: *anyopaque) void;\n };\n\n const addr: *anyopaque = self;\n return switch (order) {\n .unordered, .monotonic => @compileError(@tagName(order) ++ \" only applies to atomic loads and stores\"),\n .acquire => tsan.__tsan_acquire(addr),\n .release => tsan.__tsan_release(addr),\n .acq_rel, .seq_cst => {\n tsan.__tsan_acquire(addr);\n tsan.__tsan_release(addr);\n },\n };\n }\n\n return @fence(order);\n}\n```\n:::\n\n\nThis is how basic type inference is done in Zig. If we didn't use the dot character before\nthe values inside this switch statement, then, we would be forced to write explicitly\nthe data types of these values. For example, instead of writing `.release` we would have to\nwrite `AtomicOrder.release`. We would have to do this for every single value\nin this switch statement, and this is a lot of work. That is why type inference\nis commonly used on switch statements in Zig.\n\n\n\n## Modules\n\nWe already talked about what modules are, and also, how to import other modules into\nyou current module through *import statements*, so that you can use functionality from these other modules in\nyour current module.\nBut in this section, I just want to make it clear that modules are actually structs in Zig.\n\nIn other words, every Zig module (i.e. a `.zig` file) that you write in your project\nis internally stored as a struct object.\nTake the line exposed below as an example. In this line we are importing the\nZig Standard Library into our current module.\n\n```zig\nconst std = @import(\"std\");\n```\n\nWhen we want to access the functions and objects from the standard library, we\nare basically accessing the data members of the struct stored in the `std`\nobject. That is why we use the same syntax that we use in normal structs, with the dot operator (`.`)\nto access the data members and methods of the struct.\n\nWhen this \"import statement\" get's executed, the result of this expression is a struct\nobject that contains the Zig Standard Library modules, global variables, functions, etc.\nAnd this struct object get's saved (or stored) inside the constant object named `std`.\n\n\nTake the [`thread_pool.zig` module from the project `zap`](https://github.com/kprotty/zap/blob/blog/src/thread_pool.zig)[^thread]\nas an example. This module is written as if it was\na big struct. That is why we have a top-level and public `init()` method\nwritten in this module. The idea is that all top-level functions written in this\nmodule are methods from the struct, and all top-level objects and struct declarations\nare data members of this struct. The module is the struct itself.\n\n[^thread]: \n\n\nSo you would import and use this module by doing something like this:\n\n```zig\nconst std = @import(\"std\");\nconst ThreadPool = @import(\"thread_pool.zig\");\nconst num_cpus = std.Thread.getCpuCount()\n catch @panic(\"failed to get cpu core count\");\nconst num_threads = std.math.cast(u16, num_cpus)\n catch std.math.maxInt(u16);\nconst pool = ThreadPool.init(\n .{ .max_threads = num_threads }\n);\n```\n", + "supporting": [ + "03-structs_files" + ], "filters": [ "rmarkdown/pagebreak.lua" ], diff --git a/docs/Chapters/01-zig-weird.html b/docs/Chapters/01-zig-weird.html index 3f1058b..04768a4 100644 --- a/docs/Chapters/01-zig-weird.html +++ b/docs/Chapters/01-zig-weird.html @@ -277,7 +277,7 @@

Table of contents

  • 1.4.3 There is no such thing as unused objects
  • 1.4.4 You must mutate every variable objects
  • -
  • 1.5 Primitive Data Types
  • +
  • 1.5 Primitive Data Types
  • 1.6 Arrays
    • 1.6.1 Selecting elements of the array
    • @@ -572,8 +572,8 @@

      -

      1.5 Primitive Data Types

      +
      +

      1.5 Primitive Data Types

      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 Language Reference page15.

      But here is a quick list:

      @@ -315,7 +316,7 @@

      2.1.2 Swith statements

      Switch statements are also available in Zig. A switch statement in Zig have a similar syntax to a switch statement in Rust. As you would expect, to write a switch statement in Zig we use the switch keyword. We provide the value that we want to “switch over” inside a pair of parentheses. Then, we list the possible combinations (or “branchs”) inside a pair of curly braces.

      Let’s take a look at the code example below. You can see in this example that, I’m creating an enum type called Role. We talk more about enums at Section 7.6. But in essence, this Role type is listing different types of roles in a fictitious company, like SE for Software Engineer, DE for Data Engineer, PM for Product Manager, etc.

      -

      Notice that we are using the value from the role object in the switch statement, to discover which exact area we need to store in the area variable object. Also notice that we are using type inference inside the switch statement, with the dot character, as we described at Section 2.3. This makes the zig compiler infer the correct data type of the values (PM, SE, etc.) for us.

      +

      Notice that we are using the value from the role object in the switch statement, to discover which exact area we need to store in the area variable object. Also notice that we are using type inference inside the switch statement, with the dot character, as we described at Section 2.4. This makes the zig compiler infer the correct data type of the values (PM, SE, etc.) for us.

      Also notice that, we are grouping multiple values in the same branch of the switch statement. We just separate each possible value with a comma. So, for example, if role contains either DE or DA, the area variable would contain the value "Data & Analytics", instead of "Platform".

      const std = @import("std");
      @@ -502,8 +503,44 @@ 

      -

      2.2 Structs and OOP

      +
      +

      2.2 Function parameters are immutable

      +

      We have already discussed a lot of the syntax behind function declarations at Section 1.2.2 and Section 1.2.3. But I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig. In summary, function parameters are immutable in Zig.

      +

      Take the code example below, where we declare a simple function that just tries to add some amount to the input integer, and returns the result back. But if you look closely at the body of this add2() function, you will notice that we try to save the result back into the x function argument.

      +

      In other words, this function not only use the value that it received through the function argument x, but it also tries to change the value of this function argument, by assigning the addition result into x. However, function arguments in Zig are immutable. You cannot change their values, or, you cannot assign values to them inside the body’s function.

      +

      This is the reason why, the code example below do not compile successfully. If you try to compile this code example, you get a compile error warning you that you are trying to change the value of a immutable (i.e. constant) object.

      +
      +
      const std = @import("std");
      +fn add2(x: u32) u32 {
      +    x = x + 2;
      +    return x;
      +}
      +
      +pub fn main() !void {
      +    const y = add2(4);
      +    std.debug.print("{d}\n", .{y});
      +}
      +
      +
      t.zig:3:5: error: cannot assign to constant
      +    x = x + 2;
      +    ^
      +

      If a function argument receives as input a object whose data type is any of the primitive types that we have listed at Section 1.5 this object is always passed by value to the function. In other words, this object is copied to the function stack frame.

      +

      However, if the input object have a more complex data type, for example, it might be a struct instance, or an array, or a union, etc., in cases like that, the zig compiler will take the liberty of deciding for you which strategy is best. The zig compiler will pass your object to the function either by value, or by reference. The compiler will always choose the strategy that is faster for you. This optimization that you get for free is possible only because function arguments are immutable in Zig.

      +
      +
      const std = @import("std");
      +fn add2(x: *u32) void {
      +    x = x + 2;
      +}
      +
      +pub fn main() !void {
      +    var x: u32 = @intCast(4);
      +    add2(&x);
      +    std.debug.print("{d}\n", .{x});
      +}
      +
      +
      +
      +

      2.3 Structs and OOP

      Zig is a language more closely related to C (which is a procedural language), than it is to C++ or Java (which are object-oriented languages). Because of that, you do not have advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or class inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.

      With struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C. You give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can also register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object that you create with this new type, will always have these methods available and associated with them.

      In C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object of this particular class, and you also have a destructor method (or a destructor function) that is the function responsible for destroying every object of this class.

      @@ -512,33 +549,33 @@

      Notice the data members of this struct, id, name and email. Every data member have it’s type explicitly annotated, with the colon character (:) syntax that we described earlier at Section 1.2.2. But also notice that every line in the struct body that describes a data member, ends with a comma character (,). So every time you declare a data member in your Zig code, always end the line with a comma character, instead of ending it with the traditional semicolon character (;).

      Next, also notice in this example, that we registrated an init() function as a method of this User struct. This init() method is the constructor method that you use to instantiate every new User object. That is why this init() function return an User object as result.

      -
      const std = @import("std");
      -const stdout = std.io.getStdOut().writer();
      -const User = struct {
      -    id: u64,
      -    name: []const u8,
      -    email: []const u8,
      -
      -    pub fn init(id: u64,
      -                name: []const u8,
      -                email: []const u8) User {
      -
      -        return User {
      -            .id = id,
      -            .name = name,
      -            .email = email
      -        };
      -    }
      -
      -    pub fn print_name(self: User) !void {
      -        try stdout.print("{s}\n", .{self.name});
      -    }
      -};
      -
      -pub fn main() !void {
      -    const u = User.init(1, "pedro", "email@gmail.com");
      -    try u.print_name();
      -}
      +
      const std = @import("std");
      +const stdout = std.io.getStdOut().writer();
      +const User = struct {
      +    id: u64,
      +    name: []const u8,
      +    email: []const u8,
      +
      +    pub fn init(id: u64,
      +                name: []const u8,
      +                email: []const u8) User {
      +
      +        return User {
      +            .id = id,
      +            .name = name,
      +            .email = email
      +        };
      +    }
      +
      +    pub fn print_name(self: User) !void {
      +        try stdout.print("{s}\n", .{self.name});
      +    }
      +};
      +
      +pub fn main() !void {
      +    const u = User.init(1, "pedro", "email@gmail.com");
      +    try u.print_name();
      +}
      pedro
      @@ -546,92 +583,50 @@

      The pub keyword plays an important role in struct declarations, and OOP in Zig. Every method that you declare in your struct that is marked with the keyword pub, becomes a public method of this particular struct.

      So every method that you create in your struct, is, at first, a private method of that struct. Meaning that, this method can only be called from within this struct. But, if you mark this method as public, with the keyword pub, then, you can call the method directly from the User object you have in your code.

      In other words, the functions marked by the keyword pub are members of the public API of that struct. For example, if I did not marked the print_name() method as public, then, I could not execute the line u.print_name(). Because I would not be authorized to call this method directly in my code.

      -
      -

      2.2.1 Anonymous struct literals

      +
      +

      2.3.1 Anonymous struct literals

      You can declare a struct object as a literal value. When we do that, we normally specify the data type of this struct literal by writing it’s data type just before the opening curly braces. For example, I could write a struct literal of type User that we defined in the previous section like this:

      -
      const eu = User {
      -    .id = 1,
      -    .name = "Pedro",
      -    .email = "someemail@gmail.com"
      -};
      -_ = eu;
      +
      const eu = User {
      +    .id = 1,
      +    .name = "Pedro",
      +    .email = "someemail@gmail.com"
      +};
      +_ = eu;

      However, in Zig, we can also write an anonymous struct literal. That is, you can write a struct literal, but not especify explicitly the type of this particular struct. An anonymous struct is written by using the syntax .{}. So, we essentially replaced the explicit type of the struct literal with a dot character (.).

      -

      As we described at Section 2.3, when you put a dot before a struct literal, the type of this struct literal is automatically inferred by the zig compiler. In essence, the zig compiler will look for some hint of what is the type of that struct. It can be the type annotation of an function argument, or the return type annotation of the function that you are using, or the type annotation of a variable. If the compiler do find such type annotation, then, it will use this type in your literal struct.

      +

      As we described at Section 2.4, when you put a dot before a struct literal, the type of this struct literal is automatically inferred by the zig compiler. In essence, the zig compiler will look for some hint of what is the type of that struct. It can be the type annotation of an function argument, or the return type annotation of the function that you are using, or the type annotation of a variable. If the compiler do find such type annotation, then, it will use this type in your literal struct.

      Anonymous structs are very commom to use in function arguments in Zig. One example that you have seen already constantly, is the print() function from the stdout object. This function takes two arguments. The first argument, is a template string, which should contain string format specifiers in it, which tells how the values provided in the second argument should be printed into the message.

      While the second argument is a struct literal that lists the values to be printed into the template message specified in the first argument. You normally want to use an anonymous struct literal here, so that, the zig compiler do the job of specifying the type of this particular anonymous struct for you.

      -
      const std = @import("std");
      -pub fn main() !void {
      -    const stdout = std.io.getStdOut().writer();
      -    try stdout.print("Hello, {s}!\n", .{"world"});
      -}
      +
      const std = @import("std");
      +pub fn main() !void {
      +    const stdout = std.io.getStdOut().writer();
      +    try stdout.print("Hello, {s}!\n", .{"world"});
      +}
      Hello, world!
      -
      -

      2.2.2 Struct declarations must be constant

      +
      +

      2.3.2 Struct declarations must be constant

      Types in Zig must be const or comptime (we are going to talk more about comptime at Section 12.1). What this means is that you cannot create a new data type, and mark it as variable with the var keyword. So struct declarations are always constant. You cannot declare a new struct using the var keyword. It must be const.

      In the Vec3 example below, this declaration is allowed because I’m using the const keyword to declare this new data type.

      -
      const Vec3 = struct {
      -    x: f64,
      -    y: f64,
      -    z: f64,
      -};
      +
      const Vec3 = struct {
      +    x: f64,
      +    y: f64,
      +    z: f64,
      +};
      -
      -

      2.2.3 The self method argument

      +
      +

      2.3.3 The self method argument

      In every language that have OOP, when we declare a method of some class or struct, we usually declare this method as a function that have a self argument. This self argument is the reference to the object itself from which the method is being called from.

      Is not mandatory to use this self argument. But why would you not use this self argument? There is no reason to not use it. Because the only way to get access to the data stored in the data members of your struct is to access them through this self argument. If you don’t need to use the data in the data members of your struct inside your method, then, you very likely don’t need a method, you can just simply declare this logic as a simple function, outside of your struct declaration.

      Take the Vec3 struct below. Inside this Vec3 struct we declared a method named distance(). This method calculates the distance between two Vec3 objects, by following the distance formula in euclidean space. Notice that this distance() method takes two Vec3 objects as input, self and other.

      -
      const std = @import("std");
      -const m = std.math;
      -const Vec3 = struct {
      -    x: f64,
      -    y: f64,
      -    z: f64,
      -
      -    pub fn distance(self: Vec3, other: Vec3) f64 {
      -        const xd = m.pow(f64, self.x - other.x, 2.0);
      -        const yd = m.pow(f64, self.y - other.y, 2.0);
      -        const zd = m.pow(f64, self.z - other.z, 2.0);
      -        return m.sqrt(xd + yd + zd);
      -    }
      -};
      -
      -

      The self argument corresponds to the Vec3 object from which this distance() method is being called from. While the other is a separate Vec3 object that is given as input to this method. In the example below, the self argument corresponds to the object v1, because the distance() method is being called from the v1 object, while the other argument corresponds to the object v2.

      -
      -
      const v1 = Vec3 {
      -    .x = 4.2, .y = 2.4, .z = 0.9
      -};
      -const v2 = Vec3 {
      -    .x = 5.1, .y = 5.6, .z = 1.6
      -};
      -
      -std.debug.print(
      -    "Distance: {d}\n",
      -    .{v1.distance(v2)}
      -);
      -
      -
      Distance: 3.3970575502926055
      -
      -
      -

      2.2.4 About the struct state

      -

      Sometimes you don’t need to care about the state of your struct object. Sometimes, you just need to instantiate and use the objects, without altering their state. You can notice that when you have methods inside this struct object that might use the values that are present the data members, but they do not alter the values in the data members of structs in anyway.

      -

      The Vec3 struct that we presented in the previous section is an example of that. This struct have a single method named distance(), and this method do use the values present in all three data members of the struct (x, y and z). But at the same time, this method do not change the values of these data members in any point.

      -

      As a result of that, when we create Vec3 objects we usually create them as constant objects, like the v1 and v2 objects presented in the previous code example. We can create them as variable objects with the var keyword, if we want to. But because the methods of this Vec3 struct do not change the state of the objects in any point, is unnecessary to mark them as variable objects.

      -

      But why? Why am I talkin about this here? Is because the self argument in the methods is affected depending on whether the methods present in a struct change or not the state of the object itself. More specifically, when you have a method in a struct that changes the state of the object (i.e. change the value of a data member), the self argument in this method must be annotated in a different manner.

      -

      As I described in the previous section, the self argument in methods of a struct is the argument that receives as input the object from which the method was called from. We usually annotate this argument in the methods by writing self, followed by the colon character (:), and the data type of the struct to which the method belongs to (e.g. User, Vec3, etc.).

      -

      If we take the Vec3 struct that we defined in the previous section as an example, we can see in the distance() method that this self argument is annotated as self: Vec3. Because the state of the Vec3 object is never altered by this method.

      -

      But what if we do have a method that alters the state of the object, by altering the values of it’s data members. How should we annotate self in this instance? The answer is: “we should pass a pointer of x to self, and not simply a copy of x to self”. In other words, you should annotate self as self: *x, instead of annotating it as self: x.

      -

      If we create a new method inside the Vec3 object that, for example, expands the vector by multiplying it’s coordinates by a factor o two, then, we need to follow this rule specified in the previous paragraph. The code example below demonstrates this idea:

      -
      const std = @import("std");
       const m = std.math;
       const Vec3 = struct {
      @@ -645,31 +640,74 @@ 

      const zd = m.pow(f64, self.z - other.z, 2.0); return m.sqrt(xd + yd + zd); } - - pub fn double(self: *Vec3) void { - self.x = self.x * 2.0; - self.y = self.y * 2.0; - self.z = self.z * 2.0; - } -};

      +};

      -

      Notice in the code example above that we have added a new method to our Vec3 struct named double(). This method essentially doubles the coordinate values of our vector object. Also notice that, in the case of the double() method, we annotated the self argument as *Vec3, indicating that this argument receives a pointer (or a reference, if you prefer to call it this way) to a Vec3 object, instead of receiving a copy of the object directly, as input.

      +

      The self argument corresponds to the Vec3 object from which this distance() method is being called from. While the other is a separate Vec3 object that is given as input to this method. In the example below, the self argument corresponds to the object v1, because the distance() method is being called from the v1 object, while the other argument corresponds to the object v2.

      -
      var v3 = Vec3 {
      +
      const v1 = Vec3 {
           .x = 4.2, .y = 2.4, .z = 0.9
       };
      -v3.double();
      -std.debug.print("Doubled: {d}\n", .{v3.x});
      +const v2 = Vec3 { + .x = 5.1, .y = 5.6, .z = 1.6 +}; + +std.debug.print( + "Distance: {d}\n", + .{v1.distance(v2)} +);
      +
      +
      Distance: 3.3970575502926055
      +
      +
      +

      2.3.4 About the struct state

      +

      Sometimes you don’t need to care about the state of your struct object. Sometimes, you just need to instantiate and use the objects, without altering their state. You can notice that when you have methods inside your struct declaration that might use the values that are present in the data members, but they do not alter the values in the data members of the struct in anyway.

      +

      The Vec3 struct that was presented at Section 2.3.3 is an example of that. This struct have a single method named distance(), and this method do use the values present in all three data members of the struct (x, y and z). But at the same time, this method do not change the values of these data members in any point.

      +

      As a result of that, when we create Vec3 objects we usually create them as constant objects, like the v1 and v2 objects presented at Section 2.3.3. We can create them as variable objects with the var keyword, if we want to. But because the methods of this Vec3 struct do not change the state of the objects in any point, is unnecessary to mark them as variable objects.

      +

      But why? Why am I talkin about this here? Is because the self argument in the methods is affected depending on whether the methods present in a struct change or not the state of the object itself. More specifically, when you have a method in a struct that changes the state of the object (i.e. change the value of a data member), the self argument in this method must be annotated in a different manner.

      +

      As I described at Section 2.3.3, the self argument in methods of a struct is the argument that receives as input the object from which the method was called from. We usually annotate this argument in the methods by writing self, followed by the colon character (:), and the data type of the struct to which the method belongs to (e.g. User, Vec3, etc.).

      +

      If we take the Vec3 struct that we defined in the previous section as an example, we can see in the distance() method that this self argument is annotated as self: Vec3. Because the state of the Vec3 object is never altered by this method.

      +

      But what if we do have a method that alters the state of the object, by altering the values of it’s data members. How should we annotate self in this instance? The answer is: “we should annotate self as a pointer of x, instead of just x”. In other words, you should annotate self as self: *x, instead of annotating it as self: x.

      +

      If we create a new method inside the Vec3 object that, for example, expands the vector by multiplying it’s coordinates by a factor o two, then, we need to follow this rule specified in the previous paragraph. The code example below demonstrates this idea:

      +
      +
      const std = @import("std");
      +const m = std.math;
      +const Vec3 = struct {
      +    x: f64,
      +    y: f64,
      +    z: f64,
      +
      +    pub fn distance(self: Vec3, other: Vec3) f64 {
      +        const xd = m.pow(f64, self.x - other.x, 2.0);
      +        const yd = m.pow(f64, self.y - other.y, 2.0);
      +        const zd = m.pow(f64, self.z - other.z, 2.0);
      +        return m.sqrt(xd + yd + zd);
      +    }
      +
      +    pub fn double(self: *Vec3) void {
      +        self.x = self.x * 2.0;
      +        self.y = self.y * 2.0;
      +        self.z = self.z * 2.0;
      +    }
      +};
      +
      +

      Notice in the code example above that we have added a new method to our Vec3 struct named double(). This method essentially doubles the coordinate values of our vector object. Also notice that, in the case of the double() method, we annotated the self argument as *Vec3, indicating that this argument receives a pointer (or a reference, if you prefer to call it this way) to a Vec3 object as input.

      +
      +
      var v3 = Vec3 {
      +    .x = 4.2, .y = 2.4, .z = 0.9
      +};
      +v3.double();
      +std.debug.print("Doubled: {d}\n", .{v3.x});
      Doubled: 8.4
      -

      If you change the self argument in this double() method to self: Vec3, like in the distance() method, you will get the error exposed below as result. Notice that this error message is indicating a line from the double() method body, indicating that you cannot alter the value of the x data member.

      -
      // If we change the function signature of double to:
      -    pub fn double(self: Vec3) void {
      -

      This error message indicates that the x data member belongs to a constant object, and, because of that, it cannot be changed. Even though we marked the v3 object that we have created in the previous code example as a variable object. So even though this x data member belongs to a variable object in our code, this error message is pointing to the opposite direction.

      +

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

      +
      // If we change the function signature of double to:
      +    pub fn double(self: Vec3) void {
      +

      This error message indicates that the x data member belongs to a constant object, and, because of that, it cannot be changed. Ultimately, this error message is telling us that the self argument is constant.

      t.zig:16:13: error: cannot assign to constant
               self.x = self.x * 2.0;
               ~~~~^~
      -

      But this error message is misleading, because the only thing that we have changed is the self argument signature from *Vec3 to Vec3 in the double() method. So, just remember this general rule below about your method declarations:

      +

      If you take some time, and think hard about this error message, you will understand it. You already have the tools to understand why we are getting this error message. We have talked about it already at Section 2.2.

      +

      So remember, every function argument is immutable in Zig. It does not matter if we marked the v3 object as a variable object. Because we are trying to alter self directly, which is a function argument, and, every function argument is immutable by default. We overcome this barrier, by explicitly marking the self argument as a pointer.

      @@ -683,64 +721,64 @@

      If a method of your x struct alters the state of the object, by changing the value of any data member, then, remember to use self: *x, instead of self: x in the function signature of this method.

      -

      You could also interpret the content discussed in this section as: “if you need to alter the state of your x struct object in one of it’s methods, you must pass the x struct object by reference to the self argument of this method, instead of passing it by value”.

      +

      You could also interpret the content discussed in this section as: “if you need to alter the state of your x struct object in one of it’s methods, you must explicitly pass the x struct object by reference to the self argument of this method”.

      -
      -

      2.3 Type inference

      +
      +

      2.4 Type inference

      Zig is kind of a strongly typed language. I say “kind of” because there are situations where you don’t have to explicitly write the type of every single object in your source code, as you would expect from a traditional strongly typed language, such as C and C++.

      In some situations, the zig compiler can use type inference to solves the data types for you, easing some of the burden that you carry as a developer. The most commom way this happens is through function arguments that receives struct objects as input.

      In general, type inference in Zig is done by using the dot character (.). Everytime you see a dot character written before a struct literal, or before a enum value, or something like that, you know that this dot character is playing a special party in this place. More specifically, it is telling the zig compiler something on the lines of: “Hey! Can you infer the type of this value for me? Please!”. In other words, this dot character is playing a role similar to the auto keyword in C++.

      -

      I give you some examples of this at Section 2.2.1, where we present anonymous struct literals. Anonymous struct literals are, essentially, struct literals that use type inference to infer the exact type of this particular struct literal. This type inference is done by looking for some minimal hint of the correct data type to be used. You could say that the zig compiler looks for any neighbouring type annotation that might tell him what would be the correct type.

      +

      I give you some examples of this at Section 2.3.1, where we present anonymous struct literals. Anonymous struct literals are, essentially, struct literals that use type inference to infer the exact type of this particular struct literal. This type inference is done by looking for some minimal hint of the correct data type to be used. You could say that the zig compiler looks for any neighbouring type annotation that might tell him what would be the correct type.

      Another commom place where we use type inference in Zig is at switch statements (which we talk about at Section 2.1.2). So I also gave some other examples of type inference at Section 2.1.2, where we were inferring the data types of enum values listed inside of switch statements (e.g. .DE). But as another example, take a look at this fence() function reproduced below, which comes from the atomic.zig module2 of the Zig Standard Library.

      There are a lot of things in this function that we haven’t talked about yet, such as: what comptime means? inline? extern? What is this star symbol before Self? Let’s just ignore all of these things, and focus solely on the switch statement that is inside this function.

      We can see that this switch statement uses the order object as input. This order object is one of the inputs of this fence() function, and we can see in the type annotation, that this object is of type AtomicOrder. We can also see a bunch of values inside the switch statements that begins with a dot character, such as .release and .acquire.

      Because these weird values contain a dot character before them, we are asking the zig compiler to infer the types of these values inside the switch statement. Then, the zig compiler is looking into the current context where these values are being used, and it is trying to infer the types of these values.

      Since they are being used inside a switch statement, the zig compiler looks into the type of the input object given to the switch statement, which is the order object in this case. Because this object have type AtomicOrder, the zig compiler infers that these values are data members from this type AtomicOrder.

      -
      pub inline fn fence(self: *Self, comptime order: AtomicOrder) void {
      -    // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.
      -    if (builtin.sanitize_thread) {
      -        const tsan = struct {
      -            extern "c" fn __tsan_acquire(addr: *anyopaque) void;
      -            extern "c" fn __tsan_release(addr: *anyopaque) void;
      -        };
      -
      -        const addr: *anyopaque = self;
      -        return switch (order) {
      -            .unordered, .monotonic => @compileError(@tagName(order) ++ " only applies to atomic loads and stores"),
      -            .acquire => tsan.__tsan_acquire(addr),
      -            .release => tsan.__tsan_release(addr),
      -            .acq_rel, .seq_cst => {
      -                tsan.__tsan_acquire(addr);
      -                tsan.__tsan_release(addr);
      -            },
      -        };
      -    }
      -
      -    return @fence(order);
      -}
      +
      pub inline fn fence(self: *Self, comptime order: AtomicOrder) void {
      +    // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.
      +    if (builtin.sanitize_thread) {
      +        const tsan = struct {
      +            extern "c" fn __tsan_acquire(addr: *anyopaque) void;
      +            extern "c" fn __tsan_release(addr: *anyopaque) void;
      +        };
      +
      +        const addr: *anyopaque = self;
      +        return switch (order) {
      +            .unordered, .monotonic => @compileError(@tagName(order) ++ " only applies to atomic loads and stores"),
      +            .acquire => tsan.__tsan_acquire(addr),
      +            .release => tsan.__tsan_release(addr),
      +            .acq_rel, .seq_cst => {
      +                tsan.__tsan_acquire(addr);
      +                tsan.__tsan_release(addr);
      +            },
      +        };
      +    }
      +
      +    return @fence(order);
      +}

      This is how basic type inference is done in Zig. If we didn’t use the dot character before the values inside this switch statement, then, we would be forced to write explicitly the data types of these values. For example, instead of writing .release we would have to write AtomicOrder.release. We would have to do this for every single value in this switch statement, and this is a lot of work. That is why type inference is commonly used on switch statements in Zig.

      -
      -

      2.4 Modules

      +
      +

      2.5 Modules

      We already talked about what modules are, and also, how to import other modules into you current module through import statements, so that you can use functionality from these other modules in your current module. But in this section, I just want to make it clear that modules are actually structs in Zig.

      In other words, every Zig module (i.e. a .zig file) that you write in your project is internally stored as a struct object. Take the line exposed below as an example. In this line we are importing the Zig Standard Library into our current module.

      -
      const std = @import("std");
      +
      const std = @import("std");

      When we want to access the functions and objects from the standard library, we are basically accessing the data members of the struct stored in the std object. That is why we use the same syntax that we use in normal structs, with the dot operator (.) to access the data members and methods of the struct.

      When this “import statement” get’s executed, the result of this expression is a struct object that contains the Zig Standard Library modules, global variables, functions, etc. And this struct object get’s saved (or stored) inside the constant object named std.

      Take the thread_pool.zig module from the project zap3 as an example. This module is written as if it was a big struct. That is why we have a top-level and public init() method written in this module. The idea is that all top-level functions written in this module are methods from the struct, and all top-level objects and struct declarations are data members of this struct. The module is the struct itself.

      So you would import and use this module by doing something like this:

      -
      const std = @import("std");
      -const ThreadPool = @import("thread_pool.zig");
      -const num_cpus = std.Thread.getCpuCount()
      -    catch @panic("failed to get cpu core count");
      -const num_threads = std.math.cast(u16, num_cpus)
      -    catch std.math.maxInt(u16);
      -const pool = ThreadPool.init(
      -    .{ .max_threads = num_threads }
      -);
      +
      const std = @import("std");
      +const ThreadPool = @import("thread_pool.zig");
      +const num_cpus = std.Thread.getCpuCount()
      +    catch @panic("failed to get cpu core count");
      +const num_threads = std.math.cast(u16, num_cpus)
      +    catch std.math.maxInt(u16);
      +const pool = ThreadPool.init(
      +    .{ .max_threads = num_threads }
      +);
      diff --git a/docs/Chapters/05-pointers.html b/docs/Chapters/05-pointers.html index 06bb353..f6d9c2f 100644 --- a/docs/Chapters/05-pointers.html +++ b/docs/Chapters/05-pointers.html @@ -307,7 +307,7 @@

      std.debug.print("{d}\n", .{doubled});
      10
      -

      This syntax to dereference the pointer is nice. Because we can easily chain it with methods of the value pointed by the pointer. We can use the User struct that we created at Section 2.2 as an example. If you comeback to that section, you will see that this struct have a method named print_name().

      +

      This syntax to dereference the pointer is nice. Because we can easily chain it with methods of the value pointed by the pointer. We can use the User struct that we created at Section 2.3 as an example. If you comeback to that section, you will see that this struct have a method named print_name().

      So, for example, if we have an user object, and a pointer that points to this user object, we can use the pointer to access this user object, and, at the same time, call the method print_name() on it, by chaining the dereference method (*) with the print_name() method. Like in the example below:

      const u = User.init(1, "pedro", "email@gmail.com");
      diff --git a/docs/Chapters/09-data-structures.html b/docs/Chapters/09-data-structures.html
      index 4fe4092..eafbe16 100644
      --- a/docs/Chapters/09-data-structures.html
      +++ b/docs/Chapters/09-data-structures.html
      @@ -369,7 +369,7 @@ 

      11.1.2 Creating an ArrayList object

      In order to use ArrayList, you must provide an allocator object to it. Remember, Zig does not have a default memory allocator. And as I described at Section 3.2, all memory allocations must be done by allocator objects that you define, that you have control over. In our example here, I’m going to use a general purpose allocator, but you can use any other allocator of your preference.

      -

      When you initialize an ArrayList object, you must provide the data type of the elements of the array. In other words, this defines the type of data that this array (or container) will store. Therefore, if I provide the u8 type to it, then, I will create a dynamic array of u8 values. However, if I provide a struct that I defined instead, like the struct User from Section 2.2, then, a dynamic array of User values will be created. In the example below, with the expression ArrayList(u8) we are creating a dynamic array of u8 values.

      +

      When you initialize an ArrayList object, you must provide the data type of the elements of the array. In other words, this defines the type of data that this array (or container) will store. Therefore, if I provide the u8 type to it, then, I will create a dynamic array of u8 values. However, if I provide a struct that I defined instead, like the struct User from Section 2.3, then, a dynamic array of User values will be created. In the example below, with the expression ArrayList(u8) we are creating a dynamic array of u8 values.

      After you provide the data type of the elements of the array, you can initialize an ArrayList object by either using the init() or the initCapacity() method. The former method receives only the allocator object as input, while the latter method receives both the allocator object and a capacity number as inputs. With the latter method, you not only initialize the struct, but you also set the starting capacity of the allocated array.

      Using the initCapacity() method is the preferred way to initialize your dynamic array. Because reallocations, or, in other words, the process of expanding the capacity of the array, is always a high cost operation. You should take any possible opportunity to avoid reallocations in your array. If you know how much space your array needs to occupy at the beginning, you should always use initCapacity() to create your dynamic array.

      diff --git a/docs/Chapters/10-stack-project.html b/docs/Chapters/10-stack-project.html index 8a25aea..f92ef16 100644 --- a/docs/Chapters/10-stack-project.html +++ b/docs/Chapters/10-stack-project.html @@ -413,7 +413,7 @@

      12.2 Introducing Generics

      -

      First of all, what is a generic? Generic is the idea to allow a type (f64, u8, u32, bool, and also, user-defined types, like the User struct that we defined at Section 2.2) to be a parameter to methods, classes and interfaces (Geeks for Geeks 2024). In other words, a “generic” is a class (or a method) that can work with multiple data types.

      +

      First of all, what is a generic? Generic is the idea to allow a type (f64, u8, u32, bool, and also, user-defined types, like the User struct that we defined at Section 2.3) to be a parameter to methods, classes and interfaces (Geeks for Geeks 2024). In other words, a “generic” is a class (or a method) that can work with multiple data types.

      For example, in Java, generics are created through the operator <>. With this operator, a Java class is capable of receiving a data type as input, and therefore, the class can fit it’s features according to this input data type. As another example, generics in C++ are supported through the concept of templates. Class templates in C++ are generics.

      In Zig, generics are implemented through comptime. The comptime keyword allows us to collect a data type at compile time, and pass this data type as input to a piece of code.

      diff --git a/docs/search.json b/docs/search.json index 8d338a7..952f9ab 100644 --- a/docs/search.json +++ b/docs/search.json @@ -130,8 +130,8 @@ ] }, { - "objectID": "Chapters/01-zig-weird.html#primitive-data-types", - "href": "Chapters/01-zig-weird.html#primitive-data-types", + "objectID": "Chapters/01-zig-weird.html#sec-primitive-data-types", + "href": "Chapters/01-zig-weird.html#sec-primitive-data-types", "title": "1  Introducing Zig", "section": "1.5 Primitive Data Types", "text": "1.5 Primitive Data Types\nZig have 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 page15.\nBut here is a quick list:\n\nUnsigned integers: u8, 8-bit integer; u16, 16-bit integer; u32, 32-bit integer; u64, 64-bit integer; u128, 128-bit integer.\nSigned integers: i8, 8-bit integer; i16, 16-bit integer; i32, 32-bit integer; i64, 64-bit integer; i128, 128-bit integer.\nFloat number: f16, 16-bit floating point; f32, 32-bit floating point; f64, 64-bit floating point; f128, 128-bit floating point;\nBoolean: bool, represents true or false values.\nC ABI compatible types: c_long, c_char, c_short, c_ushort, c_int, c_uint, and many others.\nPointer sized integers: isize and usize.", @@ -184,7 +184,7 @@ "href": "Chapters/01-zig-weird.html#other-parts-of-zig", "title": "1  Introducing Zig", "section": "1.10 Other parts of Zig", - "text": "1.10 Other parts of Zig\nWe already learned a lot about Zig’s syntax, and also, some pretty technical details about it. Just as a quick recap:\n\nWe talked about how functions are written in Zig at Section 1.2.2 and Section 1.2.3.\nHow to create new objects/identifiers at Section 1.2.2 and specially at Section 1.4.\nHow strings work in Zig at Section 1.8.\nHow to use arrays and slices at Section 1.6.\nHow to import functionality from other Zig modules at Section 1.2.2.\n\nBut, 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:\n\nHow Object-Oriented programming can be done in Zig through struct declarations at Section 2.2.\nBasic control flow syntax at Section 2.1.\nEnums at Section 7.6;\nPointers and Optionals at Chapter 6;\nError handling with try and catch at Chapter 10;\nUnit tests at Chapter 8;\nVectors;\nBuild System at Chapter 9;\n\n\n\n\n\nKim, Juhee, Jinbum Park, Sihyeon Roh, Jaeyoung Chung, Youngjoo Lee, Taesoo Kim, and Byoungyoung Lee. 2024. “TikTag: Breaking ARM’s Memory Tagging Extension with Speculative Execution.” https://arxiv.org/abs/2406.08719.\n\n\nSobeston. 2024. “Zig Guide.” https://zig.guide/.", + "text": "1.10 Other parts of Zig\nWe already learned a lot about Zig’s syntax, and also, some pretty technical details about it. Just as a quick recap:\n\nWe talked about how functions are written in Zig at Section 1.2.2 and Section 1.2.3.\nHow to create new objects/identifiers at Section 1.2.2 and specially at Section 1.4.\nHow strings work in Zig at Section 1.8.\nHow to use arrays and slices at Section 1.6.\nHow to import functionality from other Zig modules at Section 1.2.2.\n\nBut, 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:\n\nHow Object-Oriented programming can be done in Zig through struct declarations at Section 2.3.\nBasic control flow syntax at Section 2.1.\nEnums at Section 7.6;\nPointers and Optionals at Chapter 6;\nError handling with try and catch at Chapter 10;\nUnit tests at Chapter 8;\nVectors;\nBuild System at Chapter 9;\n\n\n\n\n\nKim, Juhee, Jinbum Park, Sihyeon Roh, Jaeyoung Chung, Youngjoo Lee, Taesoo Kim, and Byoungyoung Lee. 2024. “TikTag: Breaking ARM’s Memory Tagging Extension with Speculative Execution.” https://arxiv.org/abs/2406.08719.\n\n\nSobeston. 2024. “Zig Guide.” https://zig.guide/.", "crumbs": [ "1  Introducing Zig" ] @@ -214,7 +214,17 @@ "href": "Chapters/03-structs.html#sec-zig-control-flow", "title": "2  Structs, Modules and Control Flow", "section": "", - "text": "2.1.1 If/else statements\nAn if/else statement performs an “conditional flow operation”. A conditional flow control (or choice control) allows you to execute or ignore a certain block of commands based on a logical condition. Many programmers and computer science professionals also use the term “branching” in this case. In essence, we use if/else statements to use the result of a logical test to decide whether or not to execute a given block of commands.\nIn Zig, we write if/else statements by using the keywords if and else. We start with the if keyword followed by a logical test inside a pair of parentheses, and then, a pair of curly braces with contains the lines of code to be executed in case the logical test returns the value true.\nAfter that, you can optionally add an else statement. Just add the else keyword followed by a pair of curly braces, with the lines of code to executed in case the logical test defined in the if returns false.\nIn the example below, we are testing if the object x contains a number that is greater than 10. Judging by the output printed to the console, we know that this logical test returned false. Because the output in the console is compatible with the line of code present in the else branch of the if/else statement.\n\nconst x = 5;\nif (x > 10) {\n try stdout.print(\n \"x > 10!\\n\", .{}\n );\n} else {\n try stdout.print(\n \"x <= 10!\\n\", .{}\n );\n}\n\nx <= 10!\n\n\n\n\n2.1.2 Swith statements\nSwitch statements are also available in Zig. A switch statement in Zig have a similar syntax to a switch statement in Rust. As you would expect, to write a switch statement in Zig we use the switch keyword. We provide the value that we want to “switch over” inside a pair of parentheses. Then, we list the possible combinations (or “branchs”) inside a pair of curly braces.\nLet’s take a look at the code example below. You can see in this example that, I’m creating an enum type called Role. We talk more about enums at Section 7.6. But in essence, this Role type is listing different types of roles in a fictitious company, like SE for Software Engineer, DE for Data Engineer, PM for Product Manager, etc.\nNotice that we are using the value from the role object in the switch statement, to discover which exact area we need to store in the area variable object. Also notice that we are using type inference inside the switch statement, with the dot character, as we described at Section 2.3. This makes the zig compiler infer the correct data type of the values (PM, SE, etc.) for us.\nAlso notice that, we are grouping multiple values in the same branch of the switch statement. We just separate each possible value with a comma. So, for example, if role contains either DE or DA, the area variable would contain the value \"Data & Analytics\", instead of \"Platform\".\n\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst Role = enum {\n SE, DPE, DE, DA, PM, PO, KS\n};\n\npub fn main() !void {\n var area: []const u8 = undefined;\n const role = Role.SE;\n switch (role) {\n .PM, .SE, .DPE, .PO => {\n area = \"Platform\";\n },\n .DE, .DA => {\n area = \"Data & Analytics\";\n },\n .KS => {\n area = \"Sales\";\n },\n }\n try stdout.print(\"{s}\\n\", .{area});\n}\n\nPlatform\n\n\nNow, one very important aspect about this switch statement presented in the code example above, is that it exhaust all existing possibilities. In other words, all possible values that could be found inside the order object are explicitly handled in this switch statement.\nSince the role object have type Role, the only possible values to be found inside this object are PM, SE, DPE, PO, DE, DA and KS. There is no other possible value to be stored in this role object. This what “exhaust all existing possibilities” means. The switch statement covers every possible case.\nIn Zig, switch statements must exhaust all existing possibilities. You cannot write a switch statement, and leave an edge case with no expliciting action to be taken. This is a similar behaviour to switch statements in Rust, which also have to handle all possible cases.\nTake a look at the dump_hex_fallible() function below as an example. This function also comes from the Zig Standard Library, but this time, it comes from the debug.zig module1. There are multiple lines in this function, but I omitted them to focus solely on the switch statement found in this function. Notice that this switch statement have four possible cases, or four explicit branches. Also, notice that we used an else branch in this case. Whenever you have multiple possible cases in your switch statement which you want to apply the same exact action, you can use an else branch to do that.\n\npub fn dump_hex_fallible(bytes: []const u8) !void {\n // Many lines ...\n switch (byte) {\n '\\n' => try writer.writeAll(\"␊\"),\n '\\r' => try writer.writeAll(\"␍\"),\n '\\t' => try writer.writeAll(\"␉\"),\n else => try writer.writeByte('.'),\n }\n}\n\nMany users would also use an else branch to handle a “not supported” case. That is, a case that cannot be properly handled by your code, or, just a case that should not be “fixed”. So many programmers use an else branch to panic (or raise an error) to stop the current execution.\nTake the code example below as an example. We can see that, we are handling the cases for the level object being either 1, 2, or 3. All other possible cases are not supported by default, and, as consequence, we raise an runtime error in these cases, through the @panic() built-in function.\nAlso notice that, we are assigning the result of the switch statement to a new object called category. This is another thing that you can do with switch statements in Zig. If the branchs in this switch statement output some value as result, you can store the result value of the switch statement into a new object.\n\nconst level: u8 = 4;\nconst category = switch (level) {\n 1, 2 => \"beginner\",\n 3 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n\nthread 13103 panic: Not supported level!\nt.zig:9:13: 0x1033c58 in main (switch2)\n @panic(\"Not supported level!\");\n ^\nFurthermore, you can also use ranges of values in switch statements. That is, you can create a branch in your switch statement that is used whenever the input value is contained in a range. These range expressions are created with the operator .... Is important to emphasize that the ranges created by this operator are inclusive on both ends.\nFor example, I could easily change the code example above to support all levels between 0 and 100. Like this:\n\nconst level: u8 = 4;\nconst category = switch (level) {\n 0...25 => \"beginner\",\n 26...75 => \"intermediary\",\n 76...100 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n\nbeginner\n\n\nThis is neat, and it works with character ranges too. That is, I could simply write 'a'...'z', to match any character value that is a lowercase letter, and it would work fine.\n\n\n2.1.3 The defer keyword\nWith 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.\n\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nfn foo() !void {\n defer std.debug.print(\n \"Exiting function ...\\n\", .{}\n );\n try stdout.print(\"Adding some numbers ...\\n\", .{});\n const x = 2 + 2; _ = x;\n try stdout.print(\"Multiplying ...\\n\", .{});\n const y = 2 * 8; _ = y;\n}\n\npub fn main() !void {\n try foo();\n}\n\nAdding some numbers ...\nMultiplying ...\nExiting function ...\nIt 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.\n\n\n2.1.4 For loops\nA loop allows you to execute the same lines of code multiple times, thus, creating a “repetition space” in the execution flow of your program. Loops are particularly useful when we want to replicate the same function (or the same set of commands) over several different inputs.\nThere are different types of loops available in Zig. But the most essential of them all is probably the for loop. A for loop is used to apply the same piece of code over the elements of a slice or an array.\nFor loops in Zig have a slightly different syntax that you are probably used to see in other languages. You start with the for keyword, then, you list the items that you want to iterate over inside a pair of parentheses. Then, inside of a pair of pipes (|) you should declare an identifier that will serve as your iterator, or, the “repetition index of the loop”.\n\nfor (items) |value| {\n // code to execute\n}\n\nInstead of using a (value in items) syntax, in Zig, for loops use the syntax (items) |value|. In the example below, you can see that we are looping through the items of the array stored at the object name, and printing to the console the decimal representation of each character in this array.\nIf we wanted, we could also iterate through a slice (or a portion) of the array, instead of iterating through the entire array stored in the name object. Just use a range selector to select the section you want. For example, I could provide the expression name[0..3] to the for loop, to iterate just through the first 3 elements in the array.\n\nconst name = [_]u8{'P','e','d','r','o'};\nfor (name) |char| {\n try stdout.print(\"{d} | \", .{char});\n}\n\n80 | 101 | 100 | 114 | 111 | \n\n\nIn the above example we are using the value itself of each element in the array as our iterator. But there are many situations where we need to use an index instead of the actual values of the items.\nYou can do that by providing a second set of items to iterate over. More precisely, you provide the range selector 0.. to the for loop. So, yes, you can use two different iterators at the same time in a for loop in Zig.\nBut remember from Section 1.4 that, every object you create in Zig must be used in some way. So if you declare two iterators in your for loop, you must use both iterators inside the for loop body. But if you want to use just the index iterator, and not use the “value iterator”, then, you can discard the value iterator by maching the value items to the underscore character, like in the example below:\n\nfor (name, 0..) |_, i| {\n try stdout.print(\"{d} | \", .{i});\n}\n\n0 | 1 | 2 | 3 | 4 |\n\n\n2.1.5 While loops\nA while loop is created from the while keyword. A for loop iterates through the items of an array, but a while loop will loop continuously, and infinitely, until a logical test (specified by you) becomes false.\nYou start with the while keyword, then, you define a logical expression inside a pair of parentheses, and the body of the loop is provided inside a pair of curly braces, like in the example below:\n\nvar i: u8 = 1;\nwhile (i < 5) {\n try stdout.print(\"{d} | \", .{i});\n i += 1;\n}\n\n1 | 2 | 3 | 4 | \n\n\n\n\n2.1.6 Using break and continue\nIn Zig, you can explicitly stop the execution of a loop, or, jump to the next iteration of the loop, using the keywords break and continue, respectively. The while loop present in the example below, is at first sight, an infinite loop. Because the logical value inside the parenthese will always be equal to true. What makes this while loop stop when the i object reaches the count 10? Is the break keyword!\nInside the while loop, we have an if statement that is constantly checking if the i variable is equal to 10. Since we are increasing the value of this i variable at each iteration of the while loop. At some point, this i variable will be equal to 10, and when it does, the if statement will execute the break expression, and, as a result, the execution of the while loop is stopped.\nNotice the expect() function from the Zig standard library after the while loop. This expect() function is an “assert” type of function. This function checks if the logical test provided is equal to true. If this logical test is false, the function raises an assertion error. But it is equal to true, then, the function will do nothing.\n\nvar i: usize = 0;\nwhile (true) {\n if (i == 10) {\n break;\n }\n i += 1;\n}\ntry std.testing.expect(i == 10);\ntry stdout.print(\"Everything worked!\", .{});\n\nEverything worked!\n\n\nSince this code example was executed succesfully by the zig compiler, without raising any errors, then, we known that, after the execution of while loop, the i variable is equal to 10. Because if it wasn’t equal to 10, then, an error would be raised by expect().\nNow, in the next example, we have an use case for the continue keyword. The if statement is constantly checking if the current index is a multiple of 2. If it is, then we jump to the next iteration of the loop directly. But it the current index is not a multiple of 2, then, the loop will simply print this index to the console.\n\nconst ns = [_]u8{1,2,3,4,5,6};\nfor (ns) |i| {\n if ((i % 2) == 0) {\n continue;\n }\n try stdout.print(\"{d} | \", .{i});\n}\n\n1 | 3 | 5 |", + "text": "2.1.1 If/else statements\nAn if/else statement performs an “conditional flow operation”. A conditional flow control (or choice control) allows you to execute or ignore a certain block of commands based on a logical condition. Many programmers and computer science professionals also use the term “branching” in this case. In essence, we use if/else statements to use the result of a logical test to decide whether or not to execute a given block of commands.\nIn Zig, we write if/else statements by using the keywords if and else. We start with the if keyword followed by a logical test inside a pair of parentheses, and then, a pair of curly braces with contains the lines of code to be executed in case the logical test returns the value true.\nAfter that, you can optionally add an else statement. Just add the else keyword followed by a pair of curly braces, with the lines of code to executed in case the logical test defined in the if returns false.\nIn the example below, we are testing if the object x contains a number that is greater than 10. Judging by the output printed to the console, we know that this logical test returned false. Because the output in the console is compatible with the line of code present in the else branch of the if/else statement.\n\nconst x = 5;\nif (x > 10) {\n try stdout.print(\n \"x > 10!\\n\", .{}\n );\n} else {\n try stdout.print(\n \"x <= 10!\\n\", .{}\n );\n}\n\nx <= 10!\n\n\n\n\n2.1.2 Swith statements\nSwitch statements are also available in Zig. A switch statement in Zig have a similar syntax to a switch statement in Rust. As you would expect, to write a switch statement in Zig we use the switch keyword. We provide the value that we want to “switch over” inside a pair of parentheses. Then, we list the possible combinations (or “branchs”) inside a pair of curly braces.\nLet’s take a look at the code example below. You can see in this example that, I’m creating an enum type called Role. We talk more about enums at Section 7.6. But in essence, this Role type is listing different types of roles in a fictitious company, like SE for Software Engineer, DE for Data Engineer, PM for Product Manager, etc.\nNotice that we are using the value from the role object in the switch statement, to discover which exact area we need to store in the area variable object. Also notice that we are using type inference inside the switch statement, with the dot character, as we described at Section 2.4. This makes the zig compiler infer the correct data type of the values (PM, SE, etc.) for us.\nAlso notice that, we are grouping multiple values in the same branch of the switch statement. We just separate each possible value with a comma. So, for example, if role contains either DE or DA, the area variable would contain the value \"Data & Analytics\", instead of \"Platform\".\n\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst Role = enum {\n SE, DPE, DE, DA, PM, PO, KS\n};\n\npub fn main() !void {\n var area: []const u8 = undefined;\n const role = Role.SE;\n switch (role) {\n .PM, .SE, .DPE, .PO => {\n area = \"Platform\";\n },\n .DE, .DA => {\n area = \"Data & Analytics\";\n },\n .KS => {\n area = \"Sales\";\n },\n }\n try stdout.print(\"{s}\\n\", .{area});\n}\n\nPlatform\n\n\nNow, one very important aspect about this switch statement presented in the code example above, is that it exhaust all existing possibilities. In other words, all possible values that could be found inside the order object are explicitly handled in this switch statement.\nSince the role object have type Role, the only possible values to be found inside this object are PM, SE, DPE, PO, DE, DA and KS. There is no other possible value to be stored in this role object. This what “exhaust all existing possibilities” means. The switch statement covers every possible case.\nIn Zig, switch statements must exhaust all existing possibilities. You cannot write a switch statement, and leave an edge case with no expliciting action to be taken. This is a similar behaviour to switch statements in Rust, which also have to handle all possible cases.\nTake a look at the dump_hex_fallible() function below as an example. This function also comes from the Zig Standard Library, but this time, it comes from the debug.zig module1. There are multiple lines in this function, but I omitted them to focus solely on the switch statement found in this function. Notice that this switch statement have four possible cases, or four explicit branches. Also, notice that we used an else branch in this case. Whenever you have multiple possible cases in your switch statement which you want to apply the same exact action, you can use an else branch to do that.\n\npub fn dump_hex_fallible(bytes: []const u8) !void {\n // Many lines ...\n switch (byte) {\n '\\n' => try writer.writeAll(\"␊\"),\n '\\r' => try writer.writeAll(\"␍\"),\n '\\t' => try writer.writeAll(\"␉\"),\n else => try writer.writeByte('.'),\n }\n}\n\nMany users would also use an else branch to handle a “not supported” case. That is, a case that cannot be properly handled by your code, or, just a case that should not be “fixed”. So many programmers use an else branch to panic (or raise an error) to stop the current execution.\nTake the code example below as an example. We can see that, we are handling the cases for the level object being either 1, 2, or 3. All other possible cases are not supported by default, and, as consequence, we raise an runtime error in these cases, through the @panic() built-in function.\nAlso notice that, we are assigning the result of the switch statement to a new object called category. This is another thing that you can do with switch statements in Zig. If the branchs in this switch statement output some value as result, you can store the result value of the switch statement into a new object.\n\nconst level: u8 = 4;\nconst category = switch (level) {\n 1, 2 => \"beginner\",\n 3 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n\nthread 13103 panic: Not supported level!\nt.zig:9:13: 0x1033c58 in main (switch2)\n @panic(\"Not supported level!\");\n ^\nFurthermore, you can also use ranges of values in switch statements. That is, you can create a branch in your switch statement that is used whenever the input value is contained in a range. These range expressions are created with the operator .... Is important to emphasize that the ranges created by this operator are inclusive on both ends.\nFor example, I could easily change the code example above to support all levels between 0 and 100. Like this:\n\nconst level: u8 = 4;\nconst category = switch (level) {\n 0...25 => \"beginner\",\n 26...75 => \"intermediary\",\n 76...100 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n\nbeginner\n\n\nThis is neat, and it works with character ranges too. That is, I could simply write 'a'...'z', to match any character value that is a lowercase letter, and it would work fine.\n\n\n2.1.3 The defer keyword\nWith 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.\n\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nfn foo() !void {\n defer std.debug.print(\n \"Exiting function ...\\n\", .{}\n );\n try stdout.print(\"Adding some numbers ...\\n\", .{});\n const x = 2 + 2; _ = x;\n try stdout.print(\"Multiplying ...\\n\", .{});\n const y = 2 * 8; _ = y;\n}\n\npub fn main() !void {\n try foo();\n}\n\nAdding some numbers ...\nMultiplying ...\nExiting function ...\nIt 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.\n\n\n2.1.4 For loops\nA loop allows you to execute the same lines of code multiple times, thus, creating a “repetition space” in the execution flow of your program. Loops are particularly useful when we want to replicate the same function (or the same set of commands) over several different inputs.\nThere are different types of loops available in Zig. But the most essential of them all is probably the for loop. A for loop is used to apply the same piece of code over the elements of a slice or an array.\nFor loops in Zig have a slightly different syntax that you are probably used to see in other languages. You start with the for keyword, then, you list the items that you want to iterate over inside a pair of parentheses. Then, inside of a pair of pipes (|) you should declare an identifier that will serve as your iterator, or, the “repetition index of the loop”.\n\nfor (items) |value| {\n // code to execute\n}\n\nInstead of using a (value in items) syntax, in Zig, for loops use the syntax (items) |value|. In the example below, you can see that we are looping through the items of the array stored at the object name, and printing to the console the decimal representation of each character in this array.\nIf we wanted, we could also iterate through a slice (or a portion) of the array, instead of iterating through the entire array stored in the name object. Just use a range selector to select the section you want. For example, I could provide the expression name[0..3] to the for loop, to iterate just through the first 3 elements in the array.\n\nconst name = [_]u8{'P','e','d','r','o'};\nfor (name) |char| {\n try stdout.print(\"{d} | \", .{char});\n}\n\n80 | 101 | 100 | 114 | 111 | \n\n\nIn the above example we are using the value itself of each element in the array as our iterator. But there are many situations where we need to use an index instead of the actual values of the items.\nYou can do that by providing a second set of items to iterate over. More precisely, you provide the range selector 0.. to the for loop. So, yes, you can use two different iterators at the same time in a for loop in Zig.\nBut remember from Section 1.4 that, every object you create in Zig must be used in some way. So if you declare two iterators in your for loop, you must use both iterators inside the for loop body. But if you want to use just the index iterator, and not use the “value iterator”, then, you can discard the value iterator by maching the value items to the underscore character, like in the example below:\n\nfor (name, 0..) |_, i| {\n try stdout.print(\"{d} | \", .{i});\n}\n\n0 | 1 | 2 | 3 | 4 |\n\n\n2.1.5 While loops\nA while loop is created from the while keyword. A for loop iterates through the items of an array, but a while loop will loop continuously, and infinitely, until a logical test (specified by you) becomes false.\nYou start with the while keyword, then, you define a logical expression inside a pair of parentheses, and the body of the loop is provided inside a pair of curly braces, like in the example below:\n\nvar i: u8 = 1;\nwhile (i < 5) {\n try stdout.print(\"{d} | \", .{i});\n i += 1;\n}\n\n1 | 2 | 3 | 4 | \n\n\n\n\n2.1.6 Using break and continue\nIn Zig, you can explicitly stop the execution of a loop, or, jump to the next iteration of the loop, using the keywords break and continue, respectively. The while loop present in the example below, is at first sight, an infinite loop. Because the logical value inside the parenthese will always be equal to true. What makes this while loop stop when the i object reaches the count 10? Is the break keyword!\nInside the while loop, we have an if statement that is constantly checking if the i variable is equal to 10. Since we are increasing the value of this i variable at each iteration of the while loop. At some point, this i variable will be equal to 10, and when it does, the if statement will execute the break expression, and, as a result, the execution of the while loop is stopped.\nNotice the expect() function from the Zig standard library after the while loop. This expect() function is an “assert” type of function. This function checks if the logical test provided is equal to true. If this logical test is false, the function raises an assertion error. But it is equal to true, then, the function will do nothing.\n\nvar i: usize = 0;\nwhile (true) {\n if (i == 10) {\n break;\n }\n i += 1;\n}\ntry std.testing.expect(i == 10);\ntry stdout.print(\"Everything worked!\", .{});\n\nEverything worked!\n\n\nSince this code example was executed succesfully by the zig compiler, without raising any errors, then, we known that, after the execution of while loop, the i variable is equal to 10. Because if it wasn’t equal to 10, then, an error would be raised by expect().\nNow, in the next example, we have an use case for the continue keyword. The if statement is constantly checking if the current index is a multiple of 2. If it is, then we jump to the next iteration of the loop directly. But it the current index is not a multiple of 2, then, the loop will simply print this index to the console.\n\nconst ns = [_]u8{1,2,3,4,5,6};\nfor (ns) |i| {\n if ((i % 2) == 0) {\n continue;\n }\n try stdout.print(\"{d} | \", .{i});\n}\n\n1 | 3 | 5 |", + "crumbs": [ + "2  Structs, Modules and Control Flow" + ] + }, + { + "objectID": "Chapters/03-structs.html#sec-fun-pars", + "href": "Chapters/03-structs.html#sec-fun-pars", + "title": "2  Structs, Modules and Control Flow", + "section": "2.2 Function parameters are immutable", + "text": "2.2 Function parameters are immutable\nWe have already discussed a lot of the syntax behind function declarations at Section 1.2.2 and Section 1.2.3. But I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig. In summary, function parameters are immutable in Zig.\nTake the code example below, where we declare a simple function that just tries to add some amount to the input integer, and returns the result back. But if you look closely at the body of this add2() function, you will notice that we try to save the result back into the x function argument.\nIn other words, this function not only use the value that it received through the function argument x, but it also tries to change the value of this function argument, by assigning the addition result into x. However, function arguments in Zig are immutable. You cannot change their values, or, you cannot assign values to them inside the body’s function.\nThis is the reason why, the code example below do not compile successfully. If you try to compile this code example, you get a compile error warning you that you are trying to change the value of a immutable (i.e. constant) object.\n\nconst std = @import(\"std\");\nfn add2(x: u32) u32 {\n x = x + 2;\n return x;\n}\n\npub fn main() !void {\n const y = add2(4);\n std.debug.print(\"{d}\\n\", .{y});\n}\n\nt.zig:3:5: error: cannot assign to constant\n x = x + 2;\n ^\nIf a function argument receives as input a object whose data type is any of the primitive types that we have listed at Section 1.5 this object is always passed by value to the function. In other words, this object is copied to the function stack frame.\nHowever, if the input object have a more complex data type, for example, it might be a struct instance, or an array, or a union, etc., in cases like that, the zig compiler will take the liberty of deciding for you which strategy is best. The zig compiler will pass your object to the function either by value, or by reference. The compiler will always choose the strategy that is faster for you. This optimization that you get for free is possible only because function arguments are immutable in Zig.\n\nconst std = @import(\"std\");\nfn add2(x: *u32) void {\n x = x + 2;\n}\n\npub fn main() !void {\n var x: u32 = @intCast(4);\n add2(&x);\n std.debug.print(\"{d}\\n\", .{x});\n}", "crumbs": [ "2  Structs, Modules and Control Flow" ] @@ -223,8 +233,8 @@ "objectID": "Chapters/03-structs.html#sec-structs-and-oop", "href": "Chapters/03-structs.html#sec-structs-and-oop", "title": "2  Structs, Modules and Control Flow", - "section": "2.2 Structs and OOP", - "text": "2.2 Structs and OOP\nZig is a language more closely related to C (which is a procedural language), than it is to C++ or Java (which are object-oriented languages). Because of that, you do not have advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or class inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C. You give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can also register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object that you create with this new type, will always have these methods available and associated with them.\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object of this particular class, and you also have a destructor method (or a destructor function) that is the function responsible for destroying every object of this class.\nIn Zig, we normally declare the constructor and the destructor methods of our structs, by declaring an init() and a deinit() methods inside the struct. This is just a naming convention that you will find across the entire Zig standard library. 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.\nThe 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 Section 3.2. 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.\nNotice the data members of this struct, id, name and email. Every data member have it’s type explicitly annotated, with the colon character (:) syntax that we described earlier at Section 1.2.2. But also notice that every line in the struct body that describes a data member, ends with a comma character (,). So every time you declare a data member in your Zig code, always end the line with a comma character, instead of ending it with the traditional semicolon character (;).\nNext, also notice in this example, that we registrated an init() function as a method of this User struct. This init() method is the constructor method that you use to instantiate every new User object. That is why this init() function return an User object as result.\n\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n\npedro\n\n\nThe pub keyword plays an important role in struct declarations, and OOP in Zig. Every method that you declare in your struct that is marked with the keyword pub, becomes a public method of this particular struct.\nSo every method that you create in your struct, is, at first, a private method of that struct. Meaning that, this method can only be called from within this struct. But, if you mark this method as public, with the keyword pub, then, you can call the method directly from the User object you have in your code.\nIn other words, the functions marked by the keyword pub are members of the public API of that struct. For example, if I did not marked the print_name() method as public, then, I could not execute the line u.print_name(). Because I would not be authorized to call this method directly in my code.\n\n2.2.1 Anonymous struct literals\nYou can declare a struct object as a literal value. When we do that, we normally specify the data type of this struct literal by writing it’s data type just before the opening curly braces. For example, I could write a struct literal of type User that we defined in the previous section like this:\n\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a struct literal, but not especify explicitly the type of this particular struct. An anonymous struct is written by using the syntax .{}. So, we essentially replaced the explicit type of the struct literal with a dot character (.).\nAs we described at Section 2.3, when you put a dot before a struct literal, the type of this struct literal is automatically inferred by the zig compiler. In essence, the zig compiler will look for some hint of what is the type of that struct. It can be the type annotation of an function argument, or the return type annotation of the function that you are using, or the type annotation of a variable. If the compiler do find such type annotation, then, it will use this type in your literal struct.\nAnonymous structs are very commom to use in function arguments in Zig. One example that you have seen already constantly, is the print() function from the stdout object. This function takes two arguments. The first argument, is a template string, which should contain string format specifiers in it, which tells how the values provided in the second argument should be printed into the message.\nWhile the second argument is a struct literal that lists the values to be printed into the template message specified in the first argument. You normally want to use an anonymous struct literal here, so that, the zig compiler do the job of specifying the type of this particular anonymous struct for you.\n\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n\nHello, world!\n\n\n\n\n2.2.2 Struct declarations must be constant\nTypes in Zig must be const or comptime (we are going to talk more about comptime at Section 12.1). What this means is that you cannot create a new data type, and mark it as variable with the var keyword. So struct declarations are always constant. You cannot declare a new struct using the var keyword. It must be const.\nIn the Vec3 example below, this declaration is allowed because I’m using the const keyword to declare this new data type.\n\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n\n\n\n2.2.3 The self method argument\nIn every language that have OOP, when we declare a method of some class or struct, we usually declare this method as a function that have a self argument. This self argument is the reference to the object itself from which the method is being called from.\nIs not mandatory to use this self argument. But why would you not use this self argument? There is no reason to not use it. Because the only way to get access to the data stored in the data members of your struct is to access them through this self argument. If you don’t need to use the data in the data members of your struct inside your method, then, you very likely don’t need a method, you can just simply declare this logic as a simple function, outside of your struct declaration.\nTake the Vec3 struct below. Inside this Vec3 struct we declared a method named distance(). This method calculates the distance between two Vec3 objects, by following the distance formula in euclidean space. Notice that this distance() method takes two Vec3 objects as input, self and other.\n\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n\nThe self argument corresponds to the Vec3 object from which this distance() method is being called from. While the other is a separate Vec3 object that is given as input to this method. In the example below, the self argument corresponds to the object v1, because the distance() method is being called from the v1 object, while the other argument corresponds to the object v2.\n\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n\nDistance: 3.3970575502926055\n\n\n2.2.4 About the struct state\nSometimes you don’t need to care about the state of your struct object. Sometimes, you just need to instantiate and use the objects, without altering their state. You can notice that when you have methods inside this struct object that might use the values that are present the data members, but they do not alter the values in the data members of structs in anyway.\nThe Vec3 struct that we presented in the previous section is an example of that. This struct have a single method named distance(), and this method do use the values present in all three data members of the struct (x, y and z). But at the same time, this method do not change the values of these data members in any point.\nAs a result of that, when we create Vec3 objects we usually create them as constant objects, like the v1 and v2 objects presented in the previous code example. We can create them as variable objects with the var keyword, if we want to. But because the methods of this Vec3 struct do not change the state of the objects in any point, is unnecessary to mark them as variable objects.\nBut why? Why am I talkin about this here? Is because the self argument in the methods is affected depending on whether the methods present in a struct change or not the state of the object itself. More specifically, when you have a method in a struct that changes the state of the object (i.e. change the value of a data member), the self argument in this method must be annotated in a different manner.\nAs I described in the previous section, the self argument in methods of a struct is the argument that receives as input the object from which the method was called from. We usually annotate this argument in the methods by writing self, followed by the colon character (:), and the data type of the struct to which the method belongs to (e.g. User, Vec3, etc.).\nIf we take the Vec3 struct that we defined in the previous section as an example, we can see in the distance() method that this self argument is annotated as self: Vec3. Because the state of the Vec3 object is never altered by this method.\nBut what if we do have a method that alters the state of the object, by altering the values of it’s data members. How should we annotate self in this instance? The answer is: “we should pass a pointer of x to self, and not simply a copy of x to self”. In other words, you should annotate self as self: *x, instead of annotating it as self: x.\nIf we create a new method inside the Vec3 object that, for example, expands the vector by multiplying it’s coordinates by a factor o two, then, we need to follow this rule specified in the previous paragraph. The code example below demonstrates this idea:\n\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n\nNotice in the code example above that we have added a new method to our Vec3 struct named double(). This method essentially doubles the coordinate values of our vector object. Also notice that, in the case of the double() method, we annotated the self argument as *Vec3, indicating that this argument receives a pointer (or a reference, if you prefer to call it this way) to a Vec3 object, instead of receiving a copy of the object directly, as input.\n\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n\nDoubled: 8.4\nIf you change the self argument in this double() method to self: Vec3, like in the distance() method, you will get the error exposed below as result. Notice that this error message is indicating a line from the double() method body, indicating that you cannot alter the value of the x data member.\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\nThis error message indicates that the x data member belongs to a constant object, and, because of that, it cannot be changed. Even though we marked the v3 object that we have created in the previous code example as a variable object. So even though this x data member belongs to a variable object in our code, this error message is pointing to the opposite direction.\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\nBut this error message is misleading, because the only thing that we have changed is the self argument signature from *Vec3 to Vec3 in the double() method. So, just remember this general rule below about your method declarations:\n\n\n\n\n\n\nNote\n\n\n\nIf a method of your x struct alters the state of the object, by changing the value of any data member, then, remember to use self: *x, instead of self: x in the function signature of this method.\n\n\nYou could also interpret the content discussed in this section as: “if you need to alter the state of your x struct object in one of it’s methods, you must pass the x struct object by reference to the self argument of this method, instead of passing it by value”.", + "section": "2.3 Structs and OOP", + "text": "2.3 Structs and OOP\nZig is a language more closely related to C (which is a procedural language), than it is to C++ or Java (which are object-oriented languages). Because of that, you do not have advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or class inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C. You give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can also register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object that you create with this new type, will always have these methods available and associated with them.\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object of this particular class, and you also have a destructor method (or a destructor function) that is the function responsible for destroying every object of this class.\nIn Zig, we normally declare the constructor and the destructor methods of our structs, by declaring an init() and a deinit() methods inside the struct. This is just a naming convention that you will find across the entire Zig standard library. 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.\nThe 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 Section 3.2. 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.\nNotice the data members of this struct, id, name and email. Every data member have it’s type explicitly annotated, with the colon character (:) syntax that we described earlier at Section 1.2.2. But also notice that every line in the struct body that describes a data member, ends with a comma character (,). So every time you declare a data member in your Zig code, always end the line with a comma character, instead of ending it with the traditional semicolon character (;).\nNext, also notice in this example, that we registrated an init() function as a method of this User struct. This init() method is the constructor method that you use to instantiate every new User object. That is why this init() function return an User object as result.\n\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n\npedro\n\n\nThe pub keyword plays an important role in struct declarations, and OOP in Zig. Every method that you declare in your struct that is marked with the keyword pub, becomes a public method of this particular struct.\nSo every method that you create in your struct, is, at first, a private method of that struct. Meaning that, this method can only be called from within this struct. But, if you mark this method as public, with the keyword pub, then, you can call the method directly from the User object you have in your code.\nIn other words, the functions marked by the keyword pub are members of the public API of that struct. For example, if I did not marked the print_name() method as public, then, I could not execute the line u.print_name(). Because I would not be authorized to call this method directly in my code.\n\n2.3.1 Anonymous struct literals\nYou can declare a struct object as a literal value. When we do that, we normally specify the data type of this struct literal by writing it’s data type just before the opening curly braces. For example, I could write a struct literal of type User that we defined in the previous section like this:\n\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a struct literal, but not especify explicitly the type of this particular struct. An anonymous struct is written by using the syntax .{}. So, we essentially replaced the explicit type of the struct literal with a dot character (.).\nAs we described at Section 2.4, when you put a dot before a struct literal, the type of this struct literal is automatically inferred by the zig compiler. In essence, the zig compiler will look for some hint of what is the type of that struct. It can be the type annotation of an function argument, or the return type annotation of the function that you are using, or the type annotation of a variable. If the compiler do find such type annotation, then, it will use this type in your literal struct.\nAnonymous structs are very commom to use in function arguments in Zig. One example that you have seen already constantly, is the print() function from the stdout object. This function takes two arguments. The first argument, is a template string, which should contain string format specifiers in it, which tells how the values provided in the second argument should be printed into the message.\nWhile the second argument is a struct literal that lists the values to be printed into the template message specified in the first argument. You normally want to use an anonymous struct literal here, so that, the zig compiler do the job of specifying the type of this particular anonymous struct for you.\n\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n\nHello, world!\n\n\n\n\n2.3.2 Struct declarations must be constant\nTypes in Zig must be const or comptime (we are going to talk more about comptime at Section 12.1). What this means is that you cannot create a new data type, and mark it as variable with the var keyword. So struct declarations are always constant. You cannot declare a new struct using the var keyword. It must be const.\nIn the Vec3 example below, this declaration is allowed because I’m using the const keyword to declare this new data type.\n\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n\n\n\n2.3.3 The self method argument\nIn every language that have OOP, when we declare a method of some class or struct, we usually declare this method as a function that have a self argument. This self argument is the reference to the object itself from which the method is being called from.\nIs not mandatory to use this self argument. But why would you not use this self argument? There is no reason to not use it. Because the only way to get access to the data stored in the data members of your struct is to access them through this self argument. If you don’t need to use the data in the data members of your struct inside your method, then, you very likely don’t need a method, you can just simply declare this logic as a simple function, outside of your struct declaration.\nTake the Vec3 struct below. Inside this Vec3 struct we declared a method named distance(). This method calculates the distance between two Vec3 objects, by following the distance formula in euclidean space. Notice that this distance() method takes two Vec3 objects as input, self and other.\n\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n\nThe self argument corresponds to the Vec3 object from which this distance() method is being called from. While the other is a separate Vec3 object that is given as input to this method. In the example below, the self argument corresponds to the object v1, because the distance() method is being called from the v1 object, while the other argument corresponds to the object v2.\n\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n\nDistance: 3.3970575502926055\n\n\n2.3.4 About the struct state\nSometimes you don’t need to care about the state of your struct object. Sometimes, you just need to instantiate and use the objects, without altering their state. You can notice that when you have methods inside your struct declaration that might use the values that are present in the data members, but they do not alter the values in the data members of the struct in anyway.\nThe Vec3 struct that was presented at Section 2.3.3 is an example of that. This struct have a single method named distance(), and this method do use the values present in all three data members of the struct (x, y and z). But at the same time, this method do not change the values of these data members in any point.\nAs a result of that, when we create Vec3 objects we usually create them as constant objects, like the v1 and v2 objects presented at Section 2.3.3. We can create them as variable objects with the var keyword, if we want to. But because the methods of this Vec3 struct do not change the state of the objects in any point, is unnecessary to mark them as variable objects.\nBut why? Why am I talkin about this here? Is because the self argument in the methods is affected depending on whether the methods present in a struct change or not the state of the object itself. More specifically, when you have a method in a struct that changes the state of the object (i.e. change the value of a data member), the self argument in this method must be annotated in a different manner.\nAs I described at Section 2.3.3, the self argument in methods of a struct is the argument that receives as input the object from which the method was called from. We usually annotate this argument in the methods by writing self, followed by the colon character (:), and the data type of the struct to which the method belongs to (e.g. User, Vec3, etc.).\nIf we take the Vec3 struct that we defined in the previous section as an example, we can see in the distance() method that this self argument is annotated as self: Vec3. Because the state of the Vec3 object is never altered by this method.\nBut what if we do have a method that alters the state of the object, by altering the values of it’s data members. How should we annotate self in this instance? The answer is: “we should annotate self as a pointer of x, instead of just x”. In other words, you should annotate self as self: *x, instead of annotating it as self: x.\nIf we create a new method inside the Vec3 object that, for example, expands the vector by multiplying it’s coordinates by a factor o two, then, we need to follow this rule specified in the previous paragraph. The code example below demonstrates this idea:\n\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n\nNotice in the code example above that we have added a new method to our Vec3 struct named double(). This method essentially doubles the coordinate values of our vector object. Also notice that, in the case of the double() method, we annotated the self argument as *Vec3, indicating that this argument receives a pointer (or a reference, if you prefer to call it this way) to a Vec3 object as input.\n\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n\nDoubled: 8.4\nNow, if you change the self argument in this double() method to self: Vec3, like in the distance() method, you will get the compiler error exposed below as result. Notice that this error message is indicating a line from the double() method body, indicating that you cannot alter the value of the x data member.\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\nThis error message indicates that the x data member belongs to a constant object, and, because of that, it cannot be changed. Ultimately, this error message is telling us that the self argument is constant.\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\nIf you take some time, and think hard about this error message, you will understand it. You already have the tools to understand why we are getting this error message. We have talked about it already at Section 2.2.\nSo remember, every function argument is immutable in Zig. It does not matter if we marked the v3 object as a variable object. Because we are trying to alter self directly, which is a function argument, and, every function argument is immutable by default. We overcome this barrier, by explicitly marking the self argument as a pointer.\n\n\n\n\n\n\nNote\n\n\n\nIf a method of your x struct alters the state of the object, by changing the value of any data member, then, remember to use self: *x, instead of self: x in the function signature of this method.\n\n\nYou could also interpret the content discussed in this section as: “if you need to alter the state of your x struct object in one of it’s methods, you must explicitly pass the x struct object by reference to the self argument of this method”.", "crumbs": [ "2  Structs, Modules and Control Flow" ] @@ -233,8 +243,8 @@ "objectID": "Chapters/03-structs.html#sec-type-inference", "href": "Chapters/03-structs.html#sec-type-inference", "title": "2  Structs, Modules and Control Flow", - "section": "2.3 Type inference", - "text": "2.3 Type inference\nZig is kind of a strongly typed language. I say “kind of” because there are situations where you don’t have to explicitly write the type of every single object in your source code, as you would expect from a traditional strongly typed language, such as C and C++.\nIn some situations, the zig compiler can use type inference to solves the data types for you, easing some of the burden that you carry as a developer. The most commom way this happens is through function arguments that receives struct objects as input.\nIn general, type inference in Zig is done by using the dot character (.). Everytime you see a dot character written before a struct literal, or before a enum value, or something like that, you know that this dot character is playing a special party in this place. More specifically, it is telling the zig compiler something on the lines of: “Hey! Can you infer the type of this value for me? Please!”. In other words, this dot character is playing a role similar to the auto keyword in C++.\nI give you some examples of this at Section 2.2.1, where we present anonymous struct literals. Anonymous struct literals are, essentially, struct literals that use type inference to infer the exact type of this particular struct literal. This type inference is done by looking for some minimal hint of the correct data type to be used. You could say that the zig compiler looks for any neighbouring type annotation that might tell him what would be the correct type.\nAnother commom place where we use type inference in Zig is at switch statements (which we talk about at Section 2.1.2). So I also gave some other examples of type inference at Section 2.1.2, where we were inferring the data types of enum values listed inside of switch statements (e.g. .DE). But as another example, take a look at this fence() function reproduced below, which comes from the atomic.zig module2 of the Zig Standard Library.\nThere are a lot of things in this function that we haven’t talked about yet, such as: what comptime means? inline? extern? What is this star symbol before Self? Let’s just ignore all of these things, and focus solely on the switch statement that is inside this function.\nWe can see that this switch statement uses the order object as input. This order object is one of the inputs of this fence() function, and we can see in the type annotation, that this object is of type AtomicOrder. We can also see a bunch of values inside the switch statements that begins with a dot character, such as .release and .acquire.\nBecause these weird values contain a dot character before them, we are asking the zig compiler to infer the types of these values inside the switch statement. Then, the zig compiler is looking into the current context where these values are being used, and it is trying to infer the types of these values.\nSince they are being used inside a switch statement, the zig compiler looks into the type of the input object given to the switch statement, which is the order object in this case. Because this object have type AtomicOrder, the zig compiler infers that these values are data members from this type AtomicOrder.\n\npub inline fn fence(self: *Self, comptime order: AtomicOrder) void {\n // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.\n if (builtin.sanitize_thread) {\n const tsan = struct {\n extern \"c\" fn __tsan_acquire(addr: *anyopaque) void;\n extern \"c\" fn __tsan_release(addr: *anyopaque) void;\n };\n\n const addr: *anyopaque = self;\n return switch (order) {\n .unordered, .monotonic => @compileError(@tagName(order) ++ \" only applies to atomic loads and stores\"),\n .acquire => tsan.__tsan_acquire(addr),\n .release => tsan.__tsan_release(addr),\n .acq_rel, .seq_cst => {\n tsan.__tsan_acquire(addr);\n tsan.__tsan_release(addr);\n },\n };\n }\n\n return @fence(order);\n}\n\nThis is how basic type inference is done in Zig. If we didn’t use the dot character before the values inside this switch statement, then, we would be forced to write explicitly the data types of these values. For example, instead of writing .release we would have to write AtomicOrder.release. We would have to do this for every single value in this switch statement, and this is a lot of work. That is why type inference is commonly used on switch statements in Zig.", + "section": "2.4 Type inference", + "text": "2.4 Type inference\nZig is kind of a strongly typed language. I say “kind of” because there are situations where you don’t have to explicitly write the type of every single object in your source code, as you would expect from a traditional strongly typed language, such as C and C++.\nIn some situations, the zig compiler can use type inference to solves the data types for you, easing some of the burden that you carry as a developer. The most commom way this happens is through function arguments that receives struct objects as input.\nIn general, type inference in Zig is done by using the dot character (.). Everytime you see a dot character written before a struct literal, or before a enum value, or something like that, you know that this dot character is playing a special party in this place. More specifically, it is telling the zig compiler something on the lines of: “Hey! Can you infer the type of this value for me? Please!”. In other words, this dot character is playing a role similar to the auto keyword in C++.\nI give you some examples of this at Section 2.3.1, where we present anonymous struct literals. Anonymous struct literals are, essentially, struct literals that use type inference to infer the exact type of this particular struct literal. This type inference is done by looking for some minimal hint of the correct data type to be used. You could say that the zig compiler looks for any neighbouring type annotation that might tell him what would be the correct type.\nAnother commom place where we use type inference in Zig is at switch statements (which we talk about at Section 2.1.2). So I also gave some other examples of type inference at Section 2.1.2, where we were inferring the data types of enum values listed inside of switch statements (e.g. .DE). But as another example, take a look at this fence() function reproduced below, which comes from the atomic.zig module2 of the Zig Standard Library.\nThere are a lot of things in this function that we haven’t talked about yet, such as: what comptime means? inline? extern? What is this star symbol before Self? Let’s just ignore all of these things, and focus solely on the switch statement that is inside this function.\nWe can see that this switch statement uses the order object as input. This order object is one of the inputs of this fence() function, and we can see in the type annotation, that this object is of type AtomicOrder. We can also see a bunch of values inside the switch statements that begins with a dot character, such as .release and .acquire.\nBecause these weird values contain a dot character before them, we are asking the zig compiler to infer the types of these values inside the switch statement. Then, the zig compiler is looking into the current context where these values are being used, and it is trying to infer the types of these values.\nSince they are being used inside a switch statement, the zig compiler looks into the type of the input object given to the switch statement, which is the order object in this case. Because this object have type AtomicOrder, the zig compiler infers that these values are data members from this type AtomicOrder.\n\npub inline fn fence(self: *Self, comptime order: AtomicOrder) void {\n // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.\n if (builtin.sanitize_thread) {\n const tsan = struct {\n extern \"c\" fn __tsan_acquire(addr: *anyopaque) void;\n extern \"c\" fn __tsan_release(addr: *anyopaque) void;\n };\n\n const addr: *anyopaque = self;\n return switch (order) {\n .unordered, .monotonic => @compileError(@tagName(order) ++ \" only applies to atomic loads and stores\"),\n .acquire => tsan.__tsan_acquire(addr),\n .release => tsan.__tsan_release(addr),\n .acq_rel, .seq_cst => {\n tsan.__tsan_acquire(addr);\n tsan.__tsan_release(addr);\n },\n };\n }\n\n return @fence(order);\n}\n\nThis is how basic type inference is done in Zig. If we didn’t use the dot character before the values inside this switch statement, then, we would be forced to write explicitly the data types of these values. For example, instead of writing .release we would have to write AtomicOrder.release. We would have to do this for every single value in this switch statement, and this is a lot of work. That is why type inference is commonly used on switch statements in Zig.", "crumbs": [ "2  Structs, Modules and Control Flow" ] @@ -243,8 +253,8 @@ "objectID": "Chapters/03-structs.html#modules", "href": "Chapters/03-structs.html#modules", "title": "2  Structs, Modules and Control Flow", - "section": "2.4 Modules", - "text": "2.4 Modules\nWe already talked about what modules are, and also, how to import other modules into you current module through import statements, so that you can use functionality from these other modules in your current module. But in this section, I just want to make it clear that modules are actually structs in Zig.\nIn other words, every Zig module (i.e. a .zig file) that you write in your project is internally stored as a struct object. Take the line exposed below as an example. In this line we are importing the Zig Standard Library into our current module.\nconst std = @import(\"std\");\nWhen we want to access the functions and objects from the standard library, we are basically accessing the data members of the struct stored in the std object. That is why we use the same syntax that we use in normal structs, with the dot operator (.) to access the data members and methods of the struct.\nWhen this “import statement” get’s executed, the result of this expression is a struct object that contains the Zig Standard Library modules, global variables, functions, etc. And this struct object get’s saved (or stored) inside the constant object named std.\nTake the thread_pool.zig module from the project zap3 as an example. This module is written as if it was a big struct. That is why we have a top-level and public init() method written in this module. The idea is that all top-level functions written in this module are methods from the struct, and all top-level objects and struct declarations are data members of this struct. The module is the struct itself.\nSo you would import and use this module by doing something like this:\nconst std = @import(\"std\");\nconst ThreadPool = @import(\"thread_pool.zig\");\nconst num_cpus = std.Thread.getCpuCount()\n catch @panic(\"failed to get cpu core count\");\nconst num_threads = std.math.cast(u16, num_cpus)\n catch std.math.maxInt(u16);\nconst pool = ThreadPool.init(\n .{ .max_threads = num_threads }\n);", + "section": "2.5 Modules", + "text": "2.5 Modules\nWe already talked about what modules are, and also, how to import other modules into you current module through import statements, so that you can use functionality from these other modules in your current module. But in this section, I just want to make it clear that modules are actually structs in Zig.\nIn other words, every Zig module (i.e. a .zig file) that you write in your project is internally stored as a struct object. Take the line exposed below as an example. In this line we are importing the Zig Standard Library into our current module.\nconst std = @import(\"std\");\nWhen we want to access the functions and objects from the standard library, we are basically accessing the data members of the struct stored in the std object. That is why we use the same syntax that we use in normal structs, with the dot operator (.) to access the data members and methods of the struct.\nWhen this “import statement” get’s executed, the result of this expression is a struct object that contains the Zig Standard Library modules, global variables, functions, etc. And this struct object get’s saved (or stored) inside the constant object named std.\nTake the thread_pool.zig module from the project zap3 as an example. This module is written as if it was a big struct. That is why we have a top-level and public init() method written in this module. The idea is that all top-level functions written in this module are methods from the struct, and all top-level objects and struct declarations are data members of this struct. The module is the struct itself.\nSo you would import and use this module by doing something like this:\nconst std = @import(\"std\");\nconst ThreadPool = @import(\"thread_pool.zig\");\nconst num_cpus = std.Thread.getCpuCount()\n catch @panic(\"failed to get cpu core count\");\nconst num_threads = std.math.cast(u16, num_cpus)\n catch std.math.maxInt(u16);\nconst pool = ThreadPool.init(\n .{ .max_threads = num_threads }\n);", "crumbs": [ "2  Structs, Modules and Control Flow" ] @@ -834,7 +844,7 @@ "href": "Chapters/09-data-structures.html#dynamic-arrays", "title": "11  Data Structures", "section": "", - "text": "11.1.1 Capacity vs Length\nWhen we talk about dynamic arrays, we have two similar concepts that are very essential to how a dynamic array works behind the hood. These concepts are capacity and length. In some contexts, specially in C++, length is also called of size.\nAlthough they look similar, these concepts represent different things in the context of dynamic arrays. Capacity is the number of items (or elements) that your dynamic array can currently hold without the need to allocate more memory.\nIn contrast, the length refers to how many elements in the array are currently being used, or, in other words, how many elements in this array that you assigned a value to. Every dynamic array works around a block of allocated memory that represents an array with total capacity of \\(n\\) elements, but only a portion of these \\(n\\) elements are being used most of the time. This portion of \\(n\\) is the length of the array. So every time you append a new value to the array, you are incrementing it’s length by one.\nThis means that a dynamic array usually works with an extra margin, or, an extra space which is currently empty, but it is waiting and ready to be used. This “extra space” is essentially the difference between capacity and length. Capacity represents the total number of elements that the array can hold without the need to re-allocate or re-expand the array, while the length represents how much of this capacity is currently being used to hold/store values.\nFigure 11.1 presents this idea visually. Notice that, at first, the capacity of the array is greater than the length of the array. So, the dynamic array have extra space that is currently empty, but it is ready to receive a value to be stored.\n\n\n\n\n\n\nFigure 11.1: Difference between capacity and length in a dynamic array\n\n\n\nWe can also see at Figure 11.1 that, when length and capacity are equal, it means that the array have no space left. We reached the roof of our capacity, and because of that, if we want to store more values in this array, we need to expand it. We need to get a bigger space that can hold more values that we currently have.\nA dynamic array works by expanding the underlying array, whenever the length becomes equal to the capacity of the array. It basically allocates a new contiguos block of memory that is bigger than the previous one, then, it copies all values that are currently being stored to this new location (i.e. this new block of memory), then, it frees the previous block of memory. At the end of this process, the new underlying array have a bigger capacity, and, therefore, the length becomes once again smaller than the capacity of the array.\nThis is the cycle of an dynamic array. Notice that, throughout this cycle, the capacity is always either equal to or higher than the length of the array. If youh have an ArrayList object, let’s suppose you named it of buffer, you can check the current capacity of your array by accessing the capacity attribute of your ArrayList object, while the current length of it is available through the items.len attribute of your ArrayList object.\n\n// Check capacity\nbuffer.capacity;\n// Check length\nbuffer.items.len;\n\n\n\n11.1.2 Creating an ArrayList object\nIn order to use ArrayList, you must provide an allocator object to it. Remember, Zig does not have a default memory allocator. And as I described at Section 3.2, all memory allocations must be done by allocator objects that you define, that you have control over. In our example here, I’m going to use a general purpose allocator, but you can use any other allocator of your preference.\nWhen you initialize an ArrayList object, you must provide the data type of the elements of the array. In other words, this defines the type of data that this array (or container) will store. Therefore, if I provide the u8 type to it, then, I will create a dynamic array of u8 values. However, if I provide a struct that I defined instead, like the struct User from Section 2.2, then, a dynamic array of User values will be created. In the example below, with the expression ArrayList(u8) we are creating a dynamic array of u8 values.\nAfter you provide the data type of the elements of the array, you can initialize an ArrayList object by either using the init() or the initCapacity() method. The former method receives only the allocator object as input, while the latter method receives both the allocator object and a capacity number as inputs. With the latter method, you not only initialize the struct, but you also set the starting capacity of the allocated array.\nUsing the initCapacity() method is the preferred way to initialize your dynamic array. Because reallocations, or, in other words, the process of expanding the capacity of the array, is always a high cost operation. You should take any possible opportunity to avoid reallocations in your array. If you know how much space your array needs to occupy at the beginning, you should always use initCapacity() to create your dynamic array.\n\nvar gpa = std.heap.GeneralPurposeAllocator(.{}){};\nconst allocator = gpa.allocator();\nvar buffer = try std.ArrayList(u8)\n .initCapacity(allocator, 100);\ndefer buffer.deinit();\n\nIn the example above, the buffer object starts as an array of 100 elements. If this buffer object needs to create more space to accomodate more elements during the runtime of your program, the ArrayList internals will perform the necessary actions for you automatically. Also notice the deinit() method being used to destroy the buffer object at the end of the current scope, by freeing all the memory that was allocated for the dynamic array stored in this buffer object.\n\n\n11.1.3 Adding new elements to the array\nNow that we created our dynamic array, we can start to use it. You can append (a.k.a “add”) new values to this array by using the append() method. This method works the same way as the append() method from a Python list, or, the emplace_back() method from std::vector of C++. You provide a single value to this method, and the method appends this value to the array.\nYou can also use the appendSlice() method to append multiple values at once. You provide a slice (slices were described at Section 1.6) to this method, and the method adds all values present in this slice to your dynamic array.\n\ntry buffer.append('H');\ntry buffer.append('e');\ntry buffer.append('l');\ntry buffer.append('l');\ntry buffer.append('o');\ntry buffer.appendSlice(\" World!\");\n\n\n\n11.1.4 Removing elements from the array\nYou can use the pop() method to “pop” or remove the last element in the array. Is worth noting that this method do not change the capacity of the array. It just deletes or erases the last value stored in the array.\nAlso, this method returns as result the value that got deleted. That is, you can use this method to both get the last value in the array, and also, remove it from the array. It is a “get and remove value” type of method.\n\nconst exclamation_mark = buffer.pop();\n\nNow, if you want to remove specific elements from specific positions of your array, you can use the orderedRemove() method from your ArrayList object. With this method, you can provide an index as input, then, the method will delete the value that is at this index in the array. This effectively reduces the length of the array everytime you execute an orderedRemove() operation.\nIn the example below, we first create an ArrayList object, and we fill it with numbers. Then, we use orderedRemove() to remove the value at index 3 in the array, two consecutive times.\nAlso, notice that we are assigning the result of orderedRemove() to the underscore character. So we are discarding the result value of this method. As the result value, the orderedRemove() method returns the value that got deleted, in a similar style to the pop() method.\n\nvar gpa = std.heap.GeneralPurposeAllocator(.{}){};\nconst allocator = gpa.allocator();\nvar buffer = try std.ArrayList(u8)\n .initCapacity(allocator, 100);\ndefer buffer.deinit();\n\nfor (0..10) |i| {\n const index: u8 = @intCast(i);\n try buffer.append(index);\n}\n\nstd.debug.print(\n \"{any}\\n\", .{buffer.items}\n);\n_ = buffer.orderedRemove(3);\n_ = buffer.orderedRemove(3);\n\nstd.debug.print(\n \"{any}\\n\", .{buffer.items}\n);\nstd.debug.print(\n \"{any}\\n\", .{buffer.items.len}\n);\n\n{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }\n{ 0, 1, 2, 5, 6, 7, 8, 9 }\n8\nOne key characteristic about orderedRemove() is that it preserves the order of the values in the array. So, it deletes the value that you asked it to remove, but it also makes sure that the order of the values that remain in the array stay the same as before.\nNow, if you don’t care about the order of the values, for example, maybe you want to treat your dynamic array as a set of values, like the std::unordered_set structure from C++, you can use the swapRemove() method instead. This method works similarly to the orderedRemove() method. You give an index to this method, then, it deletes the value that is at this index in the array. But this method does not preserve the original order of the values that remain in the array. As a result, swapRemove() is, in general, faster than orderedRemove().\n\n\n11.1.5 Inserting elements at specific indexes\nWhen you need to insert values in the middle of your array, instead of just appending them to the end of the array, you need to use the insert() and insertSlice() methods, instead of the append() and appendSlice() methods.\nThese two methods work very similarly to insert() and insert_range() from the C++ vector class. You provide an index to these methods, and they insert the values that you provide at that index in the array.\n\nvar gpa = std.heap.GeneralPurposeAllocator(.{}){};\nconst allocator = gpa.allocator();\nvar buffer = try std.ArrayList(u8)\n .initCapacity(allocator, 10);\ndefer buffer.deinit();\n\ntry buffer.appendSlice(\"My Pedro\");\ntry buffer.insert(4, '3');\ntry buffer.insertSlice(2, \" name\");\nfor (buffer.items) |char| {\n try stdout.print(\"{c}\", .{char});\n}\n\nMy name P3edro\n\n\n11.1.6 Conclusion\nIf you feel the lack of some other method, I recommend you to read the official documentation for the ArrayListAligned1 struct, which describes most of the methods available through the ArrayList object.\nYou will notice that there is a lot other methods in this page that I did not described here, and I recommend you to explore these methods, and understand how they work.", + "text": "11.1.1 Capacity vs Length\nWhen we talk about dynamic arrays, we have two similar concepts that are very essential to how a dynamic array works behind the hood. These concepts are capacity and length. In some contexts, specially in C++, length is also called of size.\nAlthough they look similar, these concepts represent different things in the context of dynamic arrays. Capacity is the number of items (or elements) that your dynamic array can currently hold without the need to allocate more memory.\nIn contrast, the length refers to how many elements in the array are currently being used, or, in other words, how many elements in this array that you assigned a value to. Every dynamic array works around a block of allocated memory that represents an array with total capacity of \\(n\\) elements, but only a portion of these \\(n\\) elements are being used most of the time. This portion of \\(n\\) is the length of the array. So every time you append a new value to the array, you are incrementing it’s length by one.\nThis means that a dynamic array usually works with an extra margin, or, an extra space which is currently empty, but it is waiting and ready to be used. This “extra space” is essentially the difference between capacity and length. Capacity represents the total number of elements that the array can hold without the need to re-allocate or re-expand the array, while the length represents how much of this capacity is currently being used to hold/store values.\nFigure 11.1 presents this idea visually. Notice that, at first, the capacity of the array is greater than the length of the array. So, the dynamic array have extra space that is currently empty, but it is ready to receive a value to be stored.\n\n\n\n\n\n\nFigure 11.1: Difference between capacity and length in a dynamic array\n\n\n\nWe can also see at Figure 11.1 that, when length and capacity are equal, it means that the array have no space left. We reached the roof of our capacity, and because of that, if we want to store more values in this array, we need to expand it. We need to get a bigger space that can hold more values that we currently have.\nA dynamic array works by expanding the underlying array, whenever the length becomes equal to the capacity of the array. It basically allocates a new contiguos block of memory that is bigger than the previous one, then, it copies all values that are currently being stored to this new location (i.e. this new block of memory), then, it frees the previous block of memory. At the end of this process, the new underlying array have a bigger capacity, and, therefore, the length becomes once again smaller than the capacity of the array.\nThis is the cycle of an dynamic array. Notice that, throughout this cycle, the capacity is always either equal to or higher than the length of the array. If youh have an ArrayList object, let’s suppose you named it of buffer, you can check the current capacity of your array by accessing the capacity attribute of your ArrayList object, while the current length of it is available through the items.len attribute of your ArrayList object.\n\n// Check capacity\nbuffer.capacity;\n// Check length\nbuffer.items.len;\n\n\n\n11.1.2 Creating an ArrayList object\nIn order to use ArrayList, you must provide an allocator object to it. Remember, Zig does not have a default memory allocator. And as I described at Section 3.2, all memory allocations must be done by allocator objects that you define, that you have control over. In our example here, I’m going to use a general purpose allocator, but you can use any other allocator of your preference.\nWhen you initialize an ArrayList object, you must provide the data type of the elements of the array. In other words, this defines the type of data that this array (or container) will store. Therefore, if I provide the u8 type to it, then, I will create a dynamic array of u8 values. However, if I provide a struct that I defined instead, like the struct User from Section 2.3, then, a dynamic array of User values will be created. In the example below, with the expression ArrayList(u8) we are creating a dynamic array of u8 values.\nAfter you provide the data type of the elements of the array, you can initialize an ArrayList object by either using the init() or the initCapacity() method. The former method receives only the allocator object as input, while the latter method receives both the allocator object and a capacity number as inputs. With the latter method, you not only initialize the struct, but you also set the starting capacity of the allocated array.\nUsing the initCapacity() method is the preferred way to initialize your dynamic array. Because reallocations, or, in other words, the process of expanding the capacity of the array, is always a high cost operation. You should take any possible opportunity to avoid reallocations in your array. If you know how much space your array needs to occupy at the beginning, you should always use initCapacity() to create your dynamic array.\n\nvar gpa = std.heap.GeneralPurposeAllocator(.{}){};\nconst allocator = gpa.allocator();\nvar buffer = try std.ArrayList(u8)\n .initCapacity(allocator, 100);\ndefer buffer.deinit();\n\nIn the example above, the buffer object starts as an array of 100 elements. If this buffer object needs to create more space to accomodate more elements during the runtime of your program, the ArrayList internals will perform the necessary actions for you automatically. Also notice the deinit() method being used to destroy the buffer object at the end of the current scope, by freeing all the memory that was allocated for the dynamic array stored in this buffer object.\n\n\n11.1.3 Adding new elements to the array\nNow that we created our dynamic array, we can start to use it. You can append (a.k.a “add”) new values to this array by using the append() method. This method works the same way as the append() method from a Python list, or, the emplace_back() method from std::vector of C++. You provide a single value to this method, and the method appends this value to the array.\nYou can also use the appendSlice() method to append multiple values at once. You provide a slice (slices were described at Section 1.6) to this method, and the method adds all values present in this slice to your dynamic array.\n\ntry buffer.append('H');\ntry buffer.append('e');\ntry buffer.append('l');\ntry buffer.append('l');\ntry buffer.append('o');\ntry buffer.appendSlice(\" World!\");\n\n\n\n11.1.4 Removing elements from the array\nYou can use the pop() method to “pop” or remove the last element in the array. Is worth noting that this method do not change the capacity of the array. It just deletes or erases the last value stored in the array.\nAlso, this method returns as result the value that got deleted. That is, you can use this method to both get the last value in the array, and also, remove it from the array. It is a “get and remove value” type of method.\n\nconst exclamation_mark = buffer.pop();\n\nNow, if you want to remove specific elements from specific positions of your array, you can use the orderedRemove() method from your ArrayList object. With this method, you can provide an index as input, then, the method will delete the value that is at this index in the array. This effectively reduces the length of the array everytime you execute an orderedRemove() operation.\nIn the example below, we first create an ArrayList object, and we fill it with numbers. Then, we use orderedRemove() to remove the value at index 3 in the array, two consecutive times.\nAlso, notice that we are assigning the result of orderedRemove() to the underscore character. So we are discarding the result value of this method. As the result value, the orderedRemove() method returns the value that got deleted, in a similar style to the pop() method.\n\nvar gpa = std.heap.GeneralPurposeAllocator(.{}){};\nconst allocator = gpa.allocator();\nvar buffer = try std.ArrayList(u8)\n .initCapacity(allocator, 100);\ndefer buffer.deinit();\n\nfor (0..10) |i| {\n const index: u8 = @intCast(i);\n try buffer.append(index);\n}\n\nstd.debug.print(\n \"{any}\\n\", .{buffer.items}\n);\n_ = buffer.orderedRemove(3);\n_ = buffer.orderedRemove(3);\n\nstd.debug.print(\n \"{any}\\n\", .{buffer.items}\n);\nstd.debug.print(\n \"{any}\\n\", .{buffer.items.len}\n);\n\n{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }\n{ 0, 1, 2, 5, 6, 7, 8, 9 }\n8\nOne key characteristic about orderedRemove() is that it preserves the order of the values in the array. So, it deletes the value that you asked it to remove, but it also makes sure that the order of the values that remain in the array stay the same as before.\nNow, if you don’t care about the order of the values, for example, maybe you want to treat your dynamic array as a set of values, like the std::unordered_set structure from C++, you can use the swapRemove() method instead. This method works similarly to the orderedRemove() method. You give an index to this method, then, it deletes the value that is at this index in the array. But this method does not preserve the original order of the values that remain in the array. As a result, swapRemove() is, in general, faster than orderedRemove().\n\n\n11.1.5 Inserting elements at specific indexes\nWhen you need to insert values in the middle of your array, instead of just appending them to the end of the array, you need to use the insert() and insertSlice() methods, instead of the append() and appendSlice() methods.\nThese two methods work very similarly to insert() and insert_range() from the C++ vector class. You provide an index to these methods, and they insert the values that you provide at that index in the array.\n\nvar gpa = std.heap.GeneralPurposeAllocator(.{}){};\nconst allocator = gpa.allocator();\nvar buffer = try std.ArrayList(u8)\n .initCapacity(allocator, 10);\ndefer buffer.deinit();\n\ntry buffer.appendSlice(\"My Pedro\");\ntry buffer.insert(4, '3');\ntry buffer.insertSlice(2, \" name\");\nfor (buffer.items) |char| {\n try stdout.print(\"{c}\", .{char});\n}\n\nMy name P3edro\n\n\n11.1.6 Conclusion\nIf you feel the lack of some other method, I recommend you to read the official documentation for the ArrayListAligned1 struct, which describes most of the methods available through the ArrayList object.\nYou will notice that there is a lot other methods in this page that I did not described here, and I recommend you to explore these methods, and understand how they work.", "crumbs": [ "11  Data Structures" ] @@ -904,7 +914,7 @@ "href": "Chapters/10-stack-project.html#sec-generics", "title": "12  Building a stack data structure", "section": "12.2 Introducing Generics", - "text": "12.2 Introducing Generics\nFirst of all, what is a generic? Generic is the idea to allow a type (f64, u8, u32, bool, and also, user-defined types, like the User struct that we defined at Section 2.2) to be a parameter to methods, classes and interfaces (Geeks for Geeks 2024). In other words, a “generic” is a class (or a method) that can work with multiple data types.\nFor example, in Java, generics are created through the operator <>. With this operator, a Java class is capable of receiving a data type as input, and therefore, the class can fit it’s features according to this input data type. As another example, generics in C++ are supported through the concept of templates. Class templates in C++ are generics.\nIn Zig, generics are implemented through comptime. The comptime keyword allows us to collect a data type at compile time, and pass this data type as input to a piece of code.\n\n12.2.1 A generic function\nTake the max() function exposed below as a first example. This function is essentially a “generic function”. In this function, we have a comptime function argument named T. Notice that this T argument have a data type of type. Weird right? This type keyword is the “father of all types”, or, “the type of types” in Zig. Because we used this type keyword in the T argument, we are telling the zig compiler that this T argument will receive some data type as input.\nAlso notice the use of the comptime keyword in this argument. As I described at Section 12.1, every time you use this keyword in a function argument, this means that the value of this argument must be known at compile-time. This makes sense, right? Because there is no data type that you can make that is known only at runtime.\nThink about this. Every data type that you will ever write is always known at compile-time. Specially because data types are an essential information for the compiler to actually compile your source code. Having this in mind, makes sense to mark this argument as a comptime argument.\n\nfn max(comptime T: type, a: T, b: T) T {\n return if (a > b) a else b;\n}\n\nAlso notice that the value of the T argument is actually used to define the data type of the other arguments of the function, a and b, and also at the return type annotation of the function. That is, the data type of these arguments (a and b), and, the return data type of the function itself, are determined by the input value given to the T argument.\nAs a result, we have a generic function that works with different data types. For example, I can provide u8 values to this max() function, and it will work as expected. But if I provide f64 values instead, it will also work as expected. If I did not use a generic function, I would have to write a max() for each one of the data types that I wanted to use. This generic function provides a very useful shortcut for us.\n\nconst std = @import(\"std\");\nfn max(comptime T: type, a: T, b: T) T {\n return if (a > b) a else b;\n}\ntest \"test max\" {\n const n1 = max(u8, 4, 10);\n std.debug.print(\"Max n1: {d}\\n\", .{n1});\n const n2 = max(f64, 89.24, 64.001);\n std.debug.print(\"Max n2: {d}\\n\", .{n2});\n}\n\nMax n1: 10\nMax n2: 89.24\n\n\n12.2.2 A generic data structure\nEvery data structure that you find in the Zig Standard Library (e.g. ArrayList, HashMap, etc.) is essentially a generic data structure. These data structures are generic in the sense that they work with any data type you want. You just say which is the data type of the values that are going to be stored in this data structure, and they just work as expected.\nA generic data structure in Zig is the way to replicate a generic class from Java, or, a class template from C++. But you may quest yourself: how do we build a generic data structure in Zig?\nThe basic idea is to write a generic function that creates the data structure definition for the specific type we want. In other words, this generic function behaves as a “constructor”. The function outputs the struct definition that defines this data structure for a specific data type.\nTo create such function, we need to add a comptime argument to this function that receives a data type as input. We already learned how to do this at the previous section (Section 12.2.1).\nI think the best way to demonstrate how to create a generic data structure is to actually write one. This where we go to our next small project in this book. This one is a very small project, which is to write a stack data structure.", + "text": "12.2 Introducing Generics\nFirst of all, what is a generic? Generic is the idea to allow a type (f64, u8, u32, bool, and also, user-defined types, like the User struct that we defined at Section 2.3) to be a parameter to methods, classes and interfaces (Geeks for Geeks 2024). In other words, a “generic” is a class (or a method) that can work with multiple data types.\nFor example, in Java, generics are created through the operator <>. With this operator, a Java class is capable of receiving a data type as input, and therefore, the class can fit it’s features according to this input data type. As another example, generics in C++ are supported through the concept of templates. Class templates in C++ are generics.\nIn Zig, generics are implemented through comptime. The comptime keyword allows us to collect a data type at compile time, and pass this data type as input to a piece of code.\n\n12.2.1 A generic function\nTake the max() function exposed below as a first example. This function is essentially a “generic function”. In this function, we have a comptime function argument named T. Notice that this T argument have a data type of type. Weird right? This type keyword is the “father of all types”, or, “the type of types” in Zig. Because we used this type keyword in the T argument, we are telling the zig compiler that this T argument will receive some data type as input.\nAlso notice the use of the comptime keyword in this argument. As I described at Section 12.1, every time you use this keyword in a function argument, this means that the value of this argument must be known at compile-time. This makes sense, right? Because there is no data type that you can make that is known only at runtime.\nThink about this. Every data type that you will ever write is always known at compile-time. Specially because data types are an essential information for the compiler to actually compile your source code. Having this in mind, makes sense to mark this argument as a comptime argument.\n\nfn max(comptime T: type, a: T, b: T) T {\n return if (a > b) a else b;\n}\n\nAlso notice that the value of the T argument is actually used to define the data type of the other arguments of the function, a and b, and also at the return type annotation of the function. That is, the data type of these arguments (a and b), and, the return data type of the function itself, are determined by the input value given to the T argument.\nAs a result, we have a generic function that works with different data types. For example, I can provide u8 values to this max() function, and it will work as expected. But if I provide f64 values instead, it will also work as expected. If I did not use a generic function, I would have to write a max() for each one of the data types that I wanted to use. This generic function provides a very useful shortcut for us.\n\nconst std = @import(\"std\");\nfn max(comptime T: type, a: T, b: T) T {\n return if (a > b) a else b;\n}\ntest \"test max\" {\n const n1 = max(u8, 4, 10);\n std.debug.print(\"Max n1: {d}\\n\", .{n1});\n const n2 = max(f64, 89.24, 64.001);\n std.debug.print(\"Max n2: {d}\\n\", .{n2});\n}\n\nMax n1: 10\nMax n2: 89.24\n\n\n12.2.2 A generic data structure\nEvery data structure that you find in the Zig Standard Library (e.g. ArrayList, HashMap, etc.) is essentially a generic data structure. These data structures are generic in the sense that they work with any data type you want. You just say which is the data type of the values that are going to be stored in this data structure, and they just work as expected.\nA generic data structure in Zig is the way to replicate a generic class from Java, or, a class template from C++. But you may quest yourself: how do we build a generic data structure in Zig?\nThe basic idea is to write a generic function that creates the data structure definition for the specific type we want. In other words, this generic function behaves as a “constructor”. The function outputs the struct definition that defines this data structure for a specific data type.\nTo create such function, we need to add a comptime argument to this function that receives a data type as input. We already learned how to do this at the previous section (Section 12.2.1).\nI think the best way to demonstrate how to create a generic data structure is to actually write one. This where we go to our next small project in this book. This one is a very small project, which is to write a stack data structure.", "crumbs": [ "12  Building a stack data structure" ] From 128510ba3cc991c25dc596c7f4cbc0648c524509 Mon Sep 17 00:00:00 2001 From: pedropark99 Date: Sun, 11 Aug 2024 22:58:20 -0300 Subject: [PATCH 2/3] Add content --- .../03-structs/execute-results/html.json | 4 +- docs/Chapters/03-structs.html | 274 +++++++++--------- docs/search.json | 2 +- 3 files changed, 142 insertions(+), 138 deletions(-) diff --git a/_freeze/Chapters/03-structs/execute-results/html.json b/_freeze/Chapters/03-structs/execute-results/html.json index 98912c7..9075b22 100644 --- a/_freeze/Chapters/03-structs/execute-results/html.json +++ b/_freeze/Chapters/03-structs/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "f4ef3a169c3185b855e4efe30ac406eb", + "hash": "6c0925fa78e94905685fb609f42d3c84", "result": { "engine": "knitr", - "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n# Structs, Modules and Control Flow\n\nI introduced a lot of the Zig's syntax to you in the last chapter,\nspecially at @sec-root-file and @sec-main-file.\nBut we still need to discuss about some other very important\nelements of the language that you will use constantly on your day-to-day\nroutine.\n\nWe begin this chapter by talking about the different keywords and structures\nin Zig related to control flow (e.g. loops and if statements).\nThen, we talk about structs and how they can be used to do some\nbasic Object-Oriented (OOP) patterns in Zig. We also talk about\ntype inference, which help us to write less code and achieve the same results.\nFinally, we end this chapter by discussing modules, and how they relate\nto structs.\n\n\n\n## Control flow {#sec-zig-control-flow}\n\nSometimes, you need to make decisions in your program. Maybe you need to decide\nwether to execute or not a specific piece of code. Or maybe,\nyou need to apply the same operation over a sequence of values. These kinds of tasks,\ninvolve using structures that are capable of changing the \"control flow\" of our program.\n\nIn computer science, the term \"control flow\" usually refers to the order in which expressions (or commands)\nare evaluated in a given language or program. But this term is also used to refer\nto structures that are capable of changing this \"evaluation order\" of the commands\nexecuted by a given language/program.\n\nThese structures are better known\nby a set of terms, such as: loops, if/else statements, switch statements, among others. So,\nloops and if/else statements are examples of structures that can change the \"control\nflow\" of our program. The keywords `continue` and `break` are also examples of symbols\nthat can change the order of evaluation, since they can move our program to the next iteration\nof a loop, or make the loop stop completely.\n\n\n### If/else statements\n\nAn if/else statement performs an \"conditional flow operation\".\nA conditional flow control (or choice control) allows you to execute\nor ignore a certain block of commands based on a logical condition.\nMany programmers and computer science professionals also use\nthe term \"branching\" in this case.\nIn essence, we use if/else statements to use the result of a logical test\nto decide whether or not to execute a given block of commands.\n\nIn Zig, we write if/else statements by using the keywords `if` and `else`.\nWe start with the `if` keyword followed by a logical test inside a pair\nof parentheses, and then, a pair of curly braces with contains the lines\nof code to be executed in case the logical test returns the value `true`.\n\nAfter that, you can optionally add an `else` statement. Just add the `else`\nkeyword followed by a pair of curly braces, with the lines of code\nto executed in case the logical test defined in the `if`\nreturns `false`.\n\nIn the example below, we are testing if the object `x` contains a number\nthat is greater than 10. Judging by the output printed to the console,\nwe know that this logical test returned `false`. Because the output\nin the console is compatible with the line of code present in the\n`else` branch of the if/else statement.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst x = 5;\nif (x > 10) {\n try stdout.print(\n \"x > 10!\\n\", .{}\n );\n} else {\n try stdout.print(\n \"x <= 10!\\n\", .{}\n );\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nx <= 10!\n```\n\n\n:::\n:::\n\n\n\n\n### Swith statements {#sec-switch}\n\nSwitch statements are also available in Zig.\nA switch statement in Zig have a similar syntax to a switch statement in Rust.\nAs you would expect, to write a switch statement in Zig we use the `switch` keyword.\nWe provide the value that we want to \"switch over\" inside a\npair of parentheses. Then, we list the possible combinations (or \"branchs\")\ninside a pair of curly braces.\n\nLet's take a look at the code example below. You can see in this example that,\nI'm creating an enum type called `Role`. We talk more about enums at @sec-enum.\nBut in essence, this `Role` type is listing different types of roles in a fictitious\ncompany, like `SE` for Software Engineer, `DE` for Data Engineer, `PM` for Product Manager,\netc.\n\nNotice that we are using the value from the `role` object in the\nswitch statement, to discover which exact area we need to store in the `area` variable object.\nAlso notice that we are using type inference inside the switch statement, with the dot character,\nas we described at @sec-type-inference.\nThis makes the `zig` compiler infer the correct data type of the values (`PM`, `SE`, etc.) for us.\n\nAlso notice that, we are grouping multiple values in the same branch of the switch statement.\nWe just separate each possible value with a comma. So, for example, if `role` contains either `DE` or `DA`,\nthe `area` variable would contain the value `\"Data & Analytics\"`, instead of `\"Platform\"`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst Role = enum {\n SE, DPE, DE, DA, PM, PO, KS\n};\n\npub fn main() !void {\n var area: []const u8 = undefined;\n const role = Role.SE;\n switch (role) {\n .PM, .SE, .DPE, .PO => {\n area = \"Platform\";\n },\n .DE, .DA => {\n area = \"Data & Analytics\";\n },\n .KS => {\n area = \"Sales\";\n },\n }\n try stdout.print(\"{s}\\n\", .{area});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nPlatform\n```\n\n\n:::\n:::\n\n\nNow, one very important aspect about this switch statement presented\nin the code example above, is that it exhaust all existing possibilities.\nIn other words, all possible values that could be found inside the `order`\nobject are explicitly handled in this switch statement.\n\nSince the `role` object have type `Role`, the only possible values to\nbe found inside this object are `PM`, `SE`, `DPE`, `PO`, `DE`, `DA` and `KS`.\nThere is no other possible value to be stored in this `role` object.\nThis what \"exhaust all existing possibilities\" means. The switch statement covers\nevery possible case.\n\nIn Zig, switch statements must exhaust all existing possibilities. You cannot write\na switch statement, and leave an edge case with no expliciting action to be taken.\nThis is a similar behaviour to switch statements in Rust, which also have to\nhandle all possible cases.\n\nTake a look at the `dump_hex_fallible()` function below as an example. This function\nalso comes from the Zig Standard Library, but this time, it comes from the [`debug.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/debug.zig)[^debug-mod].\nThere are multiple lines in this function, but I omitted them to focus solely on the\nswitch statement found in this function. Notice that this switch statement have four\npossible cases, or four explicit branches. Also, notice that we used an `else` branch\nin this case. Whenever you have multiple possible cases in your switch statement\nwhich you want to apply the same exact action, you can use an `else` branch to do that.\n\n[^debug-mod]: \n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub fn dump_hex_fallible(bytes: []const u8) !void {\n // Many lines ...\n switch (byte) {\n '\\n' => try writer.writeAll(\"␊\"),\n '\\r' => try writer.writeAll(\"␍\"),\n '\\t' => try writer.writeAll(\"␉\"),\n else => try writer.writeByte('.'),\n }\n}\n```\n:::\n\n\nMany users would also use an `else` branch to handle a \"not supported\" case.\nThat is, a case that cannot be properly handled by your code, or, just a case that\nshould not be \"fixed\". So many programmers use an `else` branch to panic (or raise an error) to stop\nthe current execution.\n\nTake the code example below as an example. We can see that, we are handling the cases\nfor the `level` object being either 1, 2, or 3. All other possible cases are not supported by default,\nand, as consequence, we raise an runtime error in these cases, through the `@panic()` built-in function.\n\nAlso notice that, we are assigning the result of the switch statement to a new object called `category`.\nThis is another thing that you can do with switch statements in Zig. If the branchs in this switch\nstatement output some value as result, you can store the result value of the switch statement into\na new object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 1, 2 => \"beginner\",\n 3 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n:::\n\n\n```\nthread 13103 panic: Not supported level!\nt.zig:9:13: 0x1033c58 in main (switch2)\n @panic(\"Not supported level!\");\n ^\n```\n\nFurthermore, you can also use ranges of values in switch statements.\nThat is, you can create a branch in your switch statement that is used\nwhenever the input value is contained in a range. These range\nexpressions are created with the operator `...`. Is important\nto emphasize that the ranges created by this operator are\ninclusive on both ends.\n\nFor example, I could easily change the code example above to support all\nlevels between 0 and 100. Like this:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 0...25 => \"beginner\",\n 26...75 => \"intermediary\",\n 76...100 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nbeginner\n```\n\n\n:::\n:::\n\n\nThis is neat, and it works with character ranges too. That is, I could\nsimply write `'a'...'z'`, to match any character value that is a\nlowercase letter, and it would work fine.\n\n\n\n### The `defer` keyword {#sec-defer}\n\nWith the `defer` keyword you can execute expressions at the end of the current scope.\nTake the `foo()` function below as an example. When we execute this function, the expression\nthat prints the message \"Exiting function ...\" get's executed only at\nthe end of the function scope.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nfn foo() !void {\n defer std.debug.print(\n \"Exiting function ...\\n\", .{}\n );\n try stdout.print(\"Adding some numbers ...\\n\", .{});\n const x = 2 + 2; _ = x;\n try stdout.print(\"Multiplying ...\\n\", .{});\n const y = 2 * 8; _ = y;\n}\n\npub fn main() !void {\n try foo();\n}\n```\n:::\n\n\n```\nAdding some numbers ...\nMultiplying ...\nExiting function ...\n```\n\nIt doesn't matter how the function exits (i.e. because\nof an error, or, because of an return statement, or whatever),\njust remember, this expression get's executed when the function exits.\n\n\n\n\n### For loops\n\nA loop allows you to execute the same lines of code multiple times,\nthus, creating a \"repetition space\" in the execution flow of your program.\nLoops are particularly useful when we want to replicate the same function\n(or the same set of commands) over several different inputs.\n\nThere are different types of loops available in Zig. But the most\nessential of them all is probably the *for loop*. A for loop is\nused to apply the same piece of code over the elements of a slice or an array.\n\nFor loops in Zig have a slightly different syntax that you are\nprobably used to see in other languages. You start with the `for` keyword, then, you\nlist the items that you want to iterate\nover inside a pair of parentheses. Then, inside of a pair of pipes (`|`)\nyou should declare an identifier that will serve as your iterator, or,\nthe \"repetition index of the loop\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (items) |value| {\n // code to execute\n}\n```\n:::\n\n\nInstead of using a `(value in items)` syntax,\nin Zig, for loops use the syntax `(items) |value|`. In the example\nbelow, you can see that we are looping through the items\nof the array stored at the object `name`, and printing to the\nconsole the decimal representation of each character in this array.\n\nIf we wanted, we could also iterate through a slice (or a portion) of\nthe array, instead of iterating through the entire array stored in the `name` object.\nJust use a range selector to select the section you want. For example,\nI could provide the expression `name[0..3]` to the for loop, to iterate\njust through the first 3 elements in the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst name = [_]u8{'P','e','d','r','o'};\nfor (name) |char| {\n try stdout.print(\"{d} | \", .{char});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n80 | 101 | 100 | 114 | 111 | \n```\n\n\n:::\n:::\n\n\nIn the above example we are using the value itself of each\nelement in the array as our iterator. But there are many situations where\nwe need to use an index instead of the actual values of the items.\n\nYou can do that by providing a second set of items to iterate over.\nMore precisely, you provide the range selector `0..` to the for loop. So,\nyes, you can use two different iterators at the same time in a for\nloop in Zig.\n\nBut remember from @sec-assignments that, every object\nyou create in Zig must be used in some way. So if you declare two iterators\nin your for loop, you must use both iterators inside the for loop body.\nBut if you want to use just the index iterator, and not use the \"value iterator\",\nthen, you can discard the value iterator by maching the\nvalue items to the underscore character, like in the example below:\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (name, 0..) |_, i| {\n try stdout.print(\"{d} | \", .{i});\n}\n```\n:::\n\n\n```\n0 | 1 | 2 | 3 | 4 |\n```\n\n\n### While loops\n\nA while loop is created from the `while` keyword. A `for` loop\niterates through the items of an array, but a `while` loop\nwill loop continuously, and infinitely, until a logical test\n(specified by you) becomes false.\n\nYou start with the `while` keyword, then, you define a logical\nexpression inside a pair of parentheses, and the body of the\nloop is provided inside a pair of curly braces, like in the example below:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: u8 = 1;\nwhile (i < 5) {\n try stdout.print(\"{d} | \", .{i});\n i += 1;\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 2 | 3 | 4 | \n```\n\n\n:::\n:::\n\n\n\n\n### Using `break` and `continue`\n\nIn Zig, you can explicitly stop the execution of a loop, or, jump to the next iteration of the loop, using\nthe keywords `break` and `continue`, respectively. The `while` loop present in the example below, is\nat first sight, an infinite loop. Because the logical value inside the parenthese will always be equal to `true`.\nWhat makes this `while` loop stop when the `i` object reaches the count\n10? Is the `break` keyword!\n\nInside the while loop, we have an if statement that is constantly checking if the `i` variable\nis equal to 10. Since we are increasing the value of this `i` variable at each iteration of the\nwhile loop. At some point, this `i` variable will be equal to 10, and when it does, the if statement\nwill execute the `break` expression, and, as a result, the execution of the while loop is stopped.\n\nNotice the `expect()` function from the Zig standard library after the while loop.\nThis `expect()` function is an \"assert\" type of function.\nThis function checks if the logical test provided is equal to true. If this logical test is false,\nthe function raises an assertion error. But it is equal to true, then, the function will do nothing.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: usize = 0;\nwhile (true) {\n if (i == 10) {\n break;\n }\n i += 1;\n}\ntry std.testing.expect(i == 10);\ntry stdout.print(\"Everything worked!\", .{});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nEverything worked!\n```\n\n\n:::\n:::\n\n\nSince this code example was executed succesfully by the `zig` compiler,\nwithout raising any errors, then, we known that, after the execution of while loop,\nthe `i` variable is equal to 10. Because if it wasn't equal to 10, then, an error would\nbe raised by `expect()`.\n\nNow, in the next example, we have an use case for\nthe `continue` keyword. The if statement is constantly\nchecking if the current index is a multiple of 2. If\nit is, then we jump to the next iteration of the loop\ndirectly. But it the current index is not a multiple of 2,\nthen, the loop will simply print this index to the console.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [_]u8{1,2,3,4,5,6};\nfor (ns) |i| {\n if ((i % 2) == 0) {\n continue;\n }\n try stdout.print(\"{d} | \", .{i});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 3 | 5 | \n```\n\n\n:::\n:::\n\n\n\n\n## Function parameters are immutable {#sec-fun-pars}\n\nWe have already discussed a lot of the syntax behind function declarations at @sec-root-file and @sec-main-file.\nBut I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig.\nIn summary, function parameters are immutable in Zig.\n\nTake the code example below, where we declare a simple function that just tries to add\nsome amount to the input integer, and returns the result back. But if you look closely\nat the body of this `add2()` function, you will notice that we try\nto save the result back into the `x` function argument.\n\nIn other words, this function not only use the value that it received through the function argument\n`x`, but it also tries to change the value of this function argument, by assigning the addition result\ninto `x`. However, function arguments in Zig are immutable. You cannot change their values, or, you\ncannot assign values to them inside the body's function.\n\nThis is the reason why, the code example below do not compile successfully. If you try to compile\nthis code example, you get a compile error warning you that you are trying to change the value of a\nimmutable (i.e. constant) object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: u32) u32 {\n x = x + 2;\n return x;\n}\n\npub fn main() !void {\n const y = add2(4);\n std.debug.print(\"{d}\\n\", .{y});\n}\n```\n:::\n\n\n```\nt.zig:3:5: error: cannot assign to constant\n x = x + 2;\n ^\n```\n\n\nIf a function argument receives as input a object whose data type is\nany of the primitive types that we have listed at @sec-primitive-data-types\nthis object is always passed by value to the function. In other words, this object\nis copied to the function stack frame.\n\nHowever, if the input object have a more complex data type, for example, it might\nbe a struct instance, or an array, or a union, etc., in cases like that, the `zig` compiler\nwill take the liberty of deciding for you which strategy is best. The `zig` compiler will\npass your object to the function either by value, or by reference. The compiler will always\nchoose the strategy that is faster for you.\nThis optimization that you get for free is possible only because function arguments are\nimmutable in Zig.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: *u32) void {\n x = x + 2;\n}\n\npub fn main() !void {\n var x: u32 = @intCast(4);\n add2(&x);\n std.debug.print(\"{d}\\n\", .{x});\n}\n```\n:::\n\n\n\n\n## Structs and OOP {#sec-structs-and-oop}\n\nZig is a language more closely related to C (which is a procedural language),\nthan it is to C++ or Java (which are object-oriented languages). Because of that, you do not\nhave advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or\nclass inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\n\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C.\nYou give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can\nalso register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object\nthat you create with this new type, will always have these methods available and associated with them.\n\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object\nof this particular class, and you also have a destructor method (or a destructor function) that\nis the function responsible for destroying every object of this class.\n\nIn Zig, we normally declare the constructor and the destructor methods\nof our structs, by declaring an `init()` and a `deinit()` methods inside the struct.\nThis is just a naming convention that you will find across the entire Zig standard library.\nSo, in Zig, the `init()` method of a struct is normally the constructor method of the class represented by this struct.\nWhile the `deinit()` method is the method used for destroying an existing instance of that struct.\n\nThe `init()` and `deinit()` methods are both used extensively in Zig code, and you will see both of\nthem being used when we talk about allocators at @sec-allocators.\nBut, as another example, let's build a simple `User` struct to represent an user of some sort of system.\nIf you look at the `User` struct below, you can see the `struct` keyword, and inside of a\npair of curly braces, we write the struct's body.\n\nNotice the data members of this struct, `id`, `name` and `email`. Every data member have it's\ntype explicitly annotated, with the colon character (`:`) syntax that we described earlier at @sec-root-file.\nBut also notice that every line in the struct body that describes a data member, ends with a comma character (`,`).\nSo every time you declare a data member in your Zig code, always end the line with a comma character, instead\nof ending it with the traditional semicolon character (`;`).\n\nNext, also notice in this example, that we registrated an `init()` function as a method\nof this `User` struct. This `init()` method is the constructor method that you use to instantiate\nevery new `User` object. That is why this `init()` function return an `User` object as result.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\npedro\n```\n\n\n:::\n:::\n\n\nThe `pub` keyword plays an important role in struct declarations, and OOP in Zig.\nEvery method that you declare in your struct that is marked with the keyword `pub`,\nbecomes a public method of this particular struct.\n\nSo every method that you create in your struct, is, at first, a private method\nof that struct. Meaning that, this method can only be called from within this\nstruct. But, if you mark this method as public, with the keyword `pub`, then,\nyou can call the method directly from the `User` object you have\nin your code.\n\nIn other words, the functions marked by the keyword `pub`\nare members of the public API of that struct.\nFor example, if I did not marked the `print_name()` method as public,\nthen, I could not execute the line `u.print_name()`. Because I would\nnot be authorized to call this method directly in my code.\n\n\n\n### Anonymous struct literals {#sec-anonymous-struct-literals}\n\nYou can declare a struct object as a literal value. When we do that, we normally specify the\ndata type of this struct literal by writing it's data type just before the opening curly braces.\nFor example, I could write a struct literal of type `User` that we defined in the previous section like\nthis:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n```\n:::\n\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a\nstruct literal, but not especify explicitly the type of this particular struct.\nAn anonymous struct is written by using the syntax `.{}`. So, we essentially\nreplaced the explicit type of the struct literal with a dot character (`.`).\n\nAs we described at @sec-type-inference, when you put a dot before a struct literal,\nthe type of this struct literal is automatically inferred by the `zig` compiler.\nIn essence, the `zig` compiler will look for some hint of what is the type of that struct.\nIt can be the type annotation of an function argument,\nor the return type annotation of the function that you are using, or the type annotation\nof a variable.\nIf the compiler do find such type annotation, then, it will use this\ntype in your literal struct. \n\nAnonymous structs are very commom to use in function arguments in Zig.\nOne example that you have seen already constantly, is the `print()`\nfunction from the `stdout` object.\nThis function takes two arguments.\nThe first argument, is a template string, which should\ncontain string format specifiers in it, which tells how the values provided\nin the second argument should be printed into the message.\n\nWhile the second argument is a struct literal that lists the values\nto be printed into the template message specified in the first argument.\nYou normally want to use an anonymous struct literal here, so that, the\n`zig` compiler do the job of specifying the type of this particular\nanonymous struct for you.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHello, world!\n```\n\n\n:::\n:::\n\n\n\n\n### Struct declarations must be constant\n\nTypes in Zig must be `const` or `comptime` (we are going to talk more about comptime at @sec-comptime).\nWhat this means is that you cannot create a new data type, and mark it as variable with the `var` keyword.\nSo struct declarations are always constant. You cannot declare a new struct using the `var` keyword.\nIt must be `const`.\n\nIn the `Vec3` example below, this declaration is allowed because I'm using the `const` keyword\nto declare this new data type.\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n```\n:::\n\n\n\n### The `self` method argument {#sec-self-arg}\n\nIn every language that have OOP, when we declare a method of some class or struct, we\nusually declare this method as a function that have a `self` argument.\nThis `self` argument is the reference to the object itself from which the method\nis being called from.\n\nIs not mandatory to use this `self` argument. But why would you not use this `self` argument?\nThere is no reason to not use it. Because the only way to get access to the data stored in the\ndata members of your struct is to access them through this `self` argument.\nIf you don't need to use the data in the data members of your struct inside your method, then, you very likely don't need\na method, you can just simply declare this logic as a simple function, outside of your\nstruct declaration.\n\n\nTake the `Vec3` struct below. Inside this `Vec3` struct we declared a method named `distance()`.\nThis method calculates the distance between two `Vec3` objects, by following the distance\nformula in euclidean space. Notice that this `distance()` method takes two `Vec3` objects\nas input, `self` and `other`.\n\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n```\n:::\n\n\n\nThe `self` argument corresponds to the `Vec3` object from which this `distance()` method\nis being called from. While the `other` is a separate `Vec3` object that is given as input\nto this method. In the example below, the `self` argument corresponds to the object\n`v1`, because the `distance()` method is being called from the `v1` object,\nwhile the `other` argument corresponds to the object `v2`.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n```\n:::\n\n\n```\nDistance: 3.3970575502926055\n```\n\n\n\n### About the struct state\n\nSometimes you don't need to care about the state of your struct object. Sometimes, you just need\nto instantiate and use the objects, without altering their state. You can notice that when you have methods\ninside your struct declaration that might use the values that are present in the data members, but they\ndo not alter the values in the data members of the struct in anyway.\n\nThe `Vec3` struct that was presented at @sec-self-arg is an example of that.\nThis struct have a single method named `distance()`, and this method do use the values\npresent in all three data members of the struct (`x`, `y` and `z`). But at the same time,\nthis method do not change the values of these data members in any point.\n\nAs a result of that, when we create `Vec3` objects we usually create them as\nconstant objects, like the `v1` and `v2` objects presented at @sec-self-arg.\nWe can create them as variable objects with the `var` keyword,\nif we want to. But because the methods of this `Vec3` struct do not change\nthe state of the objects in any point, is unnecessary to mark them\nas variable objects.\n\nBut why? Why am I talkin about this here? Is because the `self` argument\nin the methods is affected depending on whether the\nmethods present in a struct change or not the state of the object itself.\nMore specifically, when you have a method in a struct that changes the state\nof the object (i.e. change the value of a data member), the `self` argument\nin this method must be annotated in a different manner.\n\nAs I described at @sec-self-arg, the `self` argument in methods of\na struct is the argument that receives as input the object from which the method\nwas called from. We usually annotate this argument in the methods by writing `self`,\nfollowed by the colon character (`:`), and the data type of the struct to which\nthe method belongs to (e.g. `User`, `Vec3`, etc.).\n\nIf we take the `Vec3` struct that we defined in the previous section as an example,\nwe can see in the `distance()` method that this `self` argument is annotated as\n`self: Vec3`. Because the state of the `Vec3` object is never altered by this\nmethod.\n\nBut what if we do have a method that alters the state of the object, by altering the\nvalues of it's data members. How should we annotate `self` in this instance? The answer is:\n\"we should annotate `self` as a pointer of `x`, instead of just `x`\".\nIn other words, you should annotate `self` as `self: *x`, instead of annotating it\nas `self: x`.\n\nIf we create a new method inside the `Vec3` object that, for example, expands the\nvector by multiplying it's coordinates by a factor o two, then, we need to follow\nthis rule specified in the previous paragraph. The code example below demonstrates\nthis idea:\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n```\n:::\n\n\nNotice in the code example above that we have added a new method\nto our `Vec3` struct named `double()`. This method essentially doubles the\ncoordinate values of our vector object. Also notice that, in the\ncase of the `double()` method, we annotated the `self` argument as `*Vec3`,\nindicating that this argument receives a pointer (or a reference, if you prefer to call it this way)\nto a `Vec3` object as input.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n```\n:::\n\n\n```\nDoubled: 8.4\n```\n\n\n\nNow, if you change the `self` argument in this `double()` method to `self: Vec3`, like in the\n`distance()` method, you will get the compiler error exposed below as result. Notice that this\nerror message is indicating a line from the `double()` method body,\nindicating that you cannot alter the value of the `x` data member.\n\n```zig\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\n```\n\nThis error message indicates that the `x` data member belongs to a constant object,\nand, because of that, it cannot be changed. Ultimately, this error message\nis telling us that the `self` argument is constant.\n\n```\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\n```\n\nIf you take some time, and think hard about this error message, you will understand it.\nYou already have the tools to understand why we are getting this error message.\nWe have talked about it already at @sec-fun-pars.\n\nSo remember, every function argument is immutable in Zig.\nIt does not matter if we marked the `v3` object as a variable object.\nBecause we are trying to alter `self` directly, which is a function argument,\nand, every function argument is immutable by default.\nWe overcome this barrier, by explicitly marking the `self` argument as a pointer.\n\n\n::: {.callout-note}\nIf a method of your `x` struct alters the state of the object, by\nchanging the value of any data member, then, remember to use `self: *x`,\ninstead of `self: x` in the function signature of this method.\n:::\n\n\nYou could also interpret the content discussed in this section as:\n\"if you need to alter the state of your `x` struct object in one of it's methods,\nyou must explicitly pass the `x` struct object by reference to the `self` argument of this method\".\n\n\n\n## Type inference {#sec-type-inference}\n\nZig is kind of a strongly typed language. I say \"kind of\" because there are situations\nwhere you don't have to explicitly write the type of every single object in your source code,\nas you would expect from a traditional strongly typed language, such as C and C++.\n\nIn some situations, the `zig` compiler can use type inference to solves the data types for you, easing some of\nthe burden that you carry as a developer.\nThe most commom way this happens is through function arguments that receives struct objects\nas input.\n\nIn general, type inference in Zig is done by using the dot character (`.`).\nEverytime you see a dot character written before a struct literal, or before a enum value, or something like that,\nyou know that this dot character is playing a special party in this place. More specifically, it is\ntelling the `zig` compiler something on the lines of: \"Hey! Can you infer the type of this\nvalue for me? Please!\". In other words, this dot character is playing a role similar to the `auto` keyword in C++.\n\nI give you some examples of this at @sec-anonymous-struct-literals, where we present anonymous struct literals.\nAnonymous struct literals are, essentially, struct literals that use type inference to\ninfer the exact type of this particular struct literal.\nThis type inference is done by looking for some minimal hint of the correct data type to be used.\nYou could say that the `zig` compiler looks for any neighbouring type annotation that might tell him\nwhat would be the correct type.\n\nAnother commom place where we use type inference in Zig is at switch statements (which we talk about at @sec-switch).\nSo I also gave some other examples of type inference at @sec-switch, where we were inferring the data types of enum values listed inside\nof switch statements (e.g. `.DE`).\nBut as another example, take a look at this `fence()` function reproduced below,\nwhich comes from the [`atomic.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/atomic.zig)[^fence-fn]\nof the Zig Standard Library.\n\n[^fence-fn]: .\n\nThere are a lot of things in this function that we haven't talked about yet, such as:\nwhat `comptime` means? `inline`? `extern`? What is this star symbol before `Self`?\nLet's just ignore all of these things, and focus solely on the switch statement\nthat is inside this function.\n\nWe can see that this switch statement uses the `order` object as input. This `order`\nobject is one of the inputs of this `fence()` function, and we can see in the type annotation,\nthat this object is of type `AtomicOrder`. We can also see a bunch of values inside the\nswitch statements that begins with a dot character, such as `.release` and `.acquire`.\n\nBecause these weird values contain a dot character before them, we are asking the `zig`\ncompiler to infer the types of these values inside the switch statement. Then, the `zig`\ncompiler is looking into the current context where these values are being used, and it is\ntrying to infer the types of these values.\n\nSince they are being used inside a switch statement, the `zig` compiler looks into the type\nof the input object given to the switch statement, which is the `order` object in this case.\nBecause this object have type `AtomicOrder`, the `zig` compiler infers that these values\nare data members from this type `AtomicOrder`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub inline fn fence(self: *Self, comptime order: AtomicOrder) void {\n // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.\n if (builtin.sanitize_thread) {\n const tsan = struct {\n extern \"c\" fn __tsan_acquire(addr: *anyopaque) void;\n extern \"c\" fn __tsan_release(addr: *anyopaque) void;\n };\n\n const addr: *anyopaque = self;\n return switch (order) {\n .unordered, .monotonic => @compileError(@tagName(order) ++ \" only applies to atomic loads and stores\"),\n .acquire => tsan.__tsan_acquire(addr),\n .release => tsan.__tsan_release(addr),\n .acq_rel, .seq_cst => {\n tsan.__tsan_acquire(addr);\n tsan.__tsan_release(addr);\n },\n };\n }\n\n return @fence(order);\n}\n```\n:::\n\n\nThis is how basic type inference is done in Zig. If we didn't use the dot character before\nthe values inside this switch statement, then, we would be forced to write explicitly\nthe data types of these values. For example, instead of writing `.release` we would have to\nwrite `AtomicOrder.release`. We would have to do this for every single value\nin this switch statement, and this is a lot of work. That is why type inference\nis commonly used on switch statements in Zig.\n\n\n\n## Modules\n\nWe already talked about what modules are, and also, how to import other modules into\nyou current module through *import statements*, so that you can use functionality from these other modules in\nyour current module.\nBut in this section, I just want to make it clear that modules are actually structs in Zig.\n\nIn other words, every Zig module (i.e. a `.zig` file) that you write in your project\nis internally stored as a struct object.\nTake the line exposed below as an example. In this line we are importing the\nZig Standard Library into our current module.\n\n```zig\nconst std = @import(\"std\");\n```\n\nWhen we want to access the functions and objects from the standard library, we\nare basically accessing the data members of the struct stored in the `std`\nobject. That is why we use the same syntax that we use in normal structs, with the dot operator (`.`)\nto access the data members and methods of the struct.\n\nWhen this \"import statement\" get's executed, the result of this expression is a struct\nobject that contains the Zig Standard Library modules, global variables, functions, etc.\nAnd this struct object get's saved (or stored) inside the constant object named `std`.\n\n\nTake the [`thread_pool.zig` module from the project `zap`](https://github.com/kprotty/zap/blob/blog/src/thread_pool.zig)[^thread]\nas an example. This module is written as if it was\na big struct. That is why we have a top-level and public `init()` method\nwritten in this module. The idea is that all top-level functions written in this\nmodule are methods from the struct, and all top-level objects and struct declarations\nare data members of this struct. The module is the struct itself.\n\n[^thread]: \n\n\nSo you would import and use this module by doing something like this:\n\n```zig\nconst std = @import(\"std\");\nconst ThreadPool = @import(\"thread_pool.zig\");\nconst num_cpus = std.Thread.getCpuCount()\n catch @panic(\"failed to get cpu core count\");\nconst num_threads = std.math.cast(u16, num_cpus)\n catch std.math.maxInt(u16);\nconst pool = ThreadPool.init(\n .{ .max_threads = num_threads }\n);\n```\n", + "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n# Structs, Modules and Control Flow\n\nI introduced a lot of the Zig's syntax to you in the last chapter,\nspecially at @sec-root-file and @sec-main-file.\nBut we still need to discuss about some other very important\nelements of the language that you will use constantly on your day-to-day\nroutine.\n\nWe begin this chapter by talking about the different keywords and structures\nin Zig related to control flow (e.g. loops and if statements).\nThen, we talk about structs and how they can be used to do some\nbasic Object-Oriented (OOP) patterns in Zig. We also talk about\ntype inference, which help us to write less code and achieve the same results.\nFinally, we end this chapter by discussing modules, and how they relate\nto structs.\n\n\n\n## Control flow {#sec-zig-control-flow}\n\nSometimes, you need to make decisions in your program. Maybe you need to decide\nwether to execute or not a specific piece of code. Or maybe,\nyou need to apply the same operation over a sequence of values. These kinds of tasks,\ninvolve using structures that are capable of changing the \"control flow\" of our program.\n\nIn computer science, the term \"control flow\" usually refers to the order in which expressions (or commands)\nare evaluated in a given language or program. But this term is also used to refer\nto structures that are capable of changing this \"evaluation order\" of the commands\nexecuted by a given language/program.\n\nThese structures are better known\nby a set of terms, such as: loops, if/else statements, switch statements, among others. So,\nloops and if/else statements are examples of structures that can change the \"control\nflow\" of our program. The keywords `continue` and `break` are also examples of symbols\nthat can change the order of evaluation, since they can move our program to the next iteration\nof a loop, or make the loop stop completely.\n\n\n### If/else statements\n\nAn if/else statement performs an \"conditional flow operation\".\nA conditional flow control (or choice control) allows you to execute\nor ignore a certain block of commands based on a logical condition.\nMany programmers and computer science professionals also use\nthe term \"branching\" in this case.\nIn essence, we use if/else statements to use the result of a logical test\nto decide whether or not to execute a given block of commands.\n\nIn Zig, we write if/else statements by using the keywords `if` and `else`.\nWe start with the `if` keyword followed by a logical test inside a pair\nof parentheses, and then, a pair of curly braces with contains the lines\nof code to be executed in case the logical test returns the value `true`.\n\nAfter that, you can optionally add an `else` statement. Just add the `else`\nkeyword followed by a pair of curly braces, with the lines of code\nto executed in case the logical test defined in the `if`\nreturns `false`.\n\nIn the example below, we are testing if the object `x` contains a number\nthat is greater than 10. Judging by the output printed to the console,\nwe know that this logical test returned `false`. Because the output\nin the console is compatible with the line of code present in the\n`else` branch of the if/else statement.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst x = 5;\nif (x > 10) {\n try stdout.print(\n \"x > 10!\\n\", .{}\n );\n} else {\n try stdout.print(\n \"x <= 10!\\n\", .{}\n );\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nx <= 10!\n```\n\n\n:::\n:::\n\n\n\n\n### Swith statements {#sec-switch}\n\nSwitch statements are also available in Zig.\nA switch statement in Zig have a similar syntax to a switch statement in Rust.\nAs you would expect, to write a switch statement in Zig we use the `switch` keyword.\nWe provide the value that we want to \"switch over\" inside a\npair of parentheses. Then, we list the possible combinations (or \"branchs\")\ninside a pair of curly braces.\n\nLet's take a look at the code example below. You can see in this example that,\nI'm creating an enum type called `Role`. We talk more about enums at @sec-enum.\nBut in essence, this `Role` type is listing different types of roles in a fictitious\ncompany, like `SE` for Software Engineer, `DE` for Data Engineer, `PM` for Product Manager,\netc.\n\nNotice that we are using the value from the `role` object in the\nswitch statement, to discover which exact area we need to store in the `area` variable object.\nAlso notice that we are using type inference inside the switch statement, with the dot character,\nas we described at @sec-type-inference.\nThis makes the `zig` compiler infer the correct data type of the values (`PM`, `SE`, etc.) for us.\n\nAlso notice that, we are grouping multiple values in the same branch of the switch statement.\nWe just separate each possible value with a comma. So, for example, if `role` contains either `DE` or `DA`,\nthe `area` variable would contain the value `\"Data & Analytics\"`, instead of `\"Platform\"`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst Role = enum {\n SE, DPE, DE, DA, PM, PO, KS\n};\n\npub fn main() !void {\n var area: []const u8 = undefined;\n const role = Role.SE;\n switch (role) {\n .PM, .SE, .DPE, .PO => {\n area = \"Platform\";\n },\n .DE, .DA => {\n area = \"Data & Analytics\";\n },\n .KS => {\n area = \"Sales\";\n },\n }\n try stdout.print(\"{s}\\n\", .{area});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nPlatform\n```\n\n\n:::\n:::\n\n\nNow, one very important aspect about this switch statement presented\nin the code example above, is that it exhaust all existing possibilities.\nIn other words, all possible values that could be found inside the `order`\nobject are explicitly handled in this switch statement.\n\nSince the `role` object have type `Role`, the only possible values to\nbe found inside this object are `PM`, `SE`, `DPE`, `PO`, `DE`, `DA` and `KS`.\nThere is no other possible value to be stored in this `role` object.\nThis what \"exhaust all existing possibilities\" means. The switch statement covers\nevery possible case.\n\nIn Zig, switch statements must exhaust all existing possibilities. You cannot write\na switch statement, and leave an edge case with no expliciting action to be taken.\nThis is a similar behaviour to switch statements in Rust, which also have to\nhandle all possible cases.\n\nTake a look at the `dump_hex_fallible()` function below as an example. This function\nalso comes from the Zig Standard Library, but this time, it comes from the [`debug.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/debug.zig)[^debug-mod].\nThere are multiple lines in this function, but I omitted them to focus solely on the\nswitch statement found in this function. Notice that this switch statement have four\npossible cases, or four explicit branches. Also, notice that we used an `else` branch\nin this case. Whenever you have multiple possible cases in your switch statement\nwhich you want to apply the same exact action, you can use an `else` branch to do that.\n\n[^debug-mod]: \n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub fn dump_hex_fallible(bytes: []const u8) !void {\n // Many lines ...\n switch (byte) {\n '\\n' => try writer.writeAll(\"␊\"),\n '\\r' => try writer.writeAll(\"␍\"),\n '\\t' => try writer.writeAll(\"␉\"),\n else => try writer.writeByte('.'),\n }\n}\n```\n:::\n\n\nMany users would also use an `else` branch to handle a \"not supported\" case.\nThat is, a case that cannot be properly handled by your code, or, just a case that\nshould not be \"fixed\". So many programmers use an `else` branch to panic (or raise an error) to stop\nthe current execution.\n\nTake the code example below as an example. We can see that, we are handling the cases\nfor the `level` object being either 1, 2, or 3. All other possible cases are not supported by default,\nand, as consequence, we raise an runtime error in these cases, through the `@panic()` built-in function.\n\nAlso notice that, we are assigning the result of the switch statement to a new object called `category`.\nThis is another thing that you can do with switch statements in Zig. If the branchs in this switch\nstatement output some value as result, you can store the result value of the switch statement into\na new object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 1, 2 => \"beginner\",\n 3 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n:::\n\n\n```\nthread 13103 panic: Not supported level!\nt.zig:9:13: 0x1033c58 in main (switch2)\n @panic(\"Not supported level!\");\n ^\n```\n\nFurthermore, you can also use ranges of values in switch statements.\nThat is, you can create a branch in your switch statement that is used\nwhenever the input value is contained in a range. These range\nexpressions are created with the operator `...`. Is important\nto emphasize that the ranges created by this operator are\ninclusive on both ends.\n\nFor example, I could easily change the code example above to support all\nlevels between 0 and 100. Like this:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 0...25 => \"beginner\",\n 26...75 => \"intermediary\",\n 76...100 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nbeginner\n```\n\n\n:::\n:::\n\n\nThis is neat, and it works with character ranges too. That is, I could\nsimply write `'a'...'z'`, to match any character value that is a\nlowercase letter, and it would work fine.\n\n\n\n### The `defer` keyword {#sec-defer}\n\nWith the `defer` keyword you can execute expressions at the end of the current scope.\nTake the `foo()` function below as an example. When we execute this function, the expression\nthat prints the message \"Exiting function ...\" get's executed only at\nthe end of the function scope.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nfn foo() !void {\n defer std.debug.print(\n \"Exiting function ...\\n\", .{}\n );\n try stdout.print(\"Adding some numbers ...\\n\", .{});\n const x = 2 + 2; _ = x;\n try stdout.print(\"Multiplying ...\\n\", .{});\n const y = 2 * 8; _ = y;\n}\n\npub fn main() !void {\n try foo();\n}\n```\n:::\n\n\n```\nAdding some numbers ...\nMultiplying ...\nExiting function ...\n```\n\nIt doesn't matter how the function exits (i.e. because\nof an error, or, because of an return statement, or whatever),\njust remember, this expression get's executed when the function exits.\n\n\n\n\n### For loops\n\nA loop allows you to execute the same lines of code multiple times,\nthus, creating a \"repetition space\" in the execution flow of your program.\nLoops are particularly useful when we want to replicate the same function\n(or the same set of commands) over several different inputs.\n\nThere are different types of loops available in Zig. But the most\nessential of them all is probably the *for loop*. A for loop is\nused to apply the same piece of code over the elements of a slice or an array.\n\nFor loops in Zig have a slightly different syntax that you are\nprobably used to see in other languages. You start with the `for` keyword, then, you\nlist the items that you want to iterate\nover inside a pair of parentheses. Then, inside of a pair of pipes (`|`)\nyou should declare an identifier that will serve as your iterator, or,\nthe \"repetition index of the loop\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (items) |value| {\n // code to execute\n}\n```\n:::\n\n\nInstead of using a `(value in items)` syntax,\nin Zig, for loops use the syntax `(items) |value|`. In the example\nbelow, you can see that we are looping through the items\nof the array stored at the object `name`, and printing to the\nconsole the decimal representation of each character in this array.\n\nIf we wanted, we could also iterate through a slice (or a portion) of\nthe array, instead of iterating through the entire array stored in the `name` object.\nJust use a range selector to select the section you want. For example,\nI could provide the expression `name[0..3]` to the for loop, to iterate\njust through the first 3 elements in the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst name = [_]u8{'P','e','d','r','o'};\nfor (name) |char| {\n try stdout.print(\"{d} | \", .{char});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n80 | 101 | 100 | 114 | 111 | \n```\n\n\n:::\n:::\n\n\nIn the above example we are using the value itself of each\nelement in the array as our iterator. But there are many situations where\nwe need to use an index instead of the actual values of the items.\n\nYou can do that by providing a second set of items to iterate over.\nMore precisely, you provide the range selector `0..` to the for loop. So,\nyes, you can use two different iterators at the same time in a for\nloop in Zig.\n\nBut remember from @sec-assignments that, every object\nyou create in Zig must be used in some way. So if you declare two iterators\nin your for loop, you must use both iterators inside the for loop body.\nBut if you want to use just the index iterator, and not use the \"value iterator\",\nthen, you can discard the value iterator by maching the\nvalue items to the underscore character, like in the example below:\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (name, 0..) |_, i| {\n try stdout.print(\"{d} | \", .{i});\n}\n```\n:::\n\n\n```\n0 | 1 | 2 | 3 | 4 |\n```\n\n\n### While loops\n\nA while loop is created from the `while` keyword. A `for` loop\niterates through the items of an array, but a `while` loop\nwill loop continuously, and infinitely, until a logical test\n(specified by you) becomes false.\n\nYou start with the `while` keyword, then, you define a logical\nexpression inside a pair of parentheses, and the body of the\nloop is provided inside a pair of curly braces, like in the example below:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: u8 = 1;\nwhile (i < 5) {\n try stdout.print(\"{d} | \", .{i});\n i += 1;\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 2 | 3 | 4 | \n```\n\n\n:::\n:::\n\n\n\n\n### Using `break` and `continue`\n\nIn Zig, you can explicitly stop the execution of a loop, or, jump to the next iteration of the loop, using\nthe keywords `break` and `continue`, respectively. The `while` loop present in the example below, is\nat first sight, an infinite loop. Because the logical value inside the parenthese will always be equal to `true`.\nWhat makes this `while` loop stop when the `i` object reaches the count\n10? Is the `break` keyword!\n\nInside the while loop, we have an if statement that is constantly checking if the `i` variable\nis equal to 10. Since we are increasing the value of this `i` variable at each iteration of the\nwhile loop. At some point, this `i` variable will be equal to 10, and when it does, the if statement\nwill execute the `break` expression, and, as a result, the execution of the while loop is stopped.\n\nNotice the `expect()` function from the Zig standard library after the while loop.\nThis `expect()` function is an \"assert\" type of function.\nThis function checks if the logical test provided is equal to true. If this logical test is false,\nthe function raises an assertion error. But it is equal to true, then, the function will do nothing.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: usize = 0;\nwhile (true) {\n if (i == 10) {\n break;\n }\n i += 1;\n}\ntry std.testing.expect(i == 10);\ntry stdout.print(\"Everything worked!\", .{});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nEverything worked!\n```\n\n\n:::\n:::\n\n\nSince this code example was executed succesfully by the `zig` compiler,\nwithout raising any errors, then, we known that, after the execution of while loop,\nthe `i` variable is equal to 10. Because if it wasn't equal to 10, then, an error would\nbe raised by `expect()`.\n\nNow, in the next example, we have an use case for\nthe `continue` keyword. The if statement is constantly\nchecking if the current index is a multiple of 2. If\nit is, then we jump to the next iteration of the loop\ndirectly. But it the current index is not a multiple of 2,\nthen, the loop will simply print this index to the console.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [_]u8{1,2,3,4,5,6};\nfor (ns) |i| {\n if ((i % 2) == 0) {\n continue;\n }\n try stdout.print(\"{d} | \", .{i});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 3 | 5 | \n```\n\n\n:::\n:::\n\n\n\n\n## Function parameters are immutable {#sec-fun-pars}\n\nWe have already discussed a lot of the syntax behind function declarations at @sec-root-file and @sec-main-file.\nBut I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig.\nIn summary, function parameters are immutable in Zig.\n\nTake the code example below, where we declare a simple function that just tries to add\nsome amount to the input integer, and returns the result back. But if you look closely\nat the body of this `add2()` function, you will notice that we try\nto save the result back into the `x` function argument.\n\nIn other words, this function not only use the value that it received through the function argument\n`x`, but it also tries to change the value of this function argument, by assigning the addition result\ninto `x`. However, function arguments in Zig are immutable. You cannot change their values, or, you\ncannot assign values to them inside the body's function.\n\nThis is the reason why, the code example below do not compile successfully. If you try to compile\nthis code example, you get a compile error warning you that you are trying to change the value of a\nimmutable (i.e. constant) object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: u32) u32 {\n x = x + 2;\n return x;\n}\n\npub fn main() !void {\n const y = add2(4);\n std.debug.print(\"{d}\\n\", .{y});\n}\n```\n:::\n\n\n```\nt.zig:3:5: error: cannot assign to constant\n x = x + 2;\n ^\n```\n\n\nIf a function argument receives as input a object whose data type is\nany of the primitive types that we have listed at @sec-primitive-data-types\nthis object is always passed by value to the function. In other words, this object\nis copied to the function stack frame.\n\nHowever, if the input object have a more complex data type, for example, it might\nbe a struct instance, or an array, or a union, etc., in cases like that, the `zig` compiler\nwill take the liberty of deciding for you which strategy is best. The `zig` compiler will\npass your object to the function either by value, or by reference. The compiler will always\nchoose the strategy that is faster for you.\nThis optimization that you get for free is possible only because function arguments are\nimmutable in Zig.\n\nTo overcome this barrier, we need to take the lead, and explicitly choose to pass the\nobject by reference. That is, instead of depending on the `zig` compiler to decide for us, we need\nto explicitly mark the function argument as a pointer. This way, we are telling the compiler\nthat this function argument will be passed by reference to the function.\n\nBy making it a pointer, we can finally use and alter directly the value of this function argument inside\nthe body of the `add2()` function. You can see that the code example below compiles successfully.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: *u32) void {\n const d: u32 = 2;\n x.* = x.* + d;\n}\n\npub fn main() !void {\n var x: u32 = 4;\n add2(&x);\n std.debug.print(\"Result: {d}\\n\", .{x});\n}\n```\n:::\n\n\n```\nResult: 6\n```\n\n\n\n## Structs and OOP {#sec-structs-and-oop}\n\nZig is a language more closely related to C (which is a procedural language),\nthan it is to C++ or Java (which are object-oriented languages). Because of that, you do not\nhave advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or\nclass inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\n\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C.\nYou give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can\nalso register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object\nthat you create with this new type, will always have these methods available and associated with them.\n\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object\nof this particular class, and you also have a destructor method (or a destructor function) that\nis the function responsible for destroying every object of this class.\n\nIn Zig, we normally declare the constructor and the destructor methods\nof our structs, by declaring an `init()` and a `deinit()` methods inside the struct.\nThis is just a naming convention that you will find across the entire Zig standard library.\nSo, in Zig, the `init()` method of a struct is normally the constructor method of the class represented by this struct.\nWhile the `deinit()` method is the method used for destroying an existing instance of that struct.\n\nThe `init()` and `deinit()` methods are both used extensively in Zig code, and you will see both of\nthem being used when we talk about allocators at @sec-allocators.\nBut, as another example, let's build a simple `User` struct to represent an user of some sort of system.\nIf you look at the `User` struct below, you can see the `struct` keyword, and inside of a\npair of curly braces, we write the struct's body.\n\nNotice the data members of this struct, `id`, `name` and `email`. Every data member have it's\ntype explicitly annotated, with the colon character (`:`) syntax that we described earlier at @sec-root-file.\nBut also notice that every line in the struct body that describes a data member, ends with a comma character (`,`).\nSo every time you declare a data member in your Zig code, always end the line with a comma character, instead\nof ending it with the traditional semicolon character (`;`).\n\nNext, also notice in this example, that we registrated an `init()` function as a method\nof this `User` struct. This `init()` method is the constructor method that you use to instantiate\nevery new `User` object. That is why this `init()` function return an `User` object as result.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\npedro\n```\n\n\n:::\n:::\n\n\nThe `pub` keyword plays an important role in struct declarations, and OOP in Zig.\nEvery method that you declare in your struct that is marked with the keyword `pub`,\nbecomes a public method of this particular struct.\n\nSo every method that you create in your struct, is, at first, a private method\nof that struct. Meaning that, this method can only be called from within this\nstruct. But, if you mark this method as public, with the keyword `pub`, then,\nyou can call the method directly from the `User` object you have\nin your code.\n\nIn other words, the functions marked by the keyword `pub`\nare members of the public API of that struct.\nFor example, if I did not marked the `print_name()` method as public,\nthen, I could not execute the line `u.print_name()`. Because I would\nnot be authorized to call this method directly in my code.\n\n\n\n### Anonymous struct literals {#sec-anonymous-struct-literals}\n\nYou can declare a struct object as a literal value. When we do that, we normally specify the\ndata type of this struct literal by writing it's data type just before the opening curly braces.\nFor example, I could write a struct literal of type `User` that we defined in the previous section like\nthis:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n```\n:::\n\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a\nstruct literal, but not especify explicitly the type of this particular struct.\nAn anonymous struct is written by using the syntax `.{}`. So, we essentially\nreplaced the explicit type of the struct literal with a dot character (`.`).\n\nAs we described at @sec-type-inference, when you put a dot before a struct literal,\nthe type of this struct literal is automatically inferred by the `zig` compiler.\nIn essence, the `zig` compiler will look for some hint of what is the type of that struct.\nIt can be the type annotation of an function argument,\nor the return type annotation of the function that you are using, or the type annotation\nof a variable.\nIf the compiler do find such type annotation, then, it will use this\ntype in your literal struct. \n\nAnonymous structs are very commom to use in function arguments in Zig.\nOne example that you have seen already constantly, is the `print()`\nfunction from the `stdout` object.\nThis function takes two arguments.\nThe first argument, is a template string, which should\ncontain string format specifiers in it, which tells how the values provided\nin the second argument should be printed into the message.\n\nWhile the second argument is a struct literal that lists the values\nto be printed into the template message specified in the first argument.\nYou normally want to use an anonymous struct literal here, so that, the\n`zig` compiler do the job of specifying the type of this particular\nanonymous struct for you.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHello, world!\n```\n\n\n:::\n:::\n\n\n\n\n### Struct declarations must be constant\n\nTypes in Zig must be `const` or `comptime` (we are going to talk more about comptime at @sec-comptime).\nWhat this means is that you cannot create a new data type, and mark it as variable with the `var` keyword.\nSo struct declarations are always constant. You cannot declare a new struct using the `var` keyword.\nIt must be `const`.\n\nIn the `Vec3` example below, this declaration is allowed because I'm using the `const` keyword\nto declare this new data type.\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n```\n:::\n\n\n\n### The `self` method argument {#sec-self-arg}\n\nIn every language that have OOP, when we declare a method of some class or struct, we\nusually declare this method as a function that have a `self` argument.\nThis `self` argument is the reference to the object itself from which the method\nis being called from.\n\nIs not mandatory to use this `self` argument. But why would you not use this `self` argument?\nThere is no reason to not use it. Because the only way to get access to the data stored in the\ndata members of your struct is to access them through this `self` argument.\nIf you don't need to use the data in the data members of your struct inside your method, then, you very likely don't need\na method, you can just simply declare this logic as a simple function, outside of your\nstruct declaration.\n\n\nTake the `Vec3` struct below. Inside this `Vec3` struct we declared a method named `distance()`.\nThis method calculates the distance between two `Vec3` objects, by following the distance\nformula in euclidean space. Notice that this `distance()` method takes two `Vec3` objects\nas input, `self` and `other`.\n\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n```\n:::\n\n\n\nThe `self` argument corresponds to the `Vec3` object from which this `distance()` method\nis being called from. While the `other` is a separate `Vec3` object that is given as input\nto this method. In the example below, the `self` argument corresponds to the object\n`v1`, because the `distance()` method is being called from the `v1` object,\nwhile the `other` argument corresponds to the object `v2`.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n```\n:::\n\n\n```\nDistance: 3.3970575502926055\n```\n\n\n\n### About the struct state\n\nSometimes you don't need to care about the state of your struct object. Sometimes, you just need\nto instantiate and use the objects, without altering their state. You can notice that when you have methods\ninside your struct declaration that might use the values that are present in the data members, but they\ndo not alter the values in the data members of the struct in anyway.\n\nThe `Vec3` struct that was presented at @sec-self-arg is an example of that.\nThis struct have a single method named `distance()`, and this method do use the values\npresent in all three data members of the struct (`x`, `y` and `z`). But at the same time,\nthis method do not change the values of these data members in any point.\n\nAs a result of that, when we create `Vec3` objects we usually create them as\nconstant objects, like the `v1` and `v2` objects presented at @sec-self-arg.\nWe can create them as variable objects with the `var` keyword,\nif we want to. But because the methods of this `Vec3` struct do not change\nthe state of the objects in any point, is unnecessary to mark them\nas variable objects.\n\nBut why? Why am I talkin about this here? Is because the `self` argument\nin the methods is affected depending on whether the\nmethods present in a struct change or not the state of the object itself.\nMore specifically, when you have a method in a struct that changes the state\nof the object (i.e. change the value of a data member), the `self` argument\nin this method must be annotated in a different manner.\n\nAs I described at @sec-self-arg, the `self` argument in methods of\na struct is the argument that receives as input the object from which the method\nwas called from. We usually annotate this argument in the methods by writing `self`,\nfollowed by the colon character (`:`), and the data type of the struct to which\nthe method belongs to (e.g. `User`, `Vec3`, etc.).\n\nIf we take the `Vec3` struct that we defined in the previous section as an example,\nwe can see in the `distance()` method that this `self` argument is annotated as\n`self: Vec3`. Because the state of the `Vec3` object is never altered by this\nmethod.\n\nBut what if we do have a method that alters the state of the object, by altering the\nvalues of it's data members. How should we annotate `self` in this instance? The answer is:\n\"we should annotate `self` as a pointer of `x`, instead of just `x`\".\nIn other words, you should annotate `self` as `self: *x`, instead of annotating it\nas `self: x`.\n\nIf we create a new method inside the `Vec3` object that, for example, expands the\nvector by multiplying it's coordinates by a factor o two, then, we need to follow\nthis rule specified in the previous paragraph. The code example below demonstrates\nthis idea:\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n```\n:::\n\n\nNotice in the code example above that we have added a new method\nto our `Vec3` struct named `double()`. This method essentially doubles the\ncoordinate values of our vector object. Also notice that, in the\ncase of the `double()` method, we annotated the `self` argument as `*Vec3`,\nindicating that this argument receives a pointer (or a reference, if you prefer to call it this way)\nto a `Vec3` object as input.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n```\n:::\n\n\n```\nDoubled: 8.4\n```\n\n\n\nNow, if you change the `self` argument in this `double()` method to `self: Vec3`, like in the\n`distance()` method, you will get the compiler error exposed below as result. Notice that this\nerror message is indicating a line from the `double()` method body,\nindicating that you cannot alter the value of the `x` data member.\n\n```zig\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\n```\n\nThis error message indicates that the `x` data member belongs to a constant object,\nand, because of that, it cannot be changed. Ultimately, this error message\nis telling us that the `self` argument is constant.\n\n```\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\n```\n\nIf you take some time, and think hard about this error message, you will understand it.\nYou already have the tools to understand why we are getting this error message.\nWe have talked about it already at @sec-fun-pars.\n\nSo remember, every function argument is immutable in Zig.\nIt does not matter if we marked the `v3` object as a variable object.\nBecause we are trying to alter `self` directly, which is a function argument,\nand, every function argument is immutable by default.\nWe overcome this barrier, by explicitly marking the `self` argument as a pointer.\n\n\n::: {.callout-note}\nIf a method of your `x` struct alters the state of the object, by\nchanging the value of any data member, then, remember to use `self: *x`,\ninstead of `self: x` in the function signature of this method.\n:::\n\n\nYou could also interpret the content discussed in this section as:\n\"if you need to alter the state of your `x` struct object in one of it's methods,\nyou must explicitly pass the `x` struct object by reference to the `self` argument of this method\".\n\n\n\n## Type inference {#sec-type-inference}\n\nZig is kind of a strongly typed language. I say \"kind of\" because there are situations\nwhere you don't have to explicitly write the type of every single object in your source code,\nas you would expect from a traditional strongly typed language, such as C and C++.\n\nIn some situations, the `zig` compiler can use type inference to solves the data types for you, easing some of\nthe burden that you carry as a developer.\nThe most commom way this happens is through function arguments that receives struct objects\nas input.\n\nIn general, type inference in Zig is done by using the dot character (`.`).\nEverytime you see a dot character written before a struct literal, or before a enum value, or something like that,\nyou know that this dot character is playing a special party in this place. More specifically, it is\ntelling the `zig` compiler something on the lines of: \"Hey! Can you infer the type of this\nvalue for me? Please!\". In other words, this dot character is playing a role similar to the `auto` keyword in C++.\n\nI give you some examples of this at @sec-anonymous-struct-literals, where we present anonymous struct literals.\nAnonymous struct literals are, essentially, struct literals that use type inference to\ninfer the exact type of this particular struct literal.\nThis type inference is done by looking for some minimal hint of the correct data type to be used.\nYou could say that the `zig` compiler looks for any neighbouring type annotation that might tell him\nwhat would be the correct type.\n\nAnother commom place where we use type inference in Zig is at switch statements (which we talk about at @sec-switch).\nSo I also gave some other examples of type inference at @sec-switch, where we were inferring the data types of enum values listed inside\nof switch statements (e.g. `.DE`).\nBut as another example, take a look at this `fence()` function reproduced below,\nwhich comes from the [`atomic.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/atomic.zig)[^fence-fn]\nof the Zig Standard Library.\n\n[^fence-fn]: .\n\nThere are a lot of things in this function that we haven't talked about yet, such as:\nwhat `comptime` means? `inline`? `extern`? What is this star symbol before `Self`?\nLet's just ignore all of these things, and focus solely on the switch statement\nthat is inside this function.\n\nWe can see that this switch statement uses the `order` object as input. This `order`\nobject is one of the inputs of this `fence()` function, and we can see in the type annotation,\nthat this object is of type `AtomicOrder`. We can also see a bunch of values inside the\nswitch statements that begins with a dot character, such as `.release` and `.acquire`.\n\nBecause these weird values contain a dot character before them, we are asking the `zig`\ncompiler to infer the types of these values inside the switch statement. Then, the `zig`\ncompiler is looking into the current context where these values are being used, and it is\ntrying to infer the types of these values.\n\nSince they are being used inside a switch statement, the `zig` compiler looks into the type\nof the input object given to the switch statement, which is the `order` object in this case.\nBecause this object have type `AtomicOrder`, the `zig` compiler infers that these values\nare data members from this type `AtomicOrder`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub inline fn fence(self: *Self, comptime order: AtomicOrder) void {\n // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.\n if (builtin.sanitize_thread) {\n const tsan = struct {\n extern \"c\" fn __tsan_acquire(addr: *anyopaque) void;\n extern \"c\" fn __tsan_release(addr: *anyopaque) void;\n };\n\n const addr: *anyopaque = self;\n return switch (order) {\n .unordered, .monotonic => @compileError(@tagName(order) ++ \" only applies to atomic loads and stores\"),\n .acquire => tsan.__tsan_acquire(addr),\n .release => tsan.__tsan_release(addr),\n .acq_rel, .seq_cst => {\n tsan.__tsan_acquire(addr);\n tsan.__tsan_release(addr);\n },\n };\n }\n\n return @fence(order);\n}\n```\n:::\n\n\nThis is how basic type inference is done in Zig. If we didn't use the dot character before\nthe values inside this switch statement, then, we would be forced to write explicitly\nthe data types of these values. For example, instead of writing `.release` we would have to\nwrite `AtomicOrder.release`. We would have to do this for every single value\nin this switch statement, and this is a lot of work. That is why type inference\nis commonly used on switch statements in Zig.\n\n\n\n## Modules\n\nWe already talked about what modules are, and also, how to import other modules into\nyou current module through *import statements*, so that you can use functionality from these other modules in\nyour current module.\nBut in this section, I just want to make it clear that modules are actually structs in Zig.\n\nIn other words, every Zig module (i.e. a `.zig` file) that you write in your project\nis internally stored as a struct object.\nTake the line exposed below as an example. In this line we are importing the\nZig Standard Library into our current module.\n\n```zig\nconst std = @import(\"std\");\n```\n\nWhen we want to access the functions and objects from the standard library, we\nare basically accessing the data members of the struct stored in the `std`\nobject. That is why we use the same syntax that we use in normal structs, with the dot operator (`.`)\nto access the data members and methods of the struct.\n\nWhen this \"import statement\" get's executed, the result of this expression is a struct\nobject that contains the Zig Standard Library modules, global variables, functions, etc.\nAnd this struct object get's saved (or stored) inside the constant object named `std`.\n\n\nTake the [`thread_pool.zig` module from the project `zap`](https://github.com/kprotty/zap/blob/blog/src/thread_pool.zig)[^thread]\nas an example. This module is written as if it was\na big struct. That is why we have a top-level and public `init()` method\nwritten in this module. The idea is that all top-level functions written in this\nmodule are methods from the struct, and all top-level objects and struct declarations\nare data members of this struct. The module is the struct itself.\n\n[^thread]: \n\n\nSo you would import and use this module by doing something like this:\n\n```zig\nconst std = @import(\"std\");\nconst ThreadPool = @import(\"thread_pool.zig\");\nconst num_cpus = std.Thread.getCpuCount()\n catch @panic(\"failed to get cpu core count\");\nconst num_threads = std.math.cast(u16, num_cpus)\n catch std.math.maxInt(u16);\nconst pool = ThreadPool.init(\n .{ .max_threads = num_threads }\n);\n```\n", "supporting": [ "03-structs_files" ], diff --git a/docs/Chapters/03-structs.html b/docs/Chapters/03-structs.html index 923d518..5906944 100644 --- a/docs/Chapters/03-structs.html +++ b/docs/Chapters/03-structs.html @@ -526,18 +526,22 @@

      If a function argument receives as input a object whose data type is any of the primitive types that we have listed at Section 1.5 this object is always passed by value to the function. In other words, this object is copied to the function stack frame.

      However, if the input object have a more complex data type, for example, it might be a struct instance, or an array, or a union, etc., in cases like that, the zig compiler will take the liberty of deciding for you which strategy is best. The zig compiler will pass your object to the function either by value, or by reference. The compiler will always choose the strategy that is faster for you. This optimization that you get for free is possible only because function arguments are immutable in Zig.

      +

      To overcome this barrier, we need to take the lead, and explicitly choose to pass the object by reference. That is, instead of depending on the zig compiler to decide for us, we need to explicitly mark the function argument as a pointer. This way, we are telling the compiler that this function argument will be passed by reference to the function.

      +

      By making it a pointer, we can finally use and alter directly the value of this function argument inside the body of the add2() function. You can see that the code example below compiles successfully.

      const std = @import("std");
       fn add2(x: *u32) void {
      -    x = x + 2;
      -}
      -
      -pub fn main() !void {
      -    var x: u32 = @intCast(4);
      -    add2(&x);
      -    std.debug.print("{d}\n", .{x});
      -}
      + const d: u32 = 2; + x.* = x.* + d; +} + +pub fn main() !void { + var x: u32 = 4; + add2(&x); + std.debug.print("Result: {d}\n", .{x}); +}
      +
      Result: 6

      2.3 Structs and OOP

      @@ -549,33 +553,33 @@

      Notice the data members of this struct, id, name and email. Every data member have it’s type explicitly annotated, with the colon character (:) syntax that we described earlier at Section 1.2.2. But also notice that every line in the struct body that describes a data member, ends with a comma character (,). So every time you declare a data member in your Zig code, always end the line with a comma character, instead of ending it with the traditional semicolon character (;).

      Next, also notice in this example, that we registrated an init() function as a method of this User struct. This init() method is the constructor method that you use to instantiate every new User object. That is why this init() function return an User object as result.

      -
      const std = @import("std");
      -const stdout = std.io.getStdOut().writer();
      -const User = struct {
      -    id: u64,
      -    name: []const u8,
      -    email: []const u8,
      -
      -    pub fn init(id: u64,
      -                name: []const u8,
      -                email: []const u8) User {
      -
      -        return User {
      -            .id = id,
      -            .name = name,
      -            .email = email
      -        };
      -    }
      -
      -    pub fn print_name(self: User) !void {
      -        try stdout.print("{s}\n", .{self.name});
      -    }
      -};
      -
      -pub fn main() !void {
      -    const u = User.init(1, "pedro", "email@gmail.com");
      -    try u.print_name();
      -}
      +
      const std = @import("std");
      +const stdout = std.io.getStdOut().writer();
      +const User = struct {
      +    id: u64,
      +    name: []const u8,
      +    email: []const u8,
      +
      +    pub fn init(id: u64,
      +                name: []const u8,
      +                email: []const u8) User {
      +
      +        return User {
      +            .id = id,
      +            .name = name,
      +            .email = email
      +        };
      +    }
      +
      +    pub fn print_name(self: User) !void {
      +        try stdout.print("{s}\n", .{self.name});
      +    }
      +};
      +
      +pub fn main() !void {
      +    const u = User.init(1, "pedro", "email@gmail.com");
      +    try u.print_name();
      +}
      pedro
      @@ -587,23 +591,23 @@

      2.3.1 Anonymous struct literals

      You can declare a struct object as a literal value. When we do that, we normally specify the data type of this struct literal by writing it’s data type just before the opening curly braces. For example, I could write a struct literal of type User that we defined in the previous section like this:

      -
      const eu = User {
      -    .id = 1,
      -    .name = "Pedro",
      -    .email = "someemail@gmail.com"
      -};
      -_ = eu;
      +
      const eu = User {
      +    .id = 1,
      +    .name = "Pedro",
      +    .email = "someemail@gmail.com"
      +};
      +_ = eu;

      However, in Zig, we can also write an anonymous struct literal. That is, you can write a struct literal, but not especify explicitly the type of this particular struct. An anonymous struct is written by using the syntax .{}. So, we essentially replaced the explicit type of the struct literal with a dot character (.).

      As we described at Section 2.4, when you put a dot before a struct literal, the type of this struct literal is automatically inferred by the zig compiler. In essence, the zig compiler will look for some hint of what is the type of that struct. It can be the type annotation of an function argument, or the return type annotation of the function that you are using, or the type annotation of a variable. If the compiler do find such type annotation, then, it will use this type in your literal struct.

      Anonymous structs are very commom to use in function arguments in Zig. One example that you have seen already constantly, is the print() function from the stdout object. This function takes two arguments. The first argument, is a template string, which should contain string format specifiers in it, which tells how the values provided in the second argument should be printed into the message.

      While the second argument is a struct literal that lists the values to be printed into the template message specified in the first argument. You normally want to use an anonymous struct literal here, so that, the zig compiler do the job of specifying the type of this particular anonymous struct for you.

      -
      const std = @import("std");
      -pub fn main() !void {
      -    const stdout = std.io.getStdOut().writer();
      -    try stdout.print("Hello, {s}!\n", .{"world"});
      -}
      +
      const std = @import("std");
      +pub fn main() !void {
      +    const stdout = std.io.getStdOut().writer();
      +    try stdout.print("Hello, {s}!\n", .{"world"});
      +}
      Hello, world!
      @@ -614,11 +618,11 @@

      Section 12.1). What this means is that you cannot create a new data type, and mark it as variable with the var keyword. So struct declarations are always constant. You cannot declare a new struct using the var keyword. It must be const.

      In the Vec3 example below, this declaration is allowed because I’m using the const keyword to declare this new data type.

      -
      const Vec3 = struct {
      -    x: f64,
      -    y: f64,
      -    z: f64,
      -};
      +
      const Vec3 = struct {
      +    x: f64,
      +    y: f64,
      +    z: f64,
      +};

      @@ -627,34 +631,34 @@

      Is not mandatory to use this self argument. But why would you not use this self argument? There is no reason to not use it. Because the only way to get access to the data stored in the data members of your struct is to access them through this self argument. If you don’t need to use the data in the data members of your struct inside your method, then, you very likely don’t need a method, you can just simply declare this logic as a simple function, outside of your struct declaration.

      Take the Vec3 struct below. Inside this Vec3 struct we declared a method named distance(). This method calculates the distance between two Vec3 objects, by following the distance formula in euclidean space. Notice that this distance() method takes two Vec3 objects as input, self and other.

      -
      const std = @import("std");
      -const m = std.math;
      -const Vec3 = struct {
      -    x: f64,
      -    y: f64,
      -    z: f64,
      -
      -    pub fn distance(self: Vec3, other: Vec3) f64 {
      -        const xd = m.pow(f64, self.x - other.x, 2.0);
      -        const yd = m.pow(f64, self.y - other.y, 2.0);
      -        const zd = m.pow(f64, self.z - other.z, 2.0);
      -        return m.sqrt(xd + yd + zd);
      -    }
      -};
      +
      const std = @import("std");
      +const m = std.math;
      +const Vec3 = struct {
      +    x: f64,
      +    y: f64,
      +    z: f64,
      +
      +    pub fn distance(self: Vec3, other: Vec3) f64 {
      +        const xd = m.pow(f64, self.x - other.x, 2.0);
      +        const yd = m.pow(f64, self.y - other.y, 2.0);
      +        const zd = m.pow(f64, self.z - other.z, 2.0);
      +        return m.sqrt(xd + yd + zd);
      +    }
      +};

      The self argument corresponds to the Vec3 object from which this distance() method is being called from. While the other is a separate Vec3 object that is given as input to this method. In the example below, the self argument corresponds to the object v1, because the distance() method is being called from the v1 object, while the other argument corresponds to the object v2.

      -
      const v1 = Vec3 {
      -    .x = 4.2, .y = 2.4, .z = 0.9
      -};
      -const v2 = Vec3 {
      -    .x = 5.1, .y = 5.6, .z = 1.6
      -};
      -
      -std.debug.print(
      -    "Distance: {d}\n",
      -    .{v1.distance(v2)}
      -);
      +
      const v1 = Vec3 {
      +    .x = 4.2, .y = 2.4, .z = 0.9
      +};
      +const v2 = Vec3 {
      +    .x = 5.1, .y = 5.6, .z = 1.6
      +};
      +
      +std.debug.print(
      +    "Distance: {d}\n",
      +    .{v1.distance(v2)}
      +);
      Distance: 3.3970575502926055

      @@ -669,39 +673,39 @@

      But what if we do have a method that alters the state of the object, by altering the values of it’s data members. How should we annotate self in this instance? The answer is: “we should annotate self as a pointer of x, instead of just x”. In other words, you should annotate self as self: *x, instead of annotating it as self: x.

      If we create a new method inside the Vec3 object that, for example, expands the vector by multiplying it’s coordinates by a factor o two, then, we need to follow this rule specified in the previous paragraph. The code example below demonstrates this idea:

      -
      const std = @import("std");
      -const m = std.math;
      -const Vec3 = struct {
      -    x: f64,
      -    y: f64,
      -    z: f64,
      -
      -    pub fn distance(self: Vec3, other: Vec3) f64 {
      -        const xd = m.pow(f64, self.x - other.x, 2.0);
      -        const yd = m.pow(f64, self.y - other.y, 2.0);
      -        const zd = m.pow(f64, self.z - other.z, 2.0);
      -        return m.sqrt(xd + yd + zd);
      -    }
      -
      -    pub fn double(self: *Vec3) void {
      -        self.x = self.x * 2.0;
      -        self.y = self.y * 2.0;
      -        self.z = self.z * 2.0;
      -    }
      -};
      +
      const std = @import("std");
      +const m = std.math;
      +const Vec3 = struct {
      +    x: f64,
      +    y: f64,
      +    z: f64,
      +
      +    pub fn distance(self: Vec3, other: Vec3) f64 {
      +        const xd = m.pow(f64, self.x - other.x, 2.0);
      +        const yd = m.pow(f64, self.y - other.y, 2.0);
      +        const zd = m.pow(f64, self.z - other.z, 2.0);
      +        return m.sqrt(xd + yd + zd);
      +    }
      +
      +    pub fn double(self: *Vec3) void {
      +        self.x = self.x * 2.0;
      +        self.y = self.y * 2.0;
      +        self.z = self.z * 2.0;
      +    }
      +};

      Notice in the code example above that we have added a new method to our Vec3 struct named double(). This method essentially doubles the coordinate values of our vector object. Also notice that, in the case of the double() method, we annotated the self argument as *Vec3, indicating that this argument receives a pointer (or a reference, if you prefer to call it this way) to a Vec3 object as input.

      -
      var v3 = Vec3 {
      -    .x = 4.2, .y = 2.4, .z = 0.9
      -};
      -v3.double();
      -std.debug.print("Doubled: {d}\n", .{v3.x});
      +
      var v3 = Vec3 {
      +    .x = 4.2, .y = 2.4, .z = 0.9
      +};
      +v3.double();
      +std.debug.print("Doubled: {d}\n", .{v3.x});
      Doubled: 8.4

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

      -
      // If we change the function signature of double to:
      -    pub fn double(self: Vec3) void {
      +
      // If we change the function signature of double to:
      +    pub fn double(self: Vec3) void {

      This error message indicates that the x data member belongs to a constant object, and, because of that, it cannot be changed. Ultimately, this error message is telling us that the self argument is constant.

      t.zig:16:13: error: cannot assign to constant
               self.x = self.x * 2.0;
      @@ -736,28 +740,28 @@ 

      Because these weird values contain a dot character before them, we are asking the zig compiler to infer the types of these values inside the switch statement. Then, the zig compiler is looking into the current context where these values are being used, and it is trying to infer the types of these values.

      Since they are being used inside a switch statement, the zig compiler looks into the type of the input object given to the switch statement, which is the order object in this case. Because this object have type AtomicOrder, the zig compiler infers that these values are data members from this type AtomicOrder.

      -
      pub inline fn fence(self: *Self, comptime order: AtomicOrder) void {
      -    // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.
      -    if (builtin.sanitize_thread) {
      -        const tsan = struct {
      -            extern "c" fn __tsan_acquire(addr: *anyopaque) void;
      -            extern "c" fn __tsan_release(addr: *anyopaque) void;
      -        };
      -
      -        const addr: *anyopaque = self;
      -        return switch (order) {
      -            .unordered, .monotonic => @compileError(@tagName(order) ++ " only applies to atomic loads and stores"),
      -            .acquire => tsan.__tsan_acquire(addr),
      -            .release => tsan.__tsan_release(addr),
      -            .acq_rel, .seq_cst => {
      -                tsan.__tsan_acquire(addr);
      -                tsan.__tsan_release(addr);
      -            },
      -        };
      -    }
      -
      -    return @fence(order);
      -}
      +
      pub inline fn fence(self: *Self, comptime order: AtomicOrder) void {
      +    // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.
      +    if (builtin.sanitize_thread) {
      +        const tsan = struct {
      +            extern "c" fn __tsan_acquire(addr: *anyopaque) void;
      +            extern "c" fn __tsan_release(addr: *anyopaque) void;
      +        };
      +
      +        const addr: *anyopaque = self;
      +        return switch (order) {
      +            .unordered, .monotonic => @compileError(@tagName(order) ++ " only applies to atomic loads and stores"),
      +            .acquire => tsan.__tsan_acquire(addr),
      +            .release => tsan.__tsan_release(addr),
      +            .acq_rel, .seq_cst => {
      +                tsan.__tsan_acquire(addr);
      +                tsan.__tsan_release(addr);
      +            },
      +        };
      +    }
      +
      +    return @fence(order);
      +}

      This is how basic type inference is done in Zig. If we didn’t use the dot character before the values inside this switch statement, then, we would be forced to write explicitly the data types of these values. For example, instead of writing .release we would have to write AtomicOrder.release. We would have to do this for every single value in this switch statement, and this is a lot of work. That is why type inference is commonly used on switch statements in Zig.

      @@ -765,20 +769,20 @@

      2.5 Modules

      We already talked about what modules are, and also, how to import other modules into you current module through import statements, so that you can use functionality from these other modules in your current module. But in this section, I just want to make it clear that modules are actually structs in Zig.

      In other words, every Zig module (i.e. a .zig file) that you write in your project is internally stored as a struct object. Take the line exposed below as an example. In this line we are importing the Zig Standard Library into our current module.

      -
      const std = @import("std");
      +
      const std = @import("std");

      When we want to access the functions and objects from the standard library, we are basically accessing the data members of the struct stored in the std object. That is why we use the same syntax that we use in normal structs, with the dot operator (.) to access the data members and methods of the struct.

      When this “import statement” get’s executed, the result of this expression is a struct object that contains the Zig Standard Library modules, global variables, functions, etc. And this struct object get’s saved (or stored) inside the constant object named std.

      Take the thread_pool.zig module from the project zap3 as an example. This module is written as if it was a big struct. That is why we have a top-level and public init() method written in this module. The idea is that all top-level functions written in this module are methods from the struct, and all top-level objects and struct declarations are data members of this struct. The module is the struct itself.

      So you would import and use this module by doing something like this:

      -
      const std = @import("std");
      -const ThreadPool = @import("thread_pool.zig");
      -const num_cpus = std.Thread.getCpuCount()
      -    catch @panic("failed to get cpu core count");
      -const num_threads = std.math.cast(u16, num_cpus)
      -    catch std.math.maxInt(u16);
      -const pool = ThreadPool.init(
      -    .{ .max_threads = num_threads }
      -);
      +
      const std = @import("std");
      +const ThreadPool = @import("thread_pool.zig");
      +const num_cpus = std.Thread.getCpuCount()
      +    catch @panic("failed to get cpu core count");
      +const num_threads = std.math.cast(u16, num_cpus)
      +    catch std.math.maxInt(u16);
      +const pool = ThreadPool.init(
      +    .{ .max_threads = num_threads }
      +);
      diff --git a/docs/search.json b/docs/search.json index 952f9ab..5827ee2 100644 --- a/docs/search.json +++ b/docs/search.json @@ -224,7 +224,7 @@ "href": "Chapters/03-structs.html#sec-fun-pars", "title": "2  Structs, Modules and Control Flow", "section": "2.2 Function parameters are immutable", - "text": "2.2 Function parameters are immutable\nWe have already discussed a lot of the syntax behind function declarations at Section 1.2.2 and Section 1.2.3. But I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig. In summary, function parameters are immutable in Zig.\nTake the code example below, where we declare a simple function that just tries to add some amount to the input integer, and returns the result back. But if you look closely at the body of this add2() function, you will notice that we try to save the result back into the x function argument.\nIn other words, this function not only use the value that it received through the function argument x, but it also tries to change the value of this function argument, by assigning the addition result into x. However, function arguments in Zig are immutable. You cannot change their values, or, you cannot assign values to them inside the body’s function.\nThis is the reason why, the code example below do not compile successfully. If you try to compile this code example, you get a compile error warning you that you are trying to change the value of a immutable (i.e. constant) object.\n\nconst std = @import(\"std\");\nfn add2(x: u32) u32 {\n x = x + 2;\n return x;\n}\n\npub fn main() !void {\n const y = add2(4);\n std.debug.print(\"{d}\\n\", .{y});\n}\n\nt.zig:3:5: error: cannot assign to constant\n x = x + 2;\n ^\nIf a function argument receives as input a object whose data type is any of the primitive types that we have listed at Section 1.5 this object is always passed by value to the function. In other words, this object is copied to the function stack frame.\nHowever, if the input object have a more complex data type, for example, it might be a struct instance, or an array, or a union, etc., in cases like that, the zig compiler will take the liberty of deciding for you which strategy is best. The zig compiler will pass your object to the function either by value, or by reference. The compiler will always choose the strategy that is faster for you. This optimization that you get for free is possible only because function arguments are immutable in Zig.\n\nconst std = @import(\"std\");\nfn add2(x: *u32) void {\n x = x + 2;\n}\n\npub fn main() !void {\n var x: u32 = @intCast(4);\n add2(&x);\n std.debug.print(\"{d}\\n\", .{x});\n}", + "text": "2.2 Function parameters are immutable\nWe have already discussed a lot of the syntax behind function declarations at Section 1.2.2 and Section 1.2.3. But I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig. In summary, function parameters are immutable in Zig.\nTake the code example below, where we declare a simple function that just tries to add some amount to the input integer, and returns the result back. But if you look closely at the body of this add2() function, you will notice that we try to save the result back into the x function argument.\nIn other words, this function not only use the value that it received through the function argument x, but it also tries to change the value of this function argument, by assigning the addition result into x. However, function arguments in Zig are immutable. You cannot change their values, or, you cannot assign values to them inside the body’s function.\nThis is the reason why, the code example below do not compile successfully. If you try to compile this code example, you get a compile error warning you that you are trying to change the value of a immutable (i.e. constant) object.\n\nconst std = @import(\"std\");\nfn add2(x: u32) u32 {\n x = x + 2;\n return x;\n}\n\npub fn main() !void {\n const y = add2(4);\n std.debug.print(\"{d}\\n\", .{y});\n}\n\nt.zig:3:5: error: cannot assign to constant\n x = x + 2;\n ^\nIf a function argument receives as input a object whose data type is any of the primitive types that we have listed at Section 1.5 this object is always passed by value to the function. In other words, this object is copied to the function stack frame.\nHowever, if the input object have a more complex data type, for example, it might be a struct instance, or an array, or a union, etc., in cases like that, the zig compiler will take the liberty of deciding for you which strategy is best. The zig compiler will pass your object to the function either by value, or by reference. The compiler will always choose the strategy that is faster for you. This optimization that you get for free is possible only because function arguments are immutable in Zig.\nTo overcome this barrier, we need to take the lead, and explicitly choose to pass the object by reference. That is, instead of depending on the zig compiler to decide for us, we need to explicitly mark the function argument as a pointer. This way, we are telling the compiler that this function argument will be passed by reference to the function.\nBy making it a pointer, we can finally use and alter directly the value of this function argument inside the body of the add2() function. You can see that the code example below compiles successfully.\n\nconst std = @import(\"std\");\nfn add2(x: *u32) void {\n const d: u32 = 2;\n x.* = x.* + d;\n}\n\npub fn main() !void {\n var x: u32 = 4;\n add2(&x);\n std.debug.print(\"Result: {d}\\n\", .{x});\n}\n\nResult: 6", "crumbs": [ "2  Structs, Modules and Control Flow" ] From 95ed21bf6194eba7ee1e8e74d72cebb762d43781 Mon Sep 17 00:00:00 2001 From: pedropark99 Date: Sun, 11 Aug 2024 23:08:47 -0300 Subject: [PATCH 3/3] Add content --- Chapters/03-structs.qmd | 14 ++++++++++---- .../Chapters/03-structs/execute-results/html.json | 8 +++----- docs/Chapters/03-structs.html | 5 +++-- docs/search.json | 2 +- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Chapters/03-structs.qmd b/Chapters/03-structs.qmd index a36cc0e..bd38367 100644 --- a/Chapters/03-structs.qmd +++ b/Chapters/03-structs.qmd @@ -870,11 +870,17 @@ t.zig:16:13: error: cannot assign to constant If you take some time, and think hard about this error message, you will understand it. You already have the tools to understand why we are getting this error message. We have talked about it already at @sec-fun-pars. +So remember, every function argument is immutable in Zig, and `self` +is included in this rule. -So remember, every function argument is immutable in Zig. -It does not matter if we marked the `v3` object as a variable object. -Because we are trying to alter `self` directly, which is a function argument, -and, every function argument is immutable by default. +It does not matter if the object that you pass as input to the function argument is +a variable object or not. In this example, we marked the `v3` object as a variable object. +But this does not matter. Because it is not about the input object, it is about +the function argument. + +The problem begins when we try to alter the value of `self` directly, which is a function argument, +and, every function argument is immutable by default. You may quest yourself how can we overcome +this barrier, and once again, the solution was also discussed at @sec-fun-pars. We overcome this barrier, by explicitly marking the `self` argument as a pointer. diff --git a/_freeze/Chapters/03-structs/execute-results/html.json b/_freeze/Chapters/03-structs/execute-results/html.json index 9075b22..aba5524 100644 --- a/_freeze/Chapters/03-structs/execute-results/html.json +++ b/_freeze/Chapters/03-structs/execute-results/html.json @@ -1,11 +1,9 @@ { - "hash": "6c0925fa78e94905685fb609f42d3c84", + "hash": "486b722a24e15ad5b7f0ddad28848cd7", "result": { "engine": "knitr", - "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n# Structs, Modules and Control Flow\n\nI introduced a lot of the Zig's syntax to you in the last chapter,\nspecially at @sec-root-file and @sec-main-file.\nBut we still need to discuss about some other very important\nelements of the language that you will use constantly on your day-to-day\nroutine.\n\nWe begin this chapter by talking about the different keywords and structures\nin Zig related to control flow (e.g. loops and if statements).\nThen, we talk about structs and how they can be used to do some\nbasic Object-Oriented (OOP) patterns in Zig. We also talk about\ntype inference, which help us to write less code and achieve the same results.\nFinally, we end this chapter by discussing modules, and how they relate\nto structs.\n\n\n\n## Control flow {#sec-zig-control-flow}\n\nSometimes, you need to make decisions in your program. Maybe you need to decide\nwether to execute or not a specific piece of code. Or maybe,\nyou need to apply the same operation over a sequence of values. These kinds of tasks,\ninvolve using structures that are capable of changing the \"control flow\" of our program.\n\nIn computer science, the term \"control flow\" usually refers to the order in which expressions (or commands)\nare evaluated in a given language or program. But this term is also used to refer\nto structures that are capable of changing this \"evaluation order\" of the commands\nexecuted by a given language/program.\n\nThese structures are better known\nby a set of terms, such as: loops, if/else statements, switch statements, among others. So,\nloops and if/else statements are examples of structures that can change the \"control\nflow\" of our program. The keywords `continue` and `break` are also examples of symbols\nthat can change the order of evaluation, since they can move our program to the next iteration\nof a loop, or make the loop stop completely.\n\n\n### If/else statements\n\nAn if/else statement performs an \"conditional flow operation\".\nA conditional flow control (or choice control) allows you to execute\nor ignore a certain block of commands based on a logical condition.\nMany programmers and computer science professionals also use\nthe term \"branching\" in this case.\nIn essence, we use if/else statements to use the result of a logical test\nto decide whether or not to execute a given block of commands.\n\nIn Zig, we write if/else statements by using the keywords `if` and `else`.\nWe start with the `if` keyword followed by a logical test inside a pair\nof parentheses, and then, a pair of curly braces with contains the lines\nof code to be executed in case the logical test returns the value `true`.\n\nAfter that, you can optionally add an `else` statement. Just add the `else`\nkeyword followed by a pair of curly braces, with the lines of code\nto executed in case the logical test defined in the `if`\nreturns `false`.\n\nIn the example below, we are testing if the object `x` contains a number\nthat is greater than 10. Judging by the output printed to the console,\nwe know that this logical test returned `false`. Because the output\nin the console is compatible with the line of code present in the\n`else` branch of the if/else statement.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst x = 5;\nif (x > 10) {\n try stdout.print(\n \"x > 10!\\n\", .{}\n );\n} else {\n try stdout.print(\n \"x <= 10!\\n\", .{}\n );\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nx <= 10!\n```\n\n\n:::\n:::\n\n\n\n\n### Swith statements {#sec-switch}\n\nSwitch statements are also available in Zig.\nA switch statement in Zig have a similar syntax to a switch statement in Rust.\nAs you would expect, to write a switch statement in Zig we use the `switch` keyword.\nWe provide the value that we want to \"switch over\" inside a\npair of parentheses. Then, we list the possible combinations (or \"branchs\")\ninside a pair of curly braces.\n\nLet's take a look at the code example below. You can see in this example that,\nI'm creating an enum type called `Role`. We talk more about enums at @sec-enum.\nBut in essence, this `Role` type is listing different types of roles in a fictitious\ncompany, like `SE` for Software Engineer, `DE` for Data Engineer, `PM` for Product Manager,\netc.\n\nNotice that we are using the value from the `role` object in the\nswitch statement, to discover which exact area we need to store in the `area` variable object.\nAlso notice that we are using type inference inside the switch statement, with the dot character,\nas we described at @sec-type-inference.\nThis makes the `zig` compiler infer the correct data type of the values (`PM`, `SE`, etc.) for us.\n\nAlso notice that, we are grouping multiple values in the same branch of the switch statement.\nWe just separate each possible value with a comma. So, for example, if `role` contains either `DE` or `DA`,\nthe `area` variable would contain the value `\"Data & Analytics\"`, instead of `\"Platform\"`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst Role = enum {\n SE, DPE, DE, DA, PM, PO, KS\n};\n\npub fn main() !void {\n var area: []const u8 = undefined;\n const role = Role.SE;\n switch (role) {\n .PM, .SE, .DPE, .PO => {\n area = \"Platform\";\n },\n .DE, .DA => {\n area = \"Data & Analytics\";\n },\n .KS => {\n area = \"Sales\";\n },\n }\n try stdout.print(\"{s}\\n\", .{area});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nPlatform\n```\n\n\n:::\n:::\n\n\nNow, one very important aspect about this switch statement presented\nin the code example above, is that it exhaust all existing possibilities.\nIn other words, all possible values that could be found inside the `order`\nobject are explicitly handled in this switch statement.\n\nSince the `role` object have type `Role`, the only possible values to\nbe found inside this object are `PM`, `SE`, `DPE`, `PO`, `DE`, `DA` and `KS`.\nThere is no other possible value to be stored in this `role` object.\nThis what \"exhaust all existing possibilities\" means. The switch statement covers\nevery possible case.\n\nIn Zig, switch statements must exhaust all existing possibilities. You cannot write\na switch statement, and leave an edge case with no expliciting action to be taken.\nThis is a similar behaviour to switch statements in Rust, which also have to\nhandle all possible cases.\n\nTake a look at the `dump_hex_fallible()` function below as an example. This function\nalso comes from the Zig Standard Library, but this time, it comes from the [`debug.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/debug.zig)[^debug-mod].\nThere are multiple lines in this function, but I omitted them to focus solely on the\nswitch statement found in this function. Notice that this switch statement have four\npossible cases, or four explicit branches. Also, notice that we used an `else` branch\nin this case. Whenever you have multiple possible cases in your switch statement\nwhich you want to apply the same exact action, you can use an `else` branch to do that.\n\n[^debug-mod]: \n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub fn dump_hex_fallible(bytes: []const u8) !void {\n // Many lines ...\n switch (byte) {\n '\\n' => try writer.writeAll(\"␊\"),\n '\\r' => try writer.writeAll(\"␍\"),\n '\\t' => try writer.writeAll(\"␉\"),\n else => try writer.writeByte('.'),\n }\n}\n```\n:::\n\n\nMany users would also use an `else` branch to handle a \"not supported\" case.\nThat is, a case that cannot be properly handled by your code, or, just a case that\nshould not be \"fixed\". So many programmers use an `else` branch to panic (or raise an error) to stop\nthe current execution.\n\nTake the code example below as an example. We can see that, we are handling the cases\nfor the `level` object being either 1, 2, or 3. All other possible cases are not supported by default,\nand, as consequence, we raise an runtime error in these cases, through the `@panic()` built-in function.\n\nAlso notice that, we are assigning the result of the switch statement to a new object called `category`.\nThis is another thing that you can do with switch statements in Zig. If the branchs in this switch\nstatement output some value as result, you can store the result value of the switch statement into\na new object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 1, 2 => \"beginner\",\n 3 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n:::\n\n\n```\nthread 13103 panic: Not supported level!\nt.zig:9:13: 0x1033c58 in main (switch2)\n @panic(\"Not supported level!\");\n ^\n```\n\nFurthermore, you can also use ranges of values in switch statements.\nThat is, you can create a branch in your switch statement that is used\nwhenever the input value is contained in a range. These range\nexpressions are created with the operator `...`. Is important\nto emphasize that the ranges created by this operator are\ninclusive on both ends.\n\nFor example, I could easily change the code example above to support all\nlevels between 0 and 100. Like this:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 0...25 => \"beginner\",\n 26...75 => \"intermediary\",\n 76...100 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nbeginner\n```\n\n\n:::\n:::\n\n\nThis is neat, and it works with character ranges too. That is, I could\nsimply write `'a'...'z'`, to match any character value that is a\nlowercase letter, and it would work fine.\n\n\n\n### The `defer` keyword {#sec-defer}\n\nWith the `defer` keyword you can execute expressions at the end of the current scope.\nTake the `foo()` function below as an example. When we execute this function, the expression\nthat prints the message \"Exiting function ...\" get's executed only at\nthe end of the function scope.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nfn foo() !void {\n defer std.debug.print(\n \"Exiting function ...\\n\", .{}\n );\n try stdout.print(\"Adding some numbers ...\\n\", .{});\n const x = 2 + 2; _ = x;\n try stdout.print(\"Multiplying ...\\n\", .{});\n const y = 2 * 8; _ = y;\n}\n\npub fn main() !void {\n try foo();\n}\n```\n:::\n\n\n```\nAdding some numbers ...\nMultiplying ...\nExiting function ...\n```\n\nIt doesn't matter how the function exits (i.e. because\nof an error, or, because of an return statement, or whatever),\njust remember, this expression get's executed when the function exits.\n\n\n\n\n### For loops\n\nA loop allows you to execute the same lines of code multiple times,\nthus, creating a \"repetition space\" in the execution flow of your program.\nLoops are particularly useful when we want to replicate the same function\n(or the same set of commands) over several different inputs.\n\nThere are different types of loops available in Zig. But the most\nessential of them all is probably the *for loop*. A for loop is\nused to apply the same piece of code over the elements of a slice or an array.\n\nFor loops in Zig have a slightly different syntax that you are\nprobably used to see in other languages. You start with the `for` keyword, then, you\nlist the items that you want to iterate\nover inside a pair of parentheses. Then, inside of a pair of pipes (`|`)\nyou should declare an identifier that will serve as your iterator, or,\nthe \"repetition index of the loop\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (items) |value| {\n // code to execute\n}\n```\n:::\n\n\nInstead of using a `(value in items)` syntax,\nin Zig, for loops use the syntax `(items) |value|`. In the example\nbelow, you can see that we are looping through the items\nof the array stored at the object `name`, and printing to the\nconsole the decimal representation of each character in this array.\n\nIf we wanted, we could also iterate through a slice (or a portion) of\nthe array, instead of iterating through the entire array stored in the `name` object.\nJust use a range selector to select the section you want. For example,\nI could provide the expression `name[0..3]` to the for loop, to iterate\njust through the first 3 elements in the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst name = [_]u8{'P','e','d','r','o'};\nfor (name) |char| {\n try stdout.print(\"{d} | \", .{char});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n80 | 101 | 100 | 114 | 111 | \n```\n\n\n:::\n:::\n\n\nIn the above example we are using the value itself of each\nelement in the array as our iterator. But there are many situations where\nwe need to use an index instead of the actual values of the items.\n\nYou can do that by providing a second set of items to iterate over.\nMore precisely, you provide the range selector `0..` to the for loop. So,\nyes, you can use two different iterators at the same time in a for\nloop in Zig.\n\nBut remember from @sec-assignments that, every object\nyou create in Zig must be used in some way. So if you declare two iterators\nin your for loop, you must use both iterators inside the for loop body.\nBut if you want to use just the index iterator, and not use the \"value iterator\",\nthen, you can discard the value iterator by maching the\nvalue items to the underscore character, like in the example below:\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (name, 0..) |_, i| {\n try stdout.print(\"{d} | \", .{i});\n}\n```\n:::\n\n\n```\n0 | 1 | 2 | 3 | 4 |\n```\n\n\n### While loops\n\nA while loop is created from the `while` keyword. A `for` loop\niterates through the items of an array, but a `while` loop\nwill loop continuously, and infinitely, until a logical test\n(specified by you) becomes false.\n\nYou start with the `while` keyword, then, you define a logical\nexpression inside a pair of parentheses, and the body of the\nloop is provided inside a pair of curly braces, like in the example below:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: u8 = 1;\nwhile (i < 5) {\n try stdout.print(\"{d} | \", .{i});\n i += 1;\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 2 | 3 | 4 | \n```\n\n\n:::\n:::\n\n\n\n\n### Using `break` and `continue`\n\nIn Zig, you can explicitly stop the execution of a loop, or, jump to the next iteration of the loop, using\nthe keywords `break` and `continue`, respectively. The `while` loop present in the example below, is\nat first sight, an infinite loop. Because the logical value inside the parenthese will always be equal to `true`.\nWhat makes this `while` loop stop when the `i` object reaches the count\n10? Is the `break` keyword!\n\nInside the while loop, we have an if statement that is constantly checking if the `i` variable\nis equal to 10. Since we are increasing the value of this `i` variable at each iteration of the\nwhile loop. At some point, this `i` variable will be equal to 10, and when it does, the if statement\nwill execute the `break` expression, and, as a result, the execution of the while loop is stopped.\n\nNotice the `expect()` function from the Zig standard library after the while loop.\nThis `expect()` function is an \"assert\" type of function.\nThis function checks if the logical test provided is equal to true. If this logical test is false,\nthe function raises an assertion error. But it is equal to true, then, the function will do nothing.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: usize = 0;\nwhile (true) {\n if (i == 10) {\n break;\n }\n i += 1;\n}\ntry std.testing.expect(i == 10);\ntry stdout.print(\"Everything worked!\", .{});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nEverything worked!\n```\n\n\n:::\n:::\n\n\nSince this code example was executed succesfully by the `zig` compiler,\nwithout raising any errors, then, we known that, after the execution of while loop,\nthe `i` variable is equal to 10. Because if it wasn't equal to 10, then, an error would\nbe raised by `expect()`.\n\nNow, in the next example, we have an use case for\nthe `continue` keyword. The if statement is constantly\nchecking if the current index is a multiple of 2. If\nit is, then we jump to the next iteration of the loop\ndirectly. But it the current index is not a multiple of 2,\nthen, the loop will simply print this index to the console.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [_]u8{1,2,3,4,5,6};\nfor (ns) |i| {\n if ((i % 2) == 0) {\n continue;\n }\n try stdout.print(\"{d} | \", .{i});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 3 | 5 | \n```\n\n\n:::\n:::\n\n\n\n\n## Function parameters are immutable {#sec-fun-pars}\n\nWe have already discussed a lot of the syntax behind function declarations at @sec-root-file and @sec-main-file.\nBut I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig.\nIn summary, function parameters are immutable in Zig.\n\nTake the code example below, where we declare a simple function that just tries to add\nsome amount to the input integer, and returns the result back. But if you look closely\nat the body of this `add2()` function, you will notice that we try\nto save the result back into the `x` function argument.\n\nIn other words, this function not only use the value that it received through the function argument\n`x`, but it also tries to change the value of this function argument, by assigning the addition result\ninto `x`. However, function arguments in Zig are immutable. You cannot change their values, or, you\ncannot assign values to them inside the body's function.\n\nThis is the reason why, the code example below do not compile successfully. If you try to compile\nthis code example, you get a compile error warning you that you are trying to change the value of a\nimmutable (i.e. constant) object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: u32) u32 {\n x = x + 2;\n return x;\n}\n\npub fn main() !void {\n const y = add2(4);\n std.debug.print(\"{d}\\n\", .{y});\n}\n```\n:::\n\n\n```\nt.zig:3:5: error: cannot assign to constant\n x = x + 2;\n ^\n```\n\n\nIf a function argument receives as input a object whose data type is\nany of the primitive types that we have listed at @sec-primitive-data-types\nthis object is always passed by value to the function. In other words, this object\nis copied to the function stack frame.\n\nHowever, if the input object have a more complex data type, for example, it might\nbe a struct instance, or an array, or a union, etc., in cases like that, the `zig` compiler\nwill take the liberty of deciding for you which strategy is best. The `zig` compiler will\npass your object to the function either by value, or by reference. The compiler will always\nchoose the strategy that is faster for you.\nThis optimization that you get for free is possible only because function arguments are\nimmutable in Zig.\n\nTo overcome this barrier, we need to take the lead, and explicitly choose to pass the\nobject by reference. That is, instead of depending on the `zig` compiler to decide for us, we need\nto explicitly mark the function argument as a pointer. This way, we are telling the compiler\nthat this function argument will be passed by reference to the function.\n\nBy making it a pointer, we can finally use and alter directly the value of this function argument inside\nthe body of the `add2()` function. You can see that the code example below compiles successfully.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: *u32) void {\n const d: u32 = 2;\n x.* = x.* + d;\n}\n\npub fn main() !void {\n var x: u32 = 4;\n add2(&x);\n std.debug.print(\"Result: {d}\\n\", .{x});\n}\n```\n:::\n\n\n```\nResult: 6\n```\n\n\n\n## Structs and OOP {#sec-structs-and-oop}\n\nZig is a language more closely related to C (which is a procedural language),\nthan it is to C++ or Java (which are object-oriented languages). Because of that, you do not\nhave advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or\nclass inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\n\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C.\nYou give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can\nalso register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object\nthat you create with this new type, will always have these methods available and associated with them.\n\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object\nof this particular class, and you also have a destructor method (or a destructor function) that\nis the function responsible for destroying every object of this class.\n\nIn Zig, we normally declare the constructor and the destructor methods\nof our structs, by declaring an `init()` and a `deinit()` methods inside the struct.\nThis is just a naming convention that you will find across the entire Zig standard library.\nSo, in Zig, the `init()` method of a struct is normally the constructor method of the class represented by this struct.\nWhile the `deinit()` method is the method used for destroying an existing instance of that struct.\n\nThe `init()` and `deinit()` methods are both used extensively in Zig code, and you will see both of\nthem being used when we talk about allocators at @sec-allocators.\nBut, as another example, let's build a simple `User` struct to represent an user of some sort of system.\nIf you look at the `User` struct below, you can see the `struct` keyword, and inside of a\npair of curly braces, we write the struct's body.\n\nNotice the data members of this struct, `id`, `name` and `email`. Every data member have it's\ntype explicitly annotated, with the colon character (`:`) syntax that we described earlier at @sec-root-file.\nBut also notice that every line in the struct body that describes a data member, ends with a comma character (`,`).\nSo every time you declare a data member in your Zig code, always end the line with a comma character, instead\nof ending it with the traditional semicolon character (`;`).\n\nNext, also notice in this example, that we registrated an `init()` function as a method\nof this `User` struct. This `init()` method is the constructor method that you use to instantiate\nevery new `User` object. That is why this `init()` function return an `User` object as result.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\npedro\n```\n\n\n:::\n:::\n\n\nThe `pub` keyword plays an important role in struct declarations, and OOP in Zig.\nEvery method that you declare in your struct that is marked with the keyword `pub`,\nbecomes a public method of this particular struct.\n\nSo every method that you create in your struct, is, at first, a private method\nof that struct. Meaning that, this method can only be called from within this\nstruct. But, if you mark this method as public, with the keyword `pub`, then,\nyou can call the method directly from the `User` object you have\nin your code.\n\nIn other words, the functions marked by the keyword `pub`\nare members of the public API of that struct.\nFor example, if I did not marked the `print_name()` method as public,\nthen, I could not execute the line `u.print_name()`. Because I would\nnot be authorized to call this method directly in my code.\n\n\n\n### Anonymous struct literals {#sec-anonymous-struct-literals}\n\nYou can declare a struct object as a literal value. When we do that, we normally specify the\ndata type of this struct literal by writing it's data type just before the opening curly braces.\nFor example, I could write a struct literal of type `User` that we defined in the previous section like\nthis:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n```\n:::\n\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a\nstruct literal, but not especify explicitly the type of this particular struct.\nAn anonymous struct is written by using the syntax `.{}`. So, we essentially\nreplaced the explicit type of the struct literal with a dot character (`.`).\n\nAs we described at @sec-type-inference, when you put a dot before a struct literal,\nthe type of this struct literal is automatically inferred by the `zig` compiler.\nIn essence, the `zig` compiler will look for some hint of what is the type of that struct.\nIt can be the type annotation of an function argument,\nor the return type annotation of the function that you are using, or the type annotation\nof a variable.\nIf the compiler do find such type annotation, then, it will use this\ntype in your literal struct. \n\nAnonymous structs are very commom to use in function arguments in Zig.\nOne example that you have seen already constantly, is the `print()`\nfunction from the `stdout` object.\nThis function takes two arguments.\nThe first argument, is a template string, which should\ncontain string format specifiers in it, which tells how the values provided\nin the second argument should be printed into the message.\n\nWhile the second argument is a struct literal that lists the values\nto be printed into the template message specified in the first argument.\nYou normally want to use an anonymous struct literal here, so that, the\n`zig` compiler do the job of specifying the type of this particular\nanonymous struct for you.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHello, world!\n```\n\n\n:::\n:::\n\n\n\n\n### Struct declarations must be constant\n\nTypes in Zig must be `const` or `comptime` (we are going to talk more about comptime at @sec-comptime).\nWhat this means is that you cannot create a new data type, and mark it as variable with the `var` keyword.\nSo struct declarations are always constant. You cannot declare a new struct using the `var` keyword.\nIt must be `const`.\n\nIn the `Vec3` example below, this declaration is allowed because I'm using the `const` keyword\nto declare this new data type.\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n```\n:::\n\n\n\n### The `self` method argument {#sec-self-arg}\n\nIn every language that have OOP, when we declare a method of some class or struct, we\nusually declare this method as a function that have a `self` argument.\nThis `self` argument is the reference to the object itself from which the method\nis being called from.\n\nIs not mandatory to use this `self` argument. But why would you not use this `self` argument?\nThere is no reason to not use it. Because the only way to get access to the data stored in the\ndata members of your struct is to access them through this `self` argument.\nIf you don't need to use the data in the data members of your struct inside your method, then, you very likely don't need\na method, you can just simply declare this logic as a simple function, outside of your\nstruct declaration.\n\n\nTake the `Vec3` struct below. Inside this `Vec3` struct we declared a method named `distance()`.\nThis method calculates the distance between two `Vec3` objects, by following the distance\nformula in euclidean space. Notice that this `distance()` method takes two `Vec3` objects\nas input, `self` and `other`.\n\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n```\n:::\n\n\n\nThe `self` argument corresponds to the `Vec3` object from which this `distance()` method\nis being called from. While the `other` is a separate `Vec3` object that is given as input\nto this method. In the example below, the `self` argument corresponds to the object\n`v1`, because the `distance()` method is being called from the `v1` object,\nwhile the `other` argument corresponds to the object `v2`.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n```\n:::\n\n\n```\nDistance: 3.3970575502926055\n```\n\n\n\n### About the struct state\n\nSometimes you don't need to care about the state of your struct object. Sometimes, you just need\nto instantiate and use the objects, without altering their state. You can notice that when you have methods\ninside your struct declaration that might use the values that are present in the data members, but they\ndo not alter the values in the data members of the struct in anyway.\n\nThe `Vec3` struct that was presented at @sec-self-arg is an example of that.\nThis struct have a single method named `distance()`, and this method do use the values\npresent in all three data members of the struct (`x`, `y` and `z`). But at the same time,\nthis method do not change the values of these data members in any point.\n\nAs a result of that, when we create `Vec3` objects we usually create them as\nconstant objects, like the `v1` and `v2` objects presented at @sec-self-arg.\nWe can create them as variable objects with the `var` keyword,\nif we want to. But because the methods of this `Vec3` struct do not change\nthe state of the objects in any point, is unnecessary to mark them\nas variable objects.\n\nBut why? Why am I talkin about this here? Is because the `self` argument\nin the methods is affected depending on whether the\nmethods present in a struct change or not the state of the object itself.\nMore specifically, when you have a method in a struct that changes the state\nof the object (i.e. change the value of a data member), the `self` argument\nin this method must be annotated in a different manner.\n\nAs I described at @sec-self-arg, the `self` argument in methods of\na struct is the argument that receives as input the object from which the method\nwas called from. We usually annotate this argument in the methods by writing `self`,\nfollowed by the colon character (`:`), and the data type of the struct to which\nthe method belongs to (e.g. `User`, `Vec3`, etc.).\n\nIf we take the `Vec3` struct that we defined in the previous section as an example,\nwe can see in the `distance()` method that this `self` argument is annotated as\n`self: Vec3`. Because the state of the `Vec3` object is never altered by this\nmethod.\n\nBut what if we do have a method that alters the state of the object, by altering the\nvalues of it's data members. How should we annotate `self` in this instance? The answer is:\n\"we should annotate `self` as a pointer of `x`, instead of just `x`\".\nIn other words, you should annotate `self` as `self: *x`, instead of annotating it\nas `self: x`.\n\nIf we create a new method inside the `Vec3` object that, for example, expands the\nvector by multiplying it's coordinates by a factor o two, then, we need to follow\nthis rule specified in the previous paragraph. The code example below demonstrates\nthis idea:\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n```\n:::\n\n\nNotice in the code example above that we have added a new method\nto our `Vec3` struct named `double()`. This method essentially doubles the\ncoordinate values of our vector object. Also notice that, in the\ncase of the `double()` method, we annotated the `self` argument as `*Vec3`,\nindicating that this argument receives a pointer (or a reference, if you prefer to call it this way)\nto a `Vec3` object as input.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n```\n:::\n\n\n```\nDoubled: 8.4\n```\n\n\n\nNow, if you change the `self` argument in this `double()` method to `self: Vec3`, like in the\n`distance()` method, you will get the compiler error exposed below as result. Notice that this\nerror message is indicating a line from the `double()` method body,\nindicating that you cannot alter the value of the `x` data member.\n\n```zig\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\n```\n\nThis error message indicates that the `x` data member belongs to a constant object,\nand, because of that, it cannot be changed. Ultimately, this error message\nis telling us that the `self` argument is constant.\n\n```\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\n```\n\nIf you take some time, and think hard about this error message, you will understand it.\nYou already have the tools to understand why we are getting this error message.\nWe have talked about it already at @sec-fun-pars.\n\nSo remember, every function argument is immutable in Zig.\nIt does not matter if we marked the `v3` object as a variable object.\nBecause we are trying to alter `self` directly, which is a function argument,\nand, every function argument is immutable by default.\nWe overcome this barrier, by explicitly marking the `self` argument as a pointer.\n\n\n::: {.callout-note}\nIf a method of your `x` struct alters the state of the object, by\nchanging the value of any data member, then, remember to use `self: *x`,\ninstead of `self: x` in the function signature of this method.\n:::\n\n\nYou could also interpret the content discussed in this section as:\n\"if you need to alter the state of your `x` struct object in one of it's methods,\nyou must explicitly pass the `x` struct object by reference to the `self` argument of this method\".\n\n\n\n## Type inference {#sec-type-inference}\n\nZig is kind of a strongly typed language. I say \"kind of\" because there are situations\nwhere you don't have to explicitly write the type of every single object in your source code,\nas you would expect from a traditional strongly typed language, such as C and C++.\n\nIn some situations, the `zig` compiler can use type inference to solves the data types for you, easing some of\nthe burden that you carry as a developer.\nThe most commom way this happens is through function arguments that receives struct objects\nas input.\n\nIn general, type inference in Zig is done by using the dot character (`.`).\nEverytime you see a dot character written before a struct literal, or before a enum value, or something like that,\nyou know that this dot character is playing a special party in this place. More specifically, it is\ntelling the `zig` compiler something on the lines of: \"Hey! Can you infer the type of this\nvalue for me? Please!\". In other words, this dot character is playing a role similar to the `auto` keyword in C++.\n\nI give you some examples of this at @sec-anonymous-struct-literals, where we present anonymous struct literals.\nAnonymous struct literals are, essentially, struct literals that use type inference to\ninfer the exact type of this particular struct literal.\nThis type inference is done by looking for some minimal hint of the correct data type to be used.\nYou could say that the `zig` compiler looks for any neighbouring type annotation that might tell him\nwhat would be the correct type.\n\nAnother commom place where we use type inference in Zig is at switch statements (which we talk about at @sec-switch).\nSo I also gave some other examples of type inference at @sec-switch, where we were inferring the data types of enum values listed inside\nof switch statements (e.g. `.DE`).\nBut as another example, take a look at this `fence()` function reproduced below,\nwhich comes from the [`atomic.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/atomic.zig)[^fence-fn]\nof the Zig Standard Library.\n\n[^fence-fn]: .\n\nThere are a lot of things in this function that we haven't talked about yet, such as:\nwhat `comptime` means? `inline`? `extern`? What is this star symbol before `Self`?\nLet's just ignore all of these things, and focus solely on the switch statement\nthat is inside this function.\n\nWe can see that this switch statement uses the `order` object as input. This `order`\nobject is one of the inputs of this `fence()` function, and we can see in the type annotation,\nthat this object is of type `AtomicOrder`. We can also see a bunch of values inside the\nswitch statements that begins with a dot character, such as `.release` and `.acquire`.\n\nBecause these weird values contain a dot character before them, we are asking the `zig`\ncompiler to infer the types of these values inside the switch statement. Then, the `zig`\ncompiler is looking into the current context where these values are being used, and it is\ntrying to infer the types of these values.\n\nSince they are being used inside a switch statement, the `zig` compiler looks into the type\nof the input object given to the switch statement, which is the `order` object in this case.\nBecause this object have type `AtomicOrder`, the `zig` compiler infers that these values\nare data members from this type `AtomicOrder`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub inline fn fence(self: *Self, comptime order: AtomicOrder) void {\n // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.\n if (builtin.sanitize_thread) {\n const tsan = struct {\n extern \"c\" fn __tsan_acquire(addr: *anyopaque) void;\n extern \"c\" fn __tsan_release(addr: *anyopaque) void;\n };\n\n const addr: *anyopaque = self;\n return switch (order) {\n .unordered, .monotonic => @compileError(@tagName(order) ++ \" only applies to atomic loads and stores\"),\n .acquire => tsan.__tsan_acquire(addr),\n .release => tsan.__tsan_release(addr),\n .acq_rel, .seq_cst => {\n tsan.__tsan_acquire(addr);\n tsan.__tsan_release(addr);\n },\n };\n }\n\n return @fence(order);\n}\n```\n:::\n\n\nThis is how basic type inference is done in Zig. If we didn't use the dot character before\nthe values inside this switch statement, then, we would be forced to write explicitly\nthe data types of these values. For example, instead of writing `.release` we would have to\nwrite `AtomicOrder.release`. We would have to do this for every single value\nin this switch statement, and this is a lot of work. That is why type inference\nis commonly used on switch statements in Zig.\n\n\n\n## Modules\n\nWe already talked about what modules are, and also, how to import other modules into\nyou current module through *import statements*, so that you can use functionality from these other modules in\nyour current module.\nBut in this section, I just want to make it clear that modules are actually structs in Zig.\n\nIn other words, every Zig module (i.e. a `.zig` file) that you write in your project\nis internally stored as a struct object.\nTake the line exposed below as an example. In this line we are importing the\nZig Standard Library into our current module.\n\n```zig\nconst std = @import(\"std\");\n```\n\nWhen we want to access the functions and objects from the standard library, we\nare basically accessing the data members of the struct stored in the `std`\nobject. That is why we use the same syntax that we use in normal structs, with the dot operator (`.`)\nto access the data members and methods of the struct.\n\nWhen this \"import statement\" get's executed, the result of this expression is a struct\nobject that contains the Zig Standard Library modules, global variables, functions, etc.\nAnd this struct object get's saved (or stored) inside the constant object named `std`.\n\n\nTake the [`thread_pool.zig` module from the project `zap`](https://github.com/kprotty/zap/blob/blog/src/thread_pool.zig)[^thread]\nas an example. This module is written as if it was\na big struct. That is why we have a top-level and public `init()` method\nwritten in this module. The idea is that all top-level functions written in this\nmodule are methods from the struct, and all top-level objects and struct declarations\nare data members of this struct. The module is the struct itself.\n\n[^thread]: \n\n\nSo you would import and use this module by doing something like this:\n\n```zig\nconst std = @import(\"std\");\nconst ThreadPool = @import(\"thread_pool.zig\");\nconst num_cpus = std.Thread.getCpuCount()\n catch @panic(\"failed to get cpu core count\");\nconst num_threads = std.math.cast(u16, num_cpus)\n catch std.math.maxInt(u16);\nconst pool = ThreadPool.init(\n .{ .max_threads = num_threads }\n);\n```\n", - "supporting": [ - "03-structs_files" - ], + "markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n# Structs, Modules and Control Flow\n\nI introduced a lot of the Zig's syntax to you in the last chapter,\nspecially at @sec-root-file and @sec-main-file.\nBut we still need to discuss about some other very important\nelements of the language that you will use constantly on your day-to-day\nroutine.\n\nWe begin this chapter by talking about the different keywords and structures\nin Zig related to control flow (e.g. loops and if statements).\nThen, we talk about structs and how they can be used to do some\nbasic Object-Oriented (OOP) patterns in Zig. We also talk about\ntype inference, which help us to write less code and achieve the same results.\nFinally, we end this chapter by discussing modules, and how they relate\nto structs.\n\n\n\n## Control flow {#sec-zig-control-flow}\n\nSometimes, you need to make decisions in your program. Maybe you need to decide\nwether to execute or not a specific piece of code. Or maybe,\nyou need to apply the same operation over a sequence of values. These kinds of tasks,\ninvolve using structures that are capable of changing the \"control flow\" of our program.\n\nIn computer science, the term \"control flow\" usually refers to the order in which expressions (or commands)\nare evaluated in a given language or program. But this term is also used to refer\nto structures that are capable of changing this \"evaluation order\" of the commands\nexecuted by a given language/program.\n\nThese structures are better known\nby a set of terms, such as: loops, if/else statements, switch statements, among others. So,\nloops and if/else statements are examples of structures that can change the \"control\nflow\" of our program. The keywords `continue` and `break` are also examples of symbols\nthat can change the order of evaluation, since they can move our program to the next iteration\nof a loop, or make the loop stop completely.\n\n\n### If/else statements\n\nAn if/else statement performs an \"conditional flow operation\".\nA conditional flow control (or choice control) allows you to execute\nor ignore a certain block of commands based on a logical condition.\nMany programmers and computer science professionals also use\nthe term \"branching\" in this case.\nIn essence, we use if/else statements to use the result of a logical test\nto decide whether or not to execute a given block of commands.\n\nIn Zig, we write if/else statements by using the keywords `if` and `else`.\nWe start with the `if` keyword followed by a logical test inside a pair\nof parentheses, and then, a pair of curly braces with contains the lines\nof code to be executed in case the logical test returns the value `true`.\n\nAfter that, you can optionally add an `else` statement. Just add the `else`\nkeyword followed by a pair of curly braces, with the lines of code\nto executed in case the logical test defined in the `if`\nreturns `false`.\n\nIn the example below, we are testing if the object `x` contains a number\nthat is greater than 10. Judging by the output printed to the console,\nwe know that this logical test returned `false`. Because the output\nin the console is compatible with the line of code present in the\n`else` branch of the if/else statement.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst x = 5;\nif (x > 10) {\n try stdout.print(\n \"x > 10!\\n\", .{}\n );\n} else {\n try stdout.print(\n \"x <= 10!\\n\", .{}\n );\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nx <= 10!\n```\n\n\n:::\n:::\n\n\n\n\n### Swith statements {#sec-switch}\n\nSwitch statements are also available in Zig.\nA switch statement in Zig have a similar syntax to a switch statement in Rust.\nAs you would expect, to write a switch statement in Zig we use the `switch` keyword.\nWe provide the value that we want to \"switch over\" inside a\npair of parentheses. Then, we list the possible combinations (or \"branchs\")\ninside a pair of curly braces.\n\nLet's take a look at the code example below. You can see in this example that,\nI'm creating an enum type called `Role`. We talk more about enums at @sec-enum.\nBut in essence, this `Role` type is listing different types of roles in a fictitious\ncompany, like `SE` for Software Engineer, `DE` for Data Engineer, `PM` for Product Manager,\netc.\n\nNotice that we are using the value from the `role` object in the\nswitch statement, to discover which exact area we need to store in the `area` variable object.\nAlso notice that we are using type inference inside the switch statement, with the dot character,\nas we described at @sec-type-inference.\nThis makes the `zig` compiler infer the correct data type of the values (`PM`, `SE`, etc.) for us.\n\nAlso notice that, we are grouping multiple values in the same branch of the switch statement.\nWe just separate each possible value with a comma. So, for example, if `role` contains either `DE` or `DA`,\nthe `area` variable would contain the value `\"Data & Analytics\"`, instead of `\"Platform\"`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst Role = enum {\n SE, DPE, DE, DA, PM, PO, KS\n};\n\npub fn main() !void {\n var area: []const u8 = undefined;\n const role = Role.SE;\n switch (role) {\n .PM, .SE, .DPE, .PO => {\n area = \"Platform\";\n },\n .DE, .DA => {\n area = \"Data & Analytics\";\n },\n .KS => {\n area = \"Sales\";\n },\n }\n try stdout.print(\"{s}\\n\", .{area});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nPlatform\n```\n\n\n:::\n:::\n\n\nNow, one very important aspect about this switch statement presented\nin the code example above, is that it exhaust all existing possibilities.\nIn other words, all possible values that could be found inside the `order`\nobject are explicitly handled in this switch statement.\n\nSince the `role` object have type `Role`, the only possible values to\nbe found inside this object are `PM`, `SE`, `DPE`, `PO`, `DE`, `DA` and `KS`.\nThere is no other possible value to be stored in this `role` object.\nThis what \"exhaust all existing possibilities\" means. The switch statement covers\nevery possible case.\n\nIn Zig, switch statements must exhaust all existing possibilities. You cannot write\na switch statement, and leave an edge case with no expliciting action to be taken.\nThis is a similar behaviour to switch statements in Rust, which also have to\nhandle all possible cases.\n\nTake a look at the `dump_hex_fallible()` function below as an example. This function\nalso comes from the Zig Standard Library, but this time, it comes from the [`debug.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/debug.zig)[^debug-mod].\nThere are multiple lines in this function, but I omitted them to focus solely on the\nswitch statement found in this function. Notice that this switch statement have four\npossible cases, or four explicit branches. Also, notice that we used an `else` branch\nin this case. Whenever you have multiple possible cases in your switch statement\nwhich you want to apply the same exact action, you can use an `else` branch to do that.\n\n[^debug-mod]: \n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub fn dump_hex_fallible(bytes: []const u8) !void {\n // Many lines ...\n switch (byte) {\n '\\n' => try writer.writeAll(\"␊\"),\n '\\r' => try writer.writeAll(\"␍\"),\n '\\t' => try writer.writeAll(\"␉\"),\n else => try writer.writeByte('.'),\n }\n}\n```\n:::\n\n\nMany users would also use an `else` branch to handle a \"not supported\" case.\nThat is, a case that cannot be properly handled by your code, or, just a case that\nshould not be \"fixed\". So many programmers use an `else` branch to panic (or raise an error) to stop\nthe current execution.\n\nTake the code example below as an example. We can see that, we are handling the cases\nfor the `level` object being either 1, 2, or 3. All other possible cases are not supported by default,\nand, as consequence, we raise an runtime error in these cases, through the `@panic()` built-in function.\n\nAlso notice that, we are assigning the result of the switch statement to a new object called `category`.\nThis is another thing that you can do with switch statements in Zig. If the branchs in this switch\nstatement output some value as result, you can store the result value of the switch statement into\na new object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 1, 2 => \"beginner\",\n 3 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n:::\n\n\n```\nthread 13103 panic: Not supported level!\nt.zig:9:13: 0x1033c58 in main (switch2)\n @panic(\"Not supported level!\");\n ^\n```\n\nFurthermore, you can also use ranges of values in switch statements.\nThat is, you can create a branch in your switch statement that is used\nwhenever the input value is contained in a range. These range\nexpressions are created with the operator `...`. Is important\nto emphasize that the ranges created by this operator are\ninclusive on both ends.\n\nFor example, I could easily change the code example above to support all\nlevels between 0 and 100. Like this:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst level: u8 = 4;\nconst category = switch (level) {\n 0...25 => \"beginner\",\n 26...75 => \"intermediary\",\n 76...100 => \"professional\",\n else => {\n @panic(\"Not supported level!\");\n },\n};\ntry stdout.print(\"{s}\\n\", .{category});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nbeginner\n```\n\n\n:::\n:::\n\n\nThis is neat, and it works with character ranges too. That is, I could\nsimply write `'a'...'z'`, to match any character value that is a\nlowercase letter, and it would work fine.\n\n\n\n### The `defer` keyword {#sec-defer}\n\nWith the `defer` keyword you can execute expressions at the end of the current scope.\nTake the `foo()` function below as an example. When we execute this function, the expression\nthat prints the message \"Exiting function ...\" get's executed only at\nthe end of the function scope.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nfn foo() !void {\n defer std.debug.print(\n \"Exiting function ...\\n\", .{}\n );\n try stdout.print(\"Adding some numbers ...\\n\", .{});\n const x = 2 + 2; _ = x;\n try stdout.print(\"Multiplying ...\\n\", .{});\n const y = 2 * 8; _ = y;\n}\n\npub fn main() !void {\n try foo();\n}\n```\n:::\n\n\n```\nAdding some numbers ...\nMultiplying ...\nExiting function ...\n```\n\nIt doesn't matter how the function exits (i.e. because\nof an error, or, because of an return statement, or whatever),\njust remember, this expression get's executed when the function exits.\n\n\n\n\n### For loops\n\nA loop allows you to execute the same lines of code multiple times,\nthus, creating a \"repetition space\" in the execution flow of your program.\nLoops are particularly useful when we want to replicate the same function\n(or the same set of commands) over several different inputs.\n\nThere are different types of loops available in Zig. But the most\nessential of them all is probably the *for loop*. A for loop is\nused to apply the same piece of code over the elements of a slice or an array.\n\nFor loops in Zig have a slightly different syntax that you are\nprobably used to see in other languages. You start with the `for` keyword, then, you\nlist the items that you want to iterate\nover inside a pair of parentheses. Then, inside of a pair of pipes (`|`)\nyou should declare an identifier that will serve as your iterator, or,\nthe \"repetition index of the loop\".\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (items) |value| {\n // code to execute\n}\n```\n:::\n\n\nInstead of using a `(value in items)` syntax,\nin Zig, for loops use the syntax `(items) |value|`. In the example\nbelow, you can see that we are looping through the items\nof the array stored at the object `name`, and printing to the\nconsole the decimal representation of each character in this array.\n\nIf we wanted, we could also iterate through a slice (or a portion) of\nthe array, instead of iterating through the entire array stored in the `name` object.\nJust use a range selector to select the section you want. For example,\nI could provide the expression `name[0..3]` to the for loop, to iterate\njust through the first 3 elements in the array.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst name = [_]u8{'P','e','d','r','o'};\nfor (name) |char| {\n try stdout.print(\"{d} | \", .{char});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n80 | 101 | 100 | 114 | 111 | \n```\n\n\n:::\n:::\n\n\nIn the above example we are using the value itself of each\nelement in the array as our iterator. But there are many situations where\nwe need to use an index instead of the actual values of the items.\n\nYou can do that by providing a second set of items to iterate over.\nMore precisely, you provide the range selector `0..` to the for loop. So,\nyes, you can use two different iterators at the same time in a for\nloop in Zig.\n\nBut remember from @sec-assignments that, every object\nyou create in Zig must be used in some way. So if you declare two iterators\nin your for loop, you must use both iterators inside the for loop body.\nBut if you want to use just the index iterator, and not use the \"value iterator\",\nthen, you can discard the value iterator by maching the\nvalue items to the underscore character, like in the example below:\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nfor (name, 0..) |_, i| {\n try stdout.print(\"{d} | \", .{i});\n}\n```\n:::\n\n\n```\n0 | 1 | 2 | 3 | 4 |\n```\n\n\n### While loops\n\nA while loop is created from the `while` keyword. A `for` loop\niterates through the items of an array, but a `while` loop\nwill loop continuously, and infinitely, until a logical test\n(specified by you) becomes false.\n\nYou start with the `while` keyword, then, you define a logical\nexpression inside a pair of parentheses, and the body of the\nloop is provided inside a pair of curly braces, like in the example below:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: u8 = 1;\nwhile (i < 5) {\n try stdout.print(\"{d} | \", .{i});\n i += 1;\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 2 | 3 | 4 | \n```\n\n\n:::\n:::\n\n\n\n\n### Using `break` and `continue`\n\nIn Zig, you can explicitly stop the execution of a loop, or, jump to the next iteration of the loop, using\nthe keywords `break` and `continue`, respectively. The `while` loop present in the example below, is\nat first sight, an infinite loop. Because the logical value inside the parenthese will always be equal to `true`.\nWhat makes this `while` loop stop when the `i` object reaches the count\n10? Is the `break` keyword!\n\nInside the while loop, we have an if statement that is constantly checking if the `i` variable\nis equal to 10. Since we are increasing the value of this `i` variable at each iteration of the\nwhile loop. At some point, this `i` variable will be equal to 10, and when it does, the if statement\nwill execute the `break` expression, and, as a result, the execution of the while loop is stopped.\n\nNotice the `expect()` function from the Zig standard library after the while loop.\nThis `expect()` function is an \"assert\" type of function.\nThis function checks if the logical test provided is equal to true. If this logical test is false,\nthe function raises an assertion error. But it is equal to true, then, the function will do nothing.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar i: usize = 0;\nwhile (true) {\n if (i == 10) {\n break;\n }\n i += 1;\n}\ntry std.testing.expect(i == 10);\ntry stdout.print(\"Everything worked!\", .{});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nEverything worked!\n```\n\n\n:::\n:::\n\n\nSince this code example was executed succesfully by the `zig` compiler,\nwithout raising any errors, then, we known that, after the execution of while loop,\nthe `i` variable is equal to 10. Because if it wasn't equal to 10, then, an error would\nbe raised by `expect()`.\n\nNow, in the next example, we have an use case for\nthe `continue` keyword. The if statement is constantly\nchecking if the current index is a multiple of 2. If\nit is, then we jump to the next iteration of the loop\ndirectly. But it the current index is not a multiple of 2,\nthen, the loop will simply print this index to the console.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst ns = [_]u8{1,2,3,4,5,6};\nfor (ns) |i| {\n if ((i % 2) == 0) {\n continue;\n }\n try stdout.print(\"{d} | \", .{i});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1 | 3 | 5 | \n```\n\n\n:::\n:::\n\n\n\n\n## Function parameters are immutable {#sec-fun-pars}\n\nWe have already discussed a lot of the syntax behind function declarations at @sec-root-file and @sec-main-file.\nBut I want to emphasize a curious fact about function parameters (a.k.a. function arguments) in Zig.\nIn summary, function parameters are immutable in Zig.\n\nTake the code example below, where we declare a simple function that just tries to add\nsome amount to the input integer, and returns the result back. But if you look closely\nat the body of this `add2()` function, you will notice that we try\nto save the result back into the `x` function argument.\n\nIn other words, this function not only use the value that it received through the function argument\n`x`, but it also tries to change the value of this function argument, by assigning the addition result\ninto `x`. However, function arguments in Zig are immutable. You cannot change their values, or, you\ncannot assign values to them inside the body's function.\n\nThis is the reason why, the code example below do not compile successfully. If you try to compile\nthis code example, you get a compile error warning you that you are trying to change the value of a\nimmutable (i.e. constant) object.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: u32) u32 {\n x = x + 2;\n return x;\n}\n\npub fn main() !void {\n const y = add2(4);\n std.debug.print(\"{d}\\n\", .{y});\n}\n```\n:::\n\n\n```\nt.zig:3:5: error: cannot assign to constant\n x = x + 2;\n ^\n```\n\n\nIf a function argument receives as input a object whose data type is\nany of the primitive types that we have listed at @sec-primitive-data-types\nthis object is always passed by value to the function. In other words, this object\nis copied to the function stack frame.\n\nHowever, if the input object have a more complex data type, for example, it might\nbe a struct instance, or an array, or a union, etc., in cases like that, the `zig` compiler\nwill take the liberty of deciding for you which strategy is best. The `zig` compiler will\npass your object to the function either by value, or by reference. The compiler will always\nchoose the strategy that is faster for you.\nThis optimization that you get for free is possible only because function arguments are\nimmutable in Zig.\n\nTo overcome this barrier, we need to take the lead, and explicitly choose to pass the\nobject by reference. That is, instead of depending on the `zig` compiler to decide for us, we need\nto explicitly mark the function argument as a pointer. This way, we are telling the compiler\nthat this function argument will be passed by reference to the function.\n\nBy making it a pointer, we can finally use and alter directly the value of this function argument inside\nthe body of the `add2()` function. You can see that the code example below compiles successfully.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nfn add2(x: *u32) void {\n const d: u32 = 2;\n x.* = x.* + d;\n}\n\npub fn main() !void {\n var x: u32 = 4;\n add2(&x);\n std.debug.print(\"Result: {d}\\n\", .{x});\n}\n```\n:::\n\n\n```\nResult: 6\n```\n\n\n\n## Structs and OOP {#sec-structs-and-oop}\n\nZig is a language more closely related to C (which is a procedural language),\nthan it is to C++ or Java (which are object-oriented languages). Because of that, you do not\nhave advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or\nclass inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\n\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C.\nYou give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can\nalso register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object\nthat you create with this new type, will always have these methods available and associated with them.\n\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object\nof this particular class, and you also have a destructor method (or a destructor function) that\nis the function responsible for destroying every object of this class.\n\nIn Zig, we normally declare the constructor and the destructor methods\nof our structs, by declaring an `init()` and a `deinit()` methods inside the struct.\nThis is just a naming convention that you will find across the entire Zig standard library.\nSo, in Zig, the `init()` method of a struct is normally the constructor method of the class represented by this struct.\nWhile the `deinit()` method is the method used for destroying an existing instance of that struct.\n\nThe `init()` and `deinit()` methods are both used extensively in Zig code, and you will see both of\nthem being used when we talk about allocators at @sec-allocators.\nBut, as another example, let's build a simple `User` struct to represent an user of some sort of system.\nIf you look at the `User` struct below, you can see the `struct` keyword, and inside of a\npair of curly braces, we write the struct's body.\n\nNotice the data members of this struct, `id`, `name` and `email`. Every data member have it's\ntype explicitly annotated, with the colon character (`:`) syntax that we described earlier at @sec-root-file.\nBut also notice that every line in the struct body that describes a data member, ends with a comma character (`,`).\nSo every time you declare a data member in your Zig code, always end the line with a comma character, instead\nof ending it with the traditional semicolon character (`;`).\n\nNext, also notice in this example, that we registrated an `init()` function as a method\nof this `User` struct. This `init()` method is the constructor method that you use to instantiate\nevery new `User` object. That is why this `init()` function return an `User` object as result.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\npedro\n```\n\n\n:::\n:::\n\n\nThe `pub` keyword plays an important role in struct declarations, and OOP in Zig.\nEvery method that you declare in your struct that is marked with the keyword `pub`,\nbecomes a public method of this particular struct.\n\nSo every method that you create in your struct, is, at first, a private method\nof that struct. Meaning that, this method can only be called from within this\nstruct. But, if you mark this method as public, with the keyword `pub`, then,\nyou can call the method directly from the `User` object you have\nin your code.\n\nIn other words, the functions marked by the keyword `pub`\nare members of the public API of that struct.\nFor example, if I did not marked the `print_name()` method as public,\nthen, I could not execute the line `u.print_name()`. Because I would\nnot be authorized to call this method directly in my code.\n\n\n\n### Anonymous struct literals {#sec-anonymous-struct-literals}\n\nYou can declare a struct object as a literal value. When we do that, we normally specify the\ndata type of this struct literal by writing it's data type just before the opening curly braces.\nFor example, I could write a struct literal of type `User` that we defined in the previous section like\nthis:\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n```\n:::\n\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a\nstruct literal, but not especify explicitly the type of this particular struct.\nAn anonymous struct is written by using the syntax `.{}`. So, we essentially\nreplaced the explicit type of the struct literal with a dot character (`.`).\n\nAs we described at @sec-type-inference, when you put a dot before a struct literal,\nthe type of this struct literal is automatically inferred by the `zig` compiler.\nIn essence, the `zig` compiler will look for some hint of what is the type of that struct.\nIt can be the type annotation of an function argument,\nor the return type annotation of the function that you are using, or the type annotation\nof a variable.\nIf the compiler do find such type annotation, then, it will use this\ntype in your literal struct. \n\nAnonymous structs are very commom to use in function arguments in Zig.\nOne example that you have seen already constantly, is the `print()`\nfunction from the `stdout` object.\nThis function takes two arguments.\nThe first argument, is a template string, which should\ncontain string format specifiers in it, which tells how the values provided\nin the second argument should be printed into the message.\n\nWhile the second argument is a struct literal that lists the values\nto be printed into the template message specified in the first argument.\nYou normally want to use an anonymous struct literal here, so that, the\n`zig` compiler do the job of specifying the type of this particular\nanonymous struct for you.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\nHello, world!\n```\n\n\n:::\n:::\n\n\n\n\n### Struct declarations must be constant\n\nTypes in Zig must be `const` or `comptime` (we are going to talk more about comptime at @sec-comptime).\nWhat this means is that you cannot create a new data type, and mark it as variable with the `var` keyword.\nSo struct declarations are always constant. You cannot declare a new struct using the `var` keyword.\nIt must be `const`.\n\nIn the `Vec3` example below, this declaration is allowed because I'm using the `const` keyword\nto declare this new data type.\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n```\n:::\n\n\n\n### The `self` method argument {#sec-self-arg}\n\nIn every language that have OOP, when we declare a method of some class or struct, we\nusually declare this method as a function that have a `self` argument.\nThis `self` argument is the reference to the object itself from which the method\nis being called from.\n\nIs not mandatory to use this `self` argument. But why would you not use this `self` argument?\nThere is no reason to not use it. Because the only way to get access to the data stored in the\ndata members of your struct is to access them through this `self` argument.\nIf you don't need to use the data in the data members of your struct inside your method, then, you very likely don't need\na method, you can just simply declare this logic as a simple function, outside of your\nstruct declaration.\n\n\nTake the `Vec3` struct below. Inside this `Vec3` struct we declared a method named `distance()`.\nThis method calculates the distance between two `Vec3` objects, by following the distance\nformula in euclidean space. Notice that this `distance()` method takes two `Vec3` objects\nas input, `self` and `other`.\n\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n```\n:::\n\n\n\nThe `self` argument corresponds to the `Vec3` object from which this `distance()` method\nis being called from. While the `other` is a separate `Vec3` object that is given as input\nto this method. In the example below, the `self` argument corresponds to the object\n`v1`, because the `distance()` method is being called from the `v1` object,\nwhile the `other` argument corresponds to the object `v2`.\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n```\n:::\n\n\n```\nDistance: 3.3970575502926055\n```\n\n\n\n### About the struct state\n\nSometimes you don't need to care about the state of your struct object. Sometimes, you just need\nto instantiate and use the objects, without altering their state. You can notice that when you have methods\ninside your struct declaration that might use the values that are present in the data members, but they\ndo not alter the values in the data members of the struct in anyway.\n\nThe `Vec3` struct that was presented at @sec-self-arg is an example of that.\nThis struct have a single method named `distance()`, and this method do use the values\npresent in all three data members of the struct (`x`, `y` and `z`). But at the same time,\nthis method do not change the values of these data members in any point.\n\nAs a result of that, when we create `Vec3` objects we usually create them as\nconstant objects, like the `v1` and `v2` objects presented at @sec-self-arg.\nWe can create them as variable objects with the `var` keyword,\nif we want to. But because the methods of this `Vec3` struct do not change\nthe state of the objects in any point, is unnecessary to mark them\nas variable objects.\n\nBut why? Why am I talkin about this here? Is because the `self` argument\nin the methods is affected depending on whether the\nmethods present in a struct change or not the state of the object itself.\nMore specifically, when you have a method in a struct that changes the state\nof the object (i.e. change the value of a data member), the `self` argument\nin this method must be annotated in a different manner.\n\nAs I described at @sec-self-arg, the `self` argument in methods of\na struct is the argument that receives as input the object from which the method\nwas called from. We usually annotate this argument in the methods by writing `self`,\nfollowed by the colon character (`:`), and the data type of the struct to which\nthe method belongs to (e.g. `User`, `Vec3`, etc.).\n\nIf we take the `Vec3` struct that we defined in the previous section as an example,\nwe can see in the `distance()` method that this `self` argument is annotated as\n`self: Vec3`. Because the state of the `Vec3` object is never altered by this\nmethod.\n\nBut what if we do have a method that alters the state of the object, by altering the\nvalues of it's data members. How should we annotate `self` in this instance? The answer is:\n\"we should annotate `self` as a pointer of `x`, instead of just `x`\".\nIn other words, you should annotate `self` as `self: *x`, instead of annotating it\nas `self: x`.\n\nIf we create a new method inside the `Vec3` object that, for example, expands the\nvector by multiplying it's coordinates by a factor o two, then, we need to follow\nthis rule specified in the previous paragraph. The code example below demonstrates\nthis idea:\n\n\n::: {.cell build_type='lib'}\n\n```{.zig .cell-code}\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n```\n:::\n\n\nNotice in the code example above that we have added a new method\nto our `Vec3` struct named `double()`. This method essentially doubles the\ncoordinate values of our vector object. Also notice that, in the\ncase of the `double()` method, we annotated the `self` argument as `*Vec3`,\nindicating that this argument receives a pointer (or a reference, if you prefer to call it this way)\nto a `Vec3` object as input.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n```\n:::\n\n\n```\nDoubled: 8.4\n```\n\n\n\nNow, if you change the `self` argument in this `double()` method to `self: Vec3`, like in the\n`distance()` method, you will get the compiler error exposed below as result. Notice that this\nerror message is indicating a line from the `double()` method body,\nindicating that you cannot alter the value of the `x` data member.\n\n```zig\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\n```\n\nThis error message indicates that the `x` data member belongs to a constant object,\nand, because of that, it cannot be changed. Ultimately, this error message\nis telling us that the `self` argument is constant.\n\n```\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\n```\n\nIf you take some time, and think hard about this error message, you will understand it.\nYou already have the tools to understand why we are getting this error message.\nWe have talked about it already at @sec-fun-pars.\nSo remember, every function argument is immutable in Zig, and `self`\nis included in this rule.\n\nIt does not matter if the object that you pass as input to the function argument is\na variable object or not. In this example, we marked the `v3` object as a variable object.\nBut this does not matter. Because it is not about the input object, it is about\nthe function argument.\n\nThe problem begins when we try to alter the value of `self` directly, which is a function argument,\nand, every function argument is immutable by default. You may quest yourself how can we overcome\nthis barrier, and once again, the solution was also discussed at @sec-fun-pars.\nWe overcome this barrier, by explicitly marking the `self` argument as a pointer.\n\n\n::: {.callout-note}\nIf a method of your `x` struct alters the state of the object, by\nchanging the value of any data member, then, remember to use `self: *x`,\ninstead of `self: x` in the function signature of this method.\n:::\n\n\nYou could also interpret the content discussed in this section as:\n\"if you need to alter the state of your `x` struct object in one of it's methods,\nyou must explicitly pass the `x` struct object by reference to the `self` argument of this method\".\n\n\n\n## Type inference {#sec-type-inference}\n\nZig is kind of a strongly typed language. I say \"kind of\" because there are situations\nwhere you don't have to explicitly write the type of every single object in your source code,\nas you would expect from a traditional strongly typed language, such as C and C++.\n\nIn some situations, the `zig` compiler can use type inference to solves the data types for you, easing some of\nthe burden that you carry as a developer.\nThe most commom way this happens is through function arguments that receives struct objects\nas input.\n\nIn general, type inference in Zig is done by using the dot character (`.`).\nEverytime you see a dot character written before a struct literal, or before a enum value, or something like that,\nyou know that this dot character is playing a special party in this place. More specifically, it is\ntelling the `zig` compiler something on the lines of: \"Hey! Can you infer the type of this\nvalue for me? Please!\". In other words, this dot character is playing a role similar to the `auto` keyword in C++.\n\nI give you some examples of this at @sec-anonymous-struct-literals, where we present anonymous struct literals.\nAnonymous struct literals are, essentially, struct literals that use type inference to\ninfer the exact type of this particular struct literal.\nThis type inference is done by looking for some minimal hint of the correct data type to be used.\nYou could say that the `zig` compiler looks for any neighbouring type annotation that might tell him\nwhat would be the correct type.\n\nAnother commom place where we use type inference in Zig is at switch statements (which we talk about at @sec-switch).\nSo I also gave some other examples of type inference at @sec-switch, where we were inferring the data types of enum values listed inside\nof switch statements (e.g. `.DE`).\nBut as another example, take a look at this `fence()` function reproduced below,\nwhich comes from the [`atomic.zig` module](https://github.com/ziglang/zig/blob/master/lib/std/atomic.zig)[^fence-fn]\nof the Zig Standard Library.\n\n[^fence-fn]: .\n\nThere are a lot of things in this function that we haven't talked about yet, such as:\nwhat `comptime` means? `inline`? `extern`? What is this star symbol before `Self`?\nLet's just ignore all of these things, and focus solely on the switch statement\nthat is inside this function.\n\nWe can see that this switch statement uses the `order` object as input. This `order`\nobject is one of the inputs of this `fence()` function, and we can see in the type annotation,\nthat this object is of type `AtomicOrder`. We can also see a bunch of values inside the\nswitch statements that begins with a dot character, such as `.release` and `.acquire`.\n\nBecause these weird values contain a dot character before them, we are asking the `zig`\ncompiler to infer the types of these values inside the switch statement. Then, the `zig`\ncompiler is looking into the current context where these values are being used, and it is\ntrying to infer the types of these values.\n\nSince they are being used inside a switch statement, the `zig` compiler looks into the type\nof the input object given to the switch statement, which is the `order` object in this case.\nBecause this object have type `AtomicOrder`, the `zig` compiler infers that these values\nare data members from this type `AtomicOrder`.\n\n\n::: {.cell}\n\n```{.zig .cell-code}\npub inline fn fence(self: *Self, comptime order: AtomicOrder) void {\n // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.\n if (builtin.sanitize_thread) {\n const tsan = struct {\n extern \"c\" fn __tsan_acquire(addr: *anyopaque) void;\n extern \"c\" fn __tsan_release(addr: *anyopaque) void;\n };\n\n const addr: *anyopaque = self;\n return switch (order) {\n .unordered, .monotonic => @compileError(@tagName(order) ++ \" only applies to atomic loads and stores\"),\n .acquire => tsan.__tsan_acquire(addr),\n .release => tsan.__tsan_release(addr),\n .acq_rel, .seq_cst => {\n tsan.__tsan_acquire(addr);\n tsan.__tsan_release(addr);\n },\n };\n }\n\n return @fence(order);\n}\n```\n:::\n\n\nThis is how basic type inference is done in Zig. If we didn't use the dot character before\nthe values inside this switch statement, then, we would be forced to write explicitly\nthe data types of these values. For example, instead of writing `.release` we would have to\nwrite `AtomicOrder.release`. We would have to do this for every single value\nin this switch statement, and this is a lot of work. That is why type inference\nis commonly used on switch statements in Zig.\n\n\n\n## Modules\n\nWe already talked about what modules are, and also, how to import other modules into\nyou current module through *import statements*, so that you can use functionality from these other modules in\nyour current module.\nBut in this section, I just want to make it clear that modules are actually structs in Zig.\n\nIn other words, every Zig module (i.e. a `.zig` file) that you write in your project\nis internally stored as a struct object.\nTake the line exposed below as an example. In this line we are importing the\nZig Standard Library into our current module.\n\n```zig\nconst std = @import(\"std\");\n```\n\nWhen we want to access the functions and objects from the standard library, we\nare basically accessing the data members of the struct stored in the `std`\nobject. That is why we use the same syntax that we use in normal structs, with the dot operator (`.`)\nto access the data members and methods of the struct.\n\nWhen this \"import statement\" get's executed, the result of this expression is a struct\nobject that contains the Zig Standard Library modules, global variables, functions, etc.\nAnd this struct object get's saved (or stored) inside the constant object named `std`.\n\n\nTake the [`thread_pool.zig` module from the project `zap`](https://github.com/kprotty/zap/blob/blog/src/thread_pool.zig)[^thread]\nas an example. This module is written as if it was\na big struct. That is why we have a top-level and public `init()` method\nwritten in this module. The idea is that all top-level functions written in this\nmodule are methods from the struct, and all top-level objects and struct declarations\nare data members of this struct. The module is the struct itself.\n\n[^thread]: \n\n\nSo you would import and use this module by doing something like this:\n\n```zig\nconst std = @import(\"std\");\nconst ThreadPool = @import(\"thread_pool.zig\");\nconst num_cpus = std.Thread.getCpuCount()\n catch @panic(\"failed to get cpu core count\");\nconst num_threads = std.math.cast(u16, num_cpus)\n catch std.math.maxInt(u16);\nconst pool = ThreadPool.init(\n .{ .max_threads = num_threads }\n);\n```\n", + "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" ], diff --git a/docs/Chapters/03-structs.html b/docs/Chapters/03-structs.html index 5906944..4d2ab93 100644 --- a/docs/Chapters/03-structs.html +++ b/docs/Chapters/03-structs.html @@ -710,8 +710,9 @@

      t.zig:16:13: error: cannot assign to constant self.x = self.x * 2.0; ~~~~^~ -

      If you take some time, and think hard about this error message, you will understand it. You already have the tools to understand why we are getting this error message. We have talked about it already at Section 2.2.

      -

      So remember, every function argument is immutable in Zig. It does not matter if we marked the v3 object as a variable object. Because we are trying to alter self directly, which is a function argument, and, every function argument is immutable by default. We overcome this barrier, by explicitly marking the self argument as a pointer.

      +

      If you take some time, and think hard about this error message, you will understand it. You already have the tools to understand why we are getting this error message. We have talked about it already at Section 2.2. So remember, every function argument is immutable in Zig, and self is included in this rule.

      +

      It does not matter if the object that you pass as input to the function argument is a variable object or not. In this example, we marked the v3 object as a variable object. But this does not matter. Because it is not about the input object, it is about the function argument.

      +

      The problem begins when we try to alter the value of self directly, which is a function argument, and, every function argument is immutable by default. You may quest yourself how can we overcome this barrier, and once again, the solution was also discussed at Section 2.2. We overcome this barrier, by explicitly marking the self argument as a pointer.

      diff --git a/docs/search.json b/docs/search.json index 5827ee2..5502796 100644 --- a/docs/search.json +++ b/docs/search.json @@ -234,7 +234,7 @@ "href": "Chapters/03-structs.html#sec-structs-and-oop", "title": "2  Structs, Modules and Control Flow", "section": "2.3 Structs and OOP", - "text": "2.3 Structs and OOP\nZig is a language more closely related to C (which is a procedural language), than it is to C++ or Java (which are object-oriented languages). Because of that, you do not have advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or class inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C. You give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can also register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object that you create with this new type, will always have these methods available and associated with them.\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object of this particular class, and you also have a destructor method (or a destructor function) that is the function responsible for destroying every object of this class.\nIn Zig, we normally declare the constructor and the destructor methods of our structs, by declaring an init() and a deinit() methods inside the struct. This is just a naming convention that you will find across the entire Zig standard library. 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.\nThe 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 Section 3.2. 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.\nNotice the data members of this struct, id, name and email. Every data member have it’s type explicitly annotated, with the colon character (:) syntax that we described earlier at Section 1.2.2. But also notice that every line in the struct body that describes a data member, ends with a comma character (,). So every time you declare a data member in your Zig code, always end the line with a comma character, instead of ending it with the traditional semicolon character (;).\nNext, also notice in this example, that we registrated an init() function as a method of this User struct. This init() method is the constructor method that you use to instantiate every new User object. That is why this init() function return an User object as result.\n\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n\npedro\n\n\nThe pub keyword plays an important role in struct declarations, and OOP in Zig. Every method that you declare in your struct that is marked with the keyword pub, becomes a public method of this particular struct.\nSo every method that you create in your struct, is, at first, a private method of that struct. Meaning that, this method can only be called from within this struct. But, if you mark this method as public, with the keyword pub, then, you can call the method directly from the User object you have in your code.\nIn other words, the functions marked by the keyword pub are members of the public API of that struct. For example, if I did not marked the print_name() method as public, then, I could not execute the line u.print_name(). Because I would not be authorized to call this method directly in my code.\n\n2.3.1 Anonymous struct literals\nYou can declare a struct object as a literal value. When we do that, we normally specify the data type of this struct literal by writing it’s data type just before the opening curly braces. For example, I could write a struct literal of type User that we defined in the previous section like this:\n\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a struct literal, but not especify explicitly the type of this particular struct. An anonymous struct is written by using the syntax .{}. So, we essentially replaced the explicit type of the struct literal with a dot character (.).\nAs we described at Section 2.4, when you put a dot before a struct literal, the type of this struct literal is automatically inferred by the zig compiler. In essence, the zig compiler will look for some hint of what is the type of that struct. It can be the type annotation of an function argument, or the return type annotation of the function that you are using, or the type annotation of a variable. If the compiler do find such type annotation, then, it will use this type in your literal struct.\nAnonymous structs are very commom to use in function arguments in Zig. One example that you have seen already constantly, is the print() function from the stdout object. This function takes two arguments. The first argument, is a template string, which should contain string format specifiers in it, which tells how the values provided in the second argument should be printed into the message.\nWhile the second argument is a struct literal that lists the values to be printed into the template message specified in the first argument. You normally want to use an anonymous struct literal here, so that, the zig compiler do the job of specifying the type of this particular anonymous struct for you.\n\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n\nHello, world!\n\n\n\n\n2.3.2 Struct declarations must be constant\nTypes in Zig must be const or comptime (we are going to talk more about comptime at Section 12.1). What this means is that you cannot create a new data type, and mark it as variable with the var keyword. So struct declarations are always constant. You cannot declare a new struct using the var keyword. It must be const.\nIn the Vec3 example below, this declaration is allowed because I’m using the const keyword to declare this new data type.\n\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n\n\n\n2.3.3 The self method argument\nIn every language that have OOP, when we declare a method of some class or struct, we usually declare this method as a function that have a self argument. This self argument is the reference to the object itself from which the method is being called from.\nIs not mandatory to use this self argument. But why would you not use this self argument? There is no reason to not use it. Because the only way to get access to the data stored in the data members of your struct is to access them through this self argument. If you don’t need to use the data in the data members of your struct inside your method, then, you very likely don’t need a method, you can just simply declare this logic as a simple function, outside of your struct declaration.\nTake the Vec3 struct below. Inside this Vec3 struct we declared a method named distance(). This method calculates the distance between two Vec3 objects, by following the distance formula in euclidean space. Notice that this distance() method takes two Vec3 objects as input, self and other.\n\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n\nThe self argument corresponds to the Vec3 object from which this distance() method is being called from. While the other is a separate Vec3 object that is given as input to this method. In the example below, the self argument corresponds to the object v1, because the distance() method is being called from the v1 object, while the other argument corresponds to the object v2.\n\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n\nDistance: 3.3970575502926055\n\n\n2.3.4 About the struct state\nSometimes you don’t need to care about the state of your struct object. Sometimes, you just need to instantiate and use the objects, without altering their state. You can notice that when you have methods inside your struct declaration that might use the values that are present in the data members, but they do not alter the values in the data members of the struct in anyway.\nThe Vec3 struct that was presented at Section 2.3.3 is an example of that. This struct have a single method named distance(), and this method do use the values present in all three data members of the struct (x, y and z). But at the same time, this method do not change the values of these data members in any point.\nAs a result of that, when we create Vec3 objects we usually create them as constant objects, like the v1 and v2 objects presented at Section 2.3.3. We can create them as variable objects with the var keyword, if we want to. But because the methods of this Vec3 struct do not change the state of the objects in any point, is unnecessary to mark them as variable objects.\nBut why? Why am I talkin about this here? Is because the self argument in the methods is affected depending on whether the methods present in a struct change or not the state of the object itself. More specifically, when you have a method in a struct that changes the state of the object (i.e. change the value of a data member), the self argument in this method must be annotated in a different manner.\nAs I described at Section 2.3.3, the self argument in methods of a struct is the argument that receives as input the object from which the method was called from. We usually annotate this argument in the methods by writing self, followed by the colon character (:), and the data type of the struct to which the method belongs to (e.g. User, Vec3, etc.).\nIf we take the Vec3 struct that we defined in the previous section as an example, we can see in the distance() method that this self argument is annotated as self: Vec3. Because the state of the Vec3 object is never altered by this method.\nBut what if we do have a method that alters the state of the object, by altering the values of it’s data members. How should we annotate self in this instance? The answer is: “we should annotate self as a pointer of x, instead of just x”. In other words, you should annotate self as self: *x, instead of annotating it as self: x.\nIf we create a new method inside the Vec3 object that, for example, expands the vector by multiplying it’s coordinates by a factor o two, then, we need to follow this rule specified in the previous paragraph. The code example below demonstrates this idea:\n\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n\nNotice in the code example above that we have added a new method to our Vec3 struct named double(). This method essentially doubles the coordinate values of our vector object. Also notice that, in the case of the double() method, we annotated the self argument as *Vec3, indicating that this argument receives a pointer (or a reference, if you prefer to call it this way) to a Vec3 object as input.\n\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n\nDoubled: 8.4\nNow, if you change the self argument in this double() method to self: Vec3, like in the distance() method, you will get the compiler error exposed below as result. Notice that this error message is indicating a line from the double() method body, indicating that you cannot alter the value of the x data member.\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\nThis error message indicates that the x data member belongs to a constant object, and, because of that, it cannot be changed. Ultimately, this error message is telling us that the self argument is constant.\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\nIf you take some time, and think hard about this error message, you will understand it. You already have the tools to understand why we are getting this error message. We have talked about it already at Section 2.2.\nSo remember, every function argument is immutable in Zig. It does not matter if we marked the v3 object as a variable object. Because we are trying to alter self directly, which is a function argument, and, every function argument is immutable by default. We overcome this barrier, by explicitly marking the self argument as a pointer.\n\n\n\n\n\n\nNote\n\n\n\nIf a method of your x struct alters the state of the object, by changing the value of any data member, then, remember to use self: *x, instead of self: x in the function signature of this method.\n\n\nYou could also interpret the content discussed in this section as: “if you need to alter the state of your x struct object in one of it’s methods, you must explicitly pass the x struct object by reference to the self argument of this method”.", + "text": "2.3 Structs and OOP\nZig is a language more closely related to C (which is a procedural language), than it is to C++ or Java (which are object-oriented languages). Because of that, you do not have advanced OOP (Object-Oriented Programming) patterns available in Zig, such as classes, interfaces or class inheritance. Nonetheless, OOP in Zig is still possible by using struct definitions.\nWith struct definitions, you can create (or define) a new data type in Zig. These struct definitions work the same way as they work in C. You give a name to this new struct (or, to this new data type you are creating), then, you list the data members of this new struct. You can also register functions inside this struct, and they become the methods of this particular struct (or data type), so that, every object that you create with this new type, will always have these methods available and associated with them.\nIn C++, when we create a new class, we normally have a constructor method (or, a constructor function) to construct or to instantiate every object of this particular class, and you also have a destructor method (or a destructor function) that is the function responsible for destroying every object of this class.\nIn Zig, we normally declare the constructor and the destructor methods of our structs, by declaring an init() and a deinit() methods inside the struct. This is just a naming convention that you will find across the entire Zig standard library. 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.\nThe 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 Section 3.2. 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.\nNotice the data members of this struct, id, name and email. Every data member have it’s type explicitly annotated, with the colon character (:) syntax that we described earlier at Section 1.2.2. But also notice that every line in the struct body that describes a data member, ends with a comma character (,). So every time you declare a data member in your Zig code, always end the line with a comma character, instead of ending it with the traditional semicolon character (;).\nNext, also notice in this example, that we registrated an init() function as a method of this User struct. This init() method is the constructor method that you use to instantiate every new User object. That is why this init() function return an User object as result.\n\nconst std = @import(\"std\");\nconst stdout = std.io.getStdOut().writer();\nconst User = struct {\n id: u64,\n name: []const u8,\n email: []const u8,\n\n pub fn init(id: u64,\n name: []const u8,\n email: []const u8) User {\n\n return User {\n .id = id,\n .name = name,\n .email = email\n };\n }\n\n pub fn print_name(self: User) !void {\n try stdout.print(\"{s}\\n\", .{self.name});\n }\n};\n\npub fn main() !void {\n const u = User.init(1, \"pedro\", \"email@gmail.com\");\n try u.print_name();\n}\n\npedro\n\n\nThe pub keyword plays an important role in struct declarations, and OOP in Zig. Every method that you declare in your struct that is marked with the keyword pub, becomes a public method of this particular struct.\nSo every method that you create in your struct, is, at first, a private method of that struct. Meaning that, this method can only be called from within this struct. But, if you mark this method as public, with the keyword pub, then, you can call the method directly from the User object you have in your code.\nIn other words, the functions marked by the keyword pub are members of the public API of that struct. For example, if I did not marked the print_name() method as public, then, I could not execute the line u.print_name(). Because I would not be authorized to call this method directly in my code.\n\n2.3.1 Anonymous struct literals\nYou can declare a struct object as a literal value. When we do that, we normally specify the data type of this struct literal by writing it’s data type just before the opening curly braces. For example, I could write a struct literal of type User that we defined in the previous section like this:\n\nconst eu = User {\n .id = 1,\n .name = \"Pedro\",\n .email = \"someemail@gmail.com\"\n};\n_ = eu;\n\nHowever, in Zig, we can also write an anonymous struct literal. That is, you can write a struct literal, but not especify explicitly the type of this particular struct. An anonymous struct is written by using the syntax .{}. So, we essentially replaced the explicit type of the struct literal with a dot character (.).\nAs we described at Section 2.4, when you put a dot before a struct literal, the type of this struct literal is automatically inferred by the zig compiler. In essence, the zig compiler will look for some hint of what is the type of that struct. It can be the type annotation of an function argument, or the return type annotation of the function that you are using, or the type annotation of a variable. If the compiler do find such type annotation, then, it will use this type in your literal struct.\nAnonymous structs are very commom to use in function arguments in Zig. One example that you have seen already constantly, is the print() function from the stdout object. This function takes two arguments. The first argument, is a template string, which should contain string format specifiers in it, which tells how the values provided in the second argument should be printed into the message.\nWhile the second argument is a struct literal that lists the values to be printed into the template message specified in the first argument. You normally want to use an anonymous struct literal here, so that, the zig compiler do the job of specifying the type of this particular anonymous struct for you.\n\nconst std = @import(\"std\");\npub fn main() !void {\n const stdout = std.io.getStdOut().writer();\n try stdout.print(\"Hello, {s}!\\n\", .{\"world\"});\n}\n\nHello, world!\n\n\n\n\n2.3.2 Struct declarations must be constant\nTypes in Zig must be const or comptime (we are going to talk more about comptime at Section 12.1). What this means is that you cannot create a new data type, and mark it as variable with the var keyword. So struct declarations are always constant. You cannot declare a new struct using the var keyword. It must be const.\nIn the Vec3 example below, this declaration is allowed because I’m using the const keyword to declare this new data type.\n\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n};\n\n\n\n2.3.3 The self method argument\nIn every language that have OOP, when we declare a method of some class or struct, we usually declare this method as a function that have a self argument. This self argument is the reference to the object itself from which the method is being called from.\nIs not mandatory to use this self argument. But why would you not use this self argument? There is no reason to not use it. Because the only way to get access to the data stored in the data members of your struct is to access them through this self argument. If you don’t need to use the data in the data members of your struct inside your method, then, you very likely don’t need a method, you can just simply declare this logic as a simple function, outside of your struct declaration.\nTake the Vec3 struct below. Inside this Vec3 struct we declared a method named distance(). This method calculates the distance between two Vec3 objects, by following the distance formula in euclidean space. Notice that this distance() method takes two Vec3 objects as input, self and other.\n\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n};\n\nThe self argument corresponds to the Vec3 object from which this distance() method is being called from. While the other is a separate Vec3 object that is given as input to this method. In the example below, the self argument corresponds to the object v1, because the distance() method is being called from the v1 object, while the other argument corresponds to the object v2.\n\nconst v1 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nconst v2 = Vec3 {\n .x = 5.1, .y = 5.6, .z = 1.6\n};\n\nstd.debug.print(\n \"Distance: {d}\\n\",\n .{v1.distance(v2)}\n);\n\nDistance: 3.3970575502926055\n\n\n2.3.4 About the struct state\nSometimes you don’t need to care about the state of your struct object. Sometimes, you just need to instantiate and use the objects, without altering their state. You can notice that when you have methods inside your struct declaration that might use the values that are present in the data members, but they do not alter the values in the data members of the struct in anyway.\nThe Vec3 struct that was presented at Section 2.3.3 is an example of that. This struct have a single method named distance(), and this method do use the values present in all three data members of the struct (x, y and z). But at the same time, this method do not change the values of these data members in any point.\nAs a result of that, when we create Vec3 objects we usually create them as constant objects, like the v1 and v2 objects presented at Section 2.3.3. We can create them as variable objects with the var keyword, if we want to. But because the methods of this Vec3 struct do not change the state of the objects in any point, is unnecessary to mark them as variable objects.\nBut why? Why am I talkin about this here? Is because the self argument in the methods is affected depending on whether the methods present in a struct change or not the state of the object itself. More specifically, when you have a method in a struct that changes the state of the object (i.e. change the value of a data member), the self argument in this method must be annotated in a different manner.\nAs I described at Section 2.3.3, the self argument in methods of a struct is the argument that receives as input the object from which the method was called from. We usually annotate this argument in the methods by writing self, followed by the colon character (:), and the data type of the struct to which the method belongs to (e.g. User, Vec3, etc.).\nIf we take the Vec3 struct that we defined in the previous section as an example, we can see in the distance() method that this self argument is annotated as self: Vec3. Because the state of the Vec3 object is never altered by this method.\nBut what if we do have a method that alters the state of the object, by altering the values of it’s data members. How should we annotate self in this instance? The answer is: “we should annotate self as a pointer of x, instead of just x”. In other words, you should annotate self as self: *x, instead of annotating it as self: x.\nIf we create a new method inside the Vec3 object that, for example, expands the vector by multiplying it’s coordinates by a factor o two, then, we need to follow this rule specified in the previous paragraph. The code example below demonstrates this idea:\n\nconst std = @import(\"std\");\nconst m = std.math;\nconst Vec3 = struct {\n x: f64,\n y: f64,\n z: f64,\n\n pub fn distance(self: Vec3, other: Vec3) f64 {\n const xd = m.pow(f64, self.x - other.x, 2.0);\n const yd = m.pow(f64, self.y - other.y, 2.0);\n const zd = m.pow(f64, self.z - other.z, 2.0);\n return m.sqrt(xd + yd + zd);\n }\n\n pub fn double(self: *Vec3) void {\n self.x = self.x * 2.0;\n self.y = self.y * 2.0;\n self.z = self.z * 2.0;\n }\n};\n\nNotice in the code example above that we have added a new method to our Vec3 struct named double(). This method essentially doubles the coordinate values of our vector object. Also notice that, in the case of the double() method, we annotated the self argument as *Vec3, indicating that this argument receives a pointer (or a reference, if you prefer to call it this way) to a Vec3 object as input.\n\nvar v3 = Vec3 {\n .x = 4.2, .y = 2.4, .z = 0.9\n};\nv3.double();\nstd.debug.print(\"Doubled: {d}\\n\", .{v3.x});\n\nDoubled: 8.4\nNow, if you change the self argument in this double() method to self: Vec3, like in the distance() method, you will get the compiler error exposed below as result. Notice that this error message is indicating a line from the double() method body, indicating that you cannot alter the value of the x data member.\n// If we change the function signature of double to:\n pub fn double(self: Vec3) void {\nThis error message indicates that the x data member belongs to a constant object, and, because of that, it cannot be changed. Ultimately, this error message is telling us that the self argument is constant.\nt.zig:16:13: error: cannot assign to constant\n self.x = self.x * 2.0;\n ~~~~^~\nIf you take some time, and think hard about this error message, you will understand it. You already have the tools to understand why we are getting this error message. We have talked about it already at Section 2.2. So remember, every function argument is immutable in Zig, and self is included in this rule.\nIt does not matter if the object that you pass as input to the function argument is a variable object or not. In this example, we marked the v3 object as a variable object. But this does not matter. Because it is not about the input object, it is about the function argument.\nThe problem begins when we try to alter the value of self directly, which is a function argument, and, every function argument is immutable by default. You may quest yourself how can we overcome this barrier, and once again, the solution was also discussed at Section 2.2. We overcome this barrier, by explicitly marking the self argument as a pointer.\n\n\n\n\n\n\nNote\n\n\n\nIf a method of your x struct alters the state of the object, by changing the value of any data member, then, remember to use self: *x, instead of self: x in the function signature of this method.\n\n\nYou could also interpret the content discussed in this section as: “if you need to alter the state of your x struct object in one of it’s methods, you must explicitly pass the x struct object by reference to the self argument of this method”.", "crumbs": [ "2  Structs, Modules and Control Flow" ]