diff --git a/README.org b/README.org index 8883fcf..56b549b 100644 --- a/README.org +++ b/README.org @@ -3,9 +3,11 @@ The purpose of this repository is to document the features of the Zig language, You can use this documentation with 2 different ways: - Using emacs org mode so that you can execute codes on the fly(litterate programming) -- Opening the index.html file in your browser or navigating to our [[hosted guide][https://pismice.github.io/HEIG_ZIG/]] +- Opening the index.html file in your browser or navigating to our [[https://pismice.github.io/HEIG_ZIG/][hosted guide]] (this link always host the last commmit of the master branch) ** Remarks - We are going to move all the "Sources" to the "Bilbliography" chapter using Zotero - Some exemples are not working with emacs yet - You might see a few TODO or empty chapters, those are the ones we are working on or plan to work on in the future +- Not all the code blocks are org mode compatible yet +- We are going to make it nicer by adding themes to the code (I hope) diff --git a/docs/concurrency.org b/docs/concurrency.org index a8aa58e..af591cb 100644 --- a/docs/concurrency.org +++ b/docs/concurrency.org @@ -1,77 +1,89 @@ * Concurrency ** Definitions -Coroutine (cooperative multitasking): -- a coroutine itself is not concurrent !, it allows to manage concurrent tasks in the same way as callbacks for example +Before diving into the different ways to do concurrency in ZIG, let's first define some terms that are useful to understand the basics of concurrency (not related to Zig). + +*** Coroutine (cooperative multitasking) +- a coroutine in itself is not concurrent , it allows to manage concurrent tasks in the same way as callbacks for example - single threaded - allows to write code in a sequential manner - allows not to have context switch - 2 types of coroutine: symmetric vs asymmetric (relative to the control-transfer mechanism, explained a bit below) - coroutines are not preemptive, they are cooperative, therefore they must explicitly give back control by **yielding** - usually uses keywords like **yield**, **resume**, **suspend**, ... -- Coroutines can be either stackful or stackless, we are not gonna dive deep into this concept since most of the time you are going to use stackful coroutines since they allow you to suspend from within a nested stackframe (strength of stacless coroutines: efficiency) +- Coroutines can be either stackful or stackless, we are not gonna dive deep into this concept since most of the time you are going to use stackful coroutines since they allow you to suspend from within a nested stackframe (the only strength of stackless coroutines: efficiency) -Symmetric coroutines: +**** Symmetric coroutines - Their only control-transfer mechanism is: 1. explicitly passing control to another coroutine -Asymmetric coroutines (called asymmetric because the control-transfer can go both ways): +**** Asymmetric coroutines (called asymmetric because the control-transfer can go both ways) - They have two control-transfer mechanisms: 1. invoking another coroutine which is going to be the subcoroutine of the calling coroutine 2. suspending itself and giving control back to the caller -Green threads (userland threads): +*** Green threads (userland threads) - preemptive multitasking (PAS VRAI selon appel !) - managed by a VM/runtime instead of the OS - has the advantage to be managed in user space instead of kernel space which allows to avoid the overhead of making system calls to open and close threads - still use several native threads behind the scenes - used more for short-lived tasks -Fibers : +*** Fibers - Same as green threads but cooperative multitasking instead of preemptive multitasking -Preemptive multitasking: -- The underlying architecture (NOT us) is going to be deciding what to execute and when +*** Preemptive multitasking +- The underlying architecture (NOT us, but the OS for exemple) is going to be deciding what to execute and when - This means that our threads can be interrupted at any time -Cooperative multitasking: +*** Cooperative multitasking - A thread will continue to run until it decides to give up control (yielding) - We are in full control of the execution flow of our threads - The drawback of this method is that YOU have to think and code in a way to not have starving threads for exemple -Thread (multithreading -> the threads are still blocking): +*** Thread +- Multithreading +- Each threads are still blocking - Main and historical way of doing concurrency - Parallelism (eventually): Threads allow for true parallel execution on multi-core CPUs, enabling better utilization of hardware resources and potentially improving performance. - Isolation: Threads provide strong isolation between concurrent tasks, with each thread having its own execution context, stack, and local variables. - Scalability: Managing a large number of threads can be challenging and may lead to scalability issues, as the operating system kernel has to allocate resources for each thread. - Usually used with a thread pool to avoid the overhead of creating and destroying threads for each task which is very expensive -QUESTION: est ce que je parle pas des virtual threads la ? -Event-driven programming (ex: NodeJS): +*** Event-driven programming (ex: NodeJS) - Event loop that regularly checks if "events" are launched - Scalability: Event-driven architectures are inherently scalable, as they can handle a large number of concurrent connections with low memory and CPU overhead. - Code behind NodeJS: https://github.com/libuv/libuv ** Zig current state -There a multiple ways you currently can do concurent code in ZIG: -- Spawning OS threads (https://ziglang.org/documentation/master/std/#std.Thread) -- Using old async/await from before 0.11 (not recommanded because unstable !!! https://github.com/ziglang/zig/issues/6025 and might probably never get back in the language https://ziglang.org/download/0.12.0/release-notes.html#AsyncAwait-Feature-Status) -- Using an event loop (by wrapping libuv or using libxev which is the equivalent buz in ZIG) -- Using fibers (https://github.com/kprotty/zefi, https://github.com/kassane/fiber) -- async/await built on top of libxev (https://github.com/rsepassi/zigcoro) -- Low level single threaded concurrency if you want to craft your owng thing (https://github.com/kassane/context) -- ... obviously you can still use C libraries that do async stuff :) - -*** Feedbacks from thoses methods: -**** OS threads -**** Old async/await -**** libxev -**** CHOISIR UNE DES 2 fibers -**** zigcoro +There are multiple ways you currently can do concurent code in ZIG, we are going to explore a few here: + +*** OS threads (std) +Spawning OS threads (https://ziglang.org/documentation/master/std/#std.Thread) +TODO exemple + +*** Old async/await +Using old async/await from before 0.11 (not recommanded because unstable !!! https://github.com/ziglang/zig/issues/6025 and might probably never get back in the language https://ziglang.org/download/0.12.0/release-notes.html#AsyncAwait-Feature-Status) + +*** libxev +Using an event loop (by wrapping libuv or using libxev which is the equivalent buz in ZIG) +TODO exemple + +*** Fibers +Using fibers (https://github.com/kprotty/zefi, https://github.com/kassane/fiber) +TODO exemple + +*** zigcoro +async/await built on top of libxev (https://github.com/rsepassi/zigcoro) +TODO exemple + +*** Using C libraries +... obviously you can still use C libraries that do async stuff :) +TODO exemple ** Function coloring Green threads make function colors disapear ???? (dependences entre threads) -** TODO MES NOTES -------- pas besoin de lire ca, cest juste pour moi pour approfondir certains sujets plus tard +TODO MES NOTES -------- pas besoin de lire ca, cest juste pour moi pour approfondir certains sujets plus tard - "libuv and OSes will usually run background/worker threads and/or polling to perform tasks in a non-blocking manner." est ce que cest comment ca under the hood les non blocking async ? diff --git a/docs/errors.org b/docs/errors.org index 5d36408..f9b9aba 100644 --- a/docs/errors.org +++ b/docs/errors.org @@ -1,7 +1,10 @@ ** Errors https://ziglang.org/documentation/0.11.1/#Errors + In ZIG errors are juste values. + Those values are those of an special type of enum "error". + When you declare your enum using this keyword instead of "enum", the compiler is going to know that those values are going to be errors, therefore they can be catched, tried, ... diff --git a/docs/index.html b/docs/index.html index 37b4a72..dba8dc3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,7 +3,7 @@ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Zig documentation @@ -69,143 +69,209 @@

Zig documentation

Table of Contents

-
-

1 Introduction

+
+

1 Introduction

Nowadays, multiple languages have emerged to compete with the well-established C language. @@ -228,10 +294,14 @@

1 Introduction

In the sections below, certain aspects of this programming language will be explored in greater depth.

+ +

+Note that this documentation is generated from emacs with org-mode and that you can find the Github repository here. +

-
-

1.1 What this documentation is not

+
+

1.1 What this documentation is not

This is not a guide to learn Zig as first-language, this documentation wants to go in-depth in certain parts of the language. @@ -247,8 +317,8 @@

1.1 What this document

-
-

1.2 How to use this documentation

+
+

1.2 How to use this documentation

This documentation is built to guide to user through different examples. @@ -272,12 +342,12 @@

1.2 How to use this do

-
-

1.3 Build Emacs configuration

+
+

1.3 Build Emacs configuration

-
-

1.3.1 Install Zig emacs org babel plugin

+
+

1.3.1 Install Zig emacs org babel plugin

Use C-c C-c (ctrl-c ctrl-c) to evaluate the code below. @@ -305,8 +375,8 @@

1.3.1 Install Zig emac

-
-

1.3.2 Create an emacs configuration file

+
+

1.3.2 Create an emacs configuration file

To use the plugin installed before, you need to modify the first path to give the path where the plugin is installed (see section upper), @@ -357,8 +427,8 @@

1.3.2 Create an emacs

-
-

1.3.3 Execute the configuration file from the configuration

+
+

1.3.3 Execute the configuration file from the configuration

You can put the code below in your init.el it will load the file produced before and load the necessary package to learn Zig with literate programming. @@ -375,8 +445,8 @@

1.3.3 Execute the conf

-
-

2 Allocator

+
+

2 Allocator

The Zig language doesn't hide the memory management, the mantra of Zig is to have no hidden control flow. @@ -399,8 +469,8 @@

2 Allocator

-
-

2.1 General pattern

+
+

2.1 General pattern

In Zig for library development, a good practice is to pass an allocator as a parameter, so the programmer can choose the best allocator for his use case. @@ -432,8 +502,8 @@

2.1 General pattern -
-

2.2 Page allocator (page_allocator)

+
+

2.2 Page allocator (page_allocator)

The page allocator is the basic allocator that will directly ask the OS for memory. @@ -467,8 +537,8 @@

2.2 Page allocator (

-
-

2.3 Fixed buffer allocator

+
+

2.3 Fixed buffer allocator

The FixedBufferAllocator will allocate memory into a fixed buffer, the size of the buffer needs to be known at comptime. @@ -502,8 +572,8 @@

2.3 Fixed buffer alloc

-
-

2.4 Arena allocator

+
+

2.4 Arena allocator

The arena allocator takes a child allocator as input. This pattern is used to allocate multiple pieces of memory and free them at once. @@ -543,8 +613,8 @@

2.4 Arena allocator

-
-

2.4.1 Internal working of arena allocator

+
+

2.4.1 Internal working of arena allocator

Zig how arena allocator works @@ -602,8 +672,8 @@

2.4.1 Internal working

-
-

2.5 General purpose allocator

+
+

2.5 General purpose allocator

A general purpose allocator is available in Zig, this is a safe allocator that can prevent double free memory, "use after free" and detect memory leaks. @@ -701,155 +771,1272 @@

2.5 General purpose al myStruct.*.myFloat = 3.1415; myStruct.*.myInt = 42; -try stdout.print("myStruct: {}\n", .{myStruct}); +try stdout.print("myStruct: {}\n", .{myStruct}); + + +

+
+
+ + +
+

2.6 Testing allocator

+
+

+The testing allocator is available in tests and the test runner will report all the memory leaks that have occurred during testing.[cite:@LearningZigHeap] [cite:@ZiglangZig2024] +

+ +

+The example below shows how to use the testing allocator. +

+
+
test "Test ArrayList" {
+    var array = std.ArrayList(i32).init(std.testing.allocator);
+    defer array.deinit();
+
+    const expected: i32 = 42;
+    try array.append(expected);
+
+    try std.testing.expectEqual(expected, array.items[0]);
+}
+
+
+ +

+If the code below is run, the test will fail and it will display a leaked test memory. +Zig will help the programmer to detect memory leaks using code tests. +

+
+
test "Test ArrayList" {
+    var array = std.ArrayList(i32).init(std.testing.allocator);
+    //defer array.deinit(); -> the array will not be free
+
+    const expected: i32 = 42;
+    try array.append(expected);
+
+    try std.testing.expectEqual(expected, array.items[0]);
+}
+
+
+ +

+Under the hood, the testing allocator is an instance of the general purpose allocator. +Below, an extract of testing allocator of the standard library testing.zig. +If the testing allocator is used outside of the tests, a compilation error will be thrown. +

+
+
/// This should only be used in temporary test programs.
+pub const allocator = allocator_instance.allocator();
+pub var allocator_instance = b: {
+    if (!builtin.is_test)
+	@compileError("Cannot use testing allocator outside of test block");
+    break :b std.heap.GeneralPurposeAllocator(.{}){};
+};
+
+
+
+
+ +
+

2.7 TODO Failing allocator

+
+

+The failing allocator can be used to ensure that the error.OutOfMemory is well handled. +

+ +

+The failling allocator need to have a child allocator to run. +In fact, the failing allocator can set in his init function the number of allocation that will be performed without errors (see the numberOfAllocation variable). +This pattern is pretty useful in restricted memory environments such as embedded development. +

+
+
test "test alloc falling" {
+  const numberOfAllocation = 0;
+  var failingAlloc = std.testing.FailingAllocator.init(std.testing.allocator, numberOfAllocation);
+  var list = std.ArrayList(i32).init(failingAlloc.allocator());
+  defer list.deinit();
+
+  const expected = 45;
+
+  try std.testing.expectError(std.mem.Allocator.Error.OutOfMemory, list.append(expected));
+}
+
+
+
+
+ +
+

2.8 TODO C allocator

+
+

+The C standard allocator can also be used, this allocator has high performance but it has less safety feature. +

+ +

+However, to use this allocator, the libC is required. +Adding the libC in the project will add more dependencies. +

+
+
+ + +
+

2.9 TODO How to use Zig to detect memory leaks

+
+
+
+

2.9.1 TODO Comparison between gcc-utils sanitizer, Valgrind, and Zig memory leak detection

+
+
+
+
+
+ +
+

3 Comptime

+
+

+Zig has a concept called comptime, it's stands for "compile-time". +Comptime is used to evaluate an expression at compile time and not at runtime. +In comparison with C, Zig comptime has the purpose of replacing marco with a more explicit syntax. +In fact, C's macro tends to be error-prone when using it. +The advantage of using comptime over macro is the type safety of Zig when writing comptime. +

+
+ +
+

3.1 When to use and when NOT to use it

+
+
+
+

3.1.1 When to use it

+
+

+Because the compiler can now do things at compilation time, leaving less work to be done at runtime. (if the compiler gives you the power to do things at compile time so easily, you should use it ^^) +

+ +

+So you should basically try to use it as much as you can. +

+
+
+ +
+

3.1.2 When NOT to use it (source: ziglings)

+
+

+The following contexts are already IMPLICITLY evaluated at +compile time, and adding the 'comptime' keyword would be +superfluous, redundant, and smelly: +

+ +
    +
  • The container-level scope (outside of any function in a source file)
  • +
  • Type declarations of: +
      +
    • Variables
    • +
    • Functions (types of parameters and return values)
    • +
    • Structs
    • +
    • Unions
    • +
    • Enums
    • +
  • +
  • The test expressions in inline for and while loops
  • +
  • An expression passed to the @cImport() builtin
  • +
+
+
+
+ +
+

3.2 Compile-time evaluation

+
+
+
+

3.2.1 Compile-time variable

+
+

+In Zig, there are variables that can be evaluated at compile-time, in fact, Zig allows computing mutating variables at compile-time but all the inputs need to be known also at compile-time. +If not, the compiler will throw a compile error. [cite:@DocumentationZigProgramming] +

+ +

+For a mutating variable at compile-time, Zig requires naming the variables with a comptime var. +But if the variable is a constant, the compiler requires to use a const. +

+ +

+Like in the example below, the variable named variableAtCompileTime is evaluated at compile-time because all the inputs are known. +On the other hand, the variable named constantAtRuntime cannot be a comptime variable because its dependency is based on unknown before runtime. +

+ +

+Moreover, in the example, the inline for is used to unroll the for loop. +This allows to use for loops in comptime indeed, if a standard for loop is used, it will cause an error because the capture value will be evaluated at runtime.[cite:@DocumentationZigProgramming] +

+
+
const stdout = std.io.getStdOut().writer();
+
+const randVariable = std.crypto.random.float(f32);
+const selectedConstant = 6;
+
+const constantAtRuntime = randVariable * selectedConstant;
+
+comptime var variableAtCompileTime = selectedConstant * selectedConstant;
+const array = [_]comptime_int { 3, 2, 1};
+
+inline for (array) |item| {
+    variableAtCompileTime += item;
+}
+
+try stdout.print("constant-at-runtime {d:.2}\n", .{constantAtRuntime});
+try stdout.print("variable-at-compile-time  {d}", .{variableAtCompileTime});
+
+
+
+ + + + +++ ++ + + + + + + + + + + + +
constant-at-runtime4.42
variable-at-compile-time42
+
+
+ +
+

3.2.2 Compile-time expression

+
+

+In Zig, an expression can have a comptime to tell the compiler to evaluate the expression at compile-time. +Like a compile-time variable, if an expression cannot be evaluated at compile-time, a compile-time error will be thrown. +

+ +

+With a prefixed comptime keyword Zig can interpret a function at compile-time instead of runtime. [cite:@DocumentationZigProgramming] +

+ +

+A good example of demonstrating comptime expression is in the standard documentation [cite:@DocumentationZigProgramming]. +The results show that the comptime expression is faster than the runtime one when the code is executed (runtime) because the work has already been done. +But this will work only with code that hasn't runtime dependency code. +

+ +
+
const Timer = std.time.Timer;
+const stdout = std.io.getStdOut().writer();
+
+fn fib(iteration: u32) u32 {
+    if (iteration < 2) return iteration;
+
+    return fib(iteration - 1) + fib(iteration - 2);
+}
+
+test "comptime fib" {
+    var timer = try Timer.start();
+    const result = comptime fib(15);
+    const elapsed = timer.read();
+    try stdout.print("Elasped-comptime: {d:0.2}ns\n", .{elapsed});
+
+    try std.testing.expectEqual(610, result);
+}
+
+test "fib" {
+    var timer = try Timer.start();
+    var result = fib(15);
+    const elapsed = timer.read();
+    try stdout.print("Elasped-runtime: {d:0.2}ns\n", .{elapsed});
+
+    try std.testing.expect(610 == result);
+}
+
+
+
+
+
+ +
+

3.2.3 Compile-time parameter

+
+

+Zig implements generic by using duck typing at compile-time. +To use generic, Zig needs to know the type at compile-time. +

+
+
+fn greater(comptime T: type, array: []const T) !?T {
+    var max: ?T = null;
+    for (array) |item| {
+	if (max) |m| {
+	    if (m < item) {
+		max = item;
+	    }
+	} else {
+	    max = item;
+	}
+    }
+    return max;
+}
+
+test "should return the max of an i32 array" {
+    const intArray = [_]i32{ 2, 9, 4, 6, 7, 1};
+    const result = try greater(i32, &intArray);
+
+    try std.testing.expect(result == 9);
+}
+
+test "should return the max of an f32 array" {
+    const floatArray = [_]f32{ 2.34, 14.55, 4.12, 6.876, 7.111 };
+    const result = try greater(f32, &floatArray);
+
+    try std.testing.expect(result == 14.55);
+}
+
+
+
+
+ +

+But with duck typing, if the same method is used, an error will be thrown at compile time: +

+
+
test "should fail with bool" {
+    const boolArray = [_]bool{ true, false, true, true };
+    const result = greater(bool, &boolArray);
+}
+
+
+ +

+The error will be: +

+
+error: operator < not allowed for type 'bool'
+
+ +

+Moreover, comptime can also be used as a type definition. +For this, the function needs to return a type. +The example below is based on the zig guide [cite:@ComptimeZigGuide2024], it's shows that it can define a new type with a function. +

+
+
fn Matrix(
+    comptime T: type,
+    comptime width: comptime_int,
+    comptime height: comptime_int,
+) type {
+    return [height][width]T;
+}
+
+fn Matrix3x3(
+    comptime T: type,
+) type {
+    return Matrix(T, 3, 3);
+}
+
+test "returning a type" {
+    try std.testing.expect(Matrix(f32, 4, 4) == [4][4]f32);
+}
+
+test "returning a 3x3 matrix" {
+    try std.testing.expect(Matrix3x3(f32) == [3][3]f32);
+}
+
+
+
+
+
+
+ +
+

3.3 Metaprogramming

+
+
+
+

3.3.1 @TypeOf

+
+

+The @TypeOf builtin function can be used to take as a parameter an expression and return a type. +

+
+
+ +
+

3.3.2 @typeInfo

+
+

+This built-in function provides type reflection, it returns information on type. +

+ +

+See the example Example with a custom CSV writer based on type to have a view of the usability. +

+
+
+
+ +
+

3.4 TODO Optimization

+
+

+inline for / while +

+
+ +
+

3.4.1 How log works in Zig

+
+

+In the C language, a common use to use debug print is with Marco. +Like in this example, if the DEBUG is defined to 1 the code will print the debug info. +If the DEBUG is not set, at the compilation, all the print information will be removed. +

+
+
+#define DEBUG 1
+
+#if DEBUG 
+#define TRACE(x) printf x;
+#else
+#define TRACE(x)
+#endif
+
+int main() {
+  TRACE(("Hello World! : %d\n", 12));
+}
+
+
+ +

+In Zig, logging uses this same principle, a message level is set at the start of the program (at compile-time) and if the log is not enabled, all the code about the print is removed. +However, if the log level is greater than the limit, the message will be printed. +

+ +

+The code below shows an extract of the standard library for logging. +

+
+
 fn log(
+    comptime message_level: Level,
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: anytype,
+) void {
+    if (comptime !logEnabled(message_level, scope)) return;
+
+    std.options.logFn(message_level, scope, format, args);
+}
+
+
+ +

+In addition, Zig provides some helper functions for logging, such as : +

+
    +
  • std.log.debug
  • +
  • std.log.info
  • +
  • std.log.warn
  • +
  • std.log.err
  • +
+ +

+And if the release mode is set to Debug, the debug log will be printed. +But if the release mode is set to Release*, the debug log will not print, there is no need to configure the logging to have this behavior. +

+
+
+
+ +
+

3.5 Generic data structures

+
+

+To create a generic data structure, the same pattern is used as a comptime parameter. +A function needs to return an anonymous struct as a type type. +

+ +

+In a generic data structure, the @This() is used to get the type of the data structure because it is anonymous. +

+ +

+Moreover, a generic data structure can have two type of function: +

+
    +
  1. a function that can be called on the structure type
  2. +
  3. a function that can be called on the instance of the structure.
  4. +
+ +

+To have an instance function, the first argument needs to be a parameter of the type of the struct. +That's why a constant Self is used with @This(). +And after that, the parameter self can be used to get the members of the struct. +

+ +

+The example shows the difference between a function that can be called on a struct and a function that can be called on an instance of a struct. +

+
+
pub fn MyStruct(comptime T: type) type {
+    return struct {
+	const Self = @This();
+
+	myNumber: T,
+
+	pub fn structFunction(writer: anytype) !void {
+	    try writer.print("structFunction\n", .{});
+	}
+
+	pub fn instanceFunction(self: *Self, writer: anytype) !void {
+	    try writer.print("structInstance: {d}\n", .{self.myNumber});
+	}
+    };
+}
+
+pub fn main() !void {
+    const stdout = std.io.getStdOut().writer();
+
+    try MyStruct(f32).structFunction(stdout);
+
+    var myStruct = MyStruct(f32){
+	.myNumber = 42,
+    };
+
+    try myStruct.instanceFunction(stdout);
+}
+
+
+
+ +

+In Zig, a structure name can be explicitly given or Zig can infer the name of a struct when there are created: +

+
+
fn MyStruct(comptime T: type) type {
+    return struct {
+	myNumber: T,
+    };
+}
+
+pub fn main() !void {
+    // The structure name is infered
+    const myStruct1 = MyStruct(i32) {
+	.myNumber = 42,
+    };
+    _ = myStruct1;
+
+    // The structure has a explicit name
+    const intStruct = MyStruct(i32);
+    const myStruct2 =  intStruct {
+	.myNumber = 42,
+    };
+    _ = myStruct2;
+}
+
+
+ +

+Here's an compete example of an generic linked list : +

+
+
pub fn LinkedList(comptime T: type) type {
+    return struct {
+	const Node = struct {
+	    data: T,
+	    prev: ?*Node,
+	    next: ?*Node,
+	};
+
+	const LinkedListError = error{headNull};
+	const Self = @This();
+	allocator: std.mem.Allocator,
+	head: ?*Node,
+	len: u32 = 0,
+
+	pub fn init(allocator: std.mem.Allocator) Self {
+	    return Self{
+		.head = null,
+		.allocator = allocator,
+	    };
+	}
+
+	pub fn deinit(self: *Self) void {
+	    var curr = self.head;
+
+	    while (curr) |currNotNull| {
+		const node = currNotNull;
+		curr = currNotNull.next;
+		self.allocator.destroy(node);
+	    }
+	    self.len = 0;
+	}
+
+	pub fn push(self: *Self, value: T) !void {
+	    var node = try self.allocator.create(Node);
+	    node.*.data = value;
+	    self.len += 1;
+
+	    if (self.head) |head| {
+		node.next = head;
+		head.prev = node;
+		self.head = node;
+	    } else {
+		self.head = node;
+		node.*.next = null;
+		node.*.prev = null;
+	    }
+	}
+    };
+}
+
+test "Should push one item into a i32 list" {
+    const intLinkedList = LinkedList(i32);
+    var list = intLinkedList.init(std.testing.allocator);
+    defer list.deinit();
+
+    const expected = 42;
+
+    try list.push(expected);
+    const result = list.head.?.data;
+
+    try std.testing.expect(expected == result);
+}
+
+test "Should push one item into a f32 list" {
+    const intLinkedList = LinkedList(f32);
+    var list = intLinkedList.init(std.testing.allocator);
+    defer list.deinit();
+
+    const expected = 3.1415;
+
+    try list.push(expected);
+    const result = list.head.?.data;
+
+    try std.testing.expect(expected == result);
+}
+
+
+
+
+ +
+

3.6 Example with a custom CSV writer based on type

+
+

+This example shows that Zig has a type reflection with the keyword @typeInfo. +The goal of this example is to create CSV output with a generic struct as input. +Only with the try csv.stringify(&arrayList, stream.writer()); function the CsvWriter can infer at comptime the struct pass as argument. +For this example, a basic struct named Person will be transformed to CSV. +

+
+
pub fn CsvWriter(comptime T: type) type {
+    return struct {
+	const Self = @This();
+
+	const Config = struct {
+	    separator: u8 = ',',
+	};
+	config: Config,
+
+	pub fn init(config: Config) Self {
+	    return Self{
+		.config = config,
+	    };
+	}
+
+	pub fn stringify(self: *Self, arrayList: *std.ArrayList(T), writer: anytype) !void {
+	    try writeHeader(self, &writer);
+	    for (arrayList.items) |item| {
+		try writeType(self, item, &writer);
+	    }
+	}
+
+	fn writeHeader(self: *Self, writer: anytype) !void {
+	    const fields = std.meta.fields(T);
+
+	    inline for (fields, 1..) |field, i| {
+		try writer.print("{s}", .{field.name});
+		if (fields.len != i) {
+		    try writer.print("{c}", .{self.config.separator});
+		}
+	    }
+	    try writer.print("\n", .{});
+	}
+
+	fn writeType(self: *Self, item: T, writer: anytype) !void {
+	    const fields = std.meta.fields(T);
+
+	    if (@TypeOf(fields) != []const std.builtin.Type.StructField)
+		@compileError("The type is not the a struct");
+
+	    inline for (fields, 1..) |field, i| {
+		const f = @field(item, field.name);
+
+		switch (@typeInfo(@TypeOf(f))) {
+		    .Int => try writer.print("{d}", .{f}),
+		    .Float => try writer.print("{d}", .{f}),
+		    .Pointer => |pointer| {
+			if (pointer.size == std.builtin.Type.Pointer.Size.Slice and pointer.child == u8) {
+			    try writer.print("{s}", .{f});
+			} else {
+			    @compileError("Currently, the CsvWriter dosen't support complex types");
+			}
+		    },
+		    else => @compileError("Currently, the CsvWriter dosen't support complex types"),
+		}
+
+		if (fields.len != i) {
+		    try writer.print("{c}", .{self.config.separator});
+		}
+	    }
+	    try writer.print("\n", .{});
+	}
+    };
+}
+
+const Person = struct {
+    sexe: []const u8,
+    name: []const u8,
+    date: u32,
+};
+
+
+pub fn main() !void {
+    const stdout = std.io.getStdOut().writer();
+    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+
+    const person1 = .{ .sexe = "M", .name = "Lucas", .date = 2000 };
+    const person2 = .{ .sexe = "F", .name = "Ava", .date = 2020 };
+    const person3 = .{ .sexe = "F", .name = "Sophia", .date = 1989 };
+
+    var arrayList = std.ArrayList(Person).init(gpa.allocator());
+    defer arrayList.deinit();
+
+    try arrayList.append(person1);
+    try arrayList.append(person2);
+    try arrayList.append(person3);
+
+    var buffer: [1024]u8 = undefined;
+    var stream = std.io.fixedBufferStream(buffer[0..]);
+
+    const personCsvWriter = CsvWriter(Person);
+    var csv = personCsvWriter.init(.{ .separator = ' ' });
+    try csv.stringify(&arrayList, stream.writer());
+
+    try stdout.print("{s}", .{stream.getWritten()});
+
+}
+
+
+
+ + + + +++ ++ ++ + + + + + + + + + + + + + + + + + + + + + + + + + +
sexenamedate
MLucas2000
FAva2020
FSophia1989
+
+
+ +
+

3.7 Bonus

+
+

+Here is a very nice blog written by a core member of the ZIG community if you want to dig further: https://kristoff.it/blog/what-is-zig-comptime/ +

+ +

+https://ziglang.org/documentation/master/#cImport-vs-translate-c +

+ +

+TODO enlever le import std car est juste un exemple de code + :imports '(std) +

+ +

+cImport doit etre fait une seule fois sinon on peut avoir des générations à double (2 import qui devraient etre les memes vont creer 2 types différents) +

+ +

+on utilise casi tout le temps les boucles while sauf si on itere sur une liste +

+ +

+beaucoup de conversion explicites (aucune conversion implicite en zig sauf si on elargit le type -> zig ne nous cache rien) +

+
+
+
+ +
+

4 Zig and C

+
+
+
+

4.1 Main differences between Zig and C in the syntax

+
+

+There are a few main differences between the 2 languages that you have high chance of encountering but obviously not all of them are listed here. +

+
+ +
+

4.1.1 Types

+
+

+Here you can find all the primitive types in Zig and their C equivalent: +

+ +

+https://ziglang.org/documentation/master/#Primitive-Types +

+ +

+https://ziglang.org/documentation/master/#C-Type-Primitives +

+ +

+TODO would it be better if i do the array in the doc rather than having a link to it ? +

+
+
+ +
+

4.1.2 Loops

+
+

+In Zig, the for loop is not used as much as in C. +Instead, the while loop is used more often. The for loop is used when iterating over multiples elements of a container (typically slices or arrays), so it basically is a kind of foreach loop. In all the other cases the while loop is used. +

+ +
+
const items = [_]u16 { 1, 4, 0, 1 };
+var sum: u16 = 0;
+for (items) |value| {
+    sum += value;
+}
+std.debug.print("Sum: {}\n", .{sum});
+
+
+ +
+
var i: usize = 0;
+while (i < 3) {
+    i += 1;
+    std.debug.print("i: {}\n", .{i});
+}
+
+
+
+
+ +
+

4.1.3 Pointers

+
+

+Zig has 2 different pointers: +

+
    +
  • Single-item pointers: *T
  • +
  • Many-item pointers: [*]T
  • +
+

+Which can both be optional by adding a "?". +

+ +

+But actually…. there is a third pointer type: +

+
    +
  • The C pointer: [*c]T
  • +
+

+This one is to be avoided as much as possible. The only reasons for its existence is for the translation from C code to Zig code, when the translater is not able to know what to convert it to in Zig (eg. sometimes it does not know if it can convert it to a non-optional pointer or not which could cause UBs). +

+
+
+ +
+

4.1.4 Type conversions

+
+

+Since in Zig there is no implicit conversions, depending on the project you might end up having "ugly" code with a lot of explicit type conversions. The code is more verbose but it is also less error-prone. +Exemple: +ZIG: +

+
+
fn rgb_to_grayscale_1d(img: *imageh.img_1D_t, result: *imageh.img_1D_t) void {
+    var i: usize = 0;
+    while (i < @as(usize, @intCast(img.height * img.width))) : (i += 1) {
+	const index = i * @as(usize, @intCast(img.components));
+	const grayscale_value: u8 = @intFromFloat(imageh.FACTOR_R * @as(f64, @floatFromInt(img.data[imageh.R_OFFSET + index])) +
+	    imageh.FACTOR_G * @as(f64, @floatFromInt(img.data[imageh.G_OFFSET + index])) +
+	    imageh.FACTOR_B * @as(f64, @floatFromInt(img.data[imageh.B_OFFSET + index])));
+	result.data[i] = grayscale_value;
+    }
+}
+
+
+

+C: +

+
+
void rgb_to_grayscale_1D(const struct img_1D_t *img, struct img_1D_t *result) {
+  printf("height: %d", img->height);
+  for (size_t i = 0; i < img->height * img->width; i++) {
+    int index = i * img->components;
+    uint8_t grayscale_value = (uint8_t)(FACTOR_R * img->data[R_OFFSET] +
+					FACTOR_G * img->data[G_OFFSET] +
+					FACTOR_B * img->data[B_OFFSET]);
+    result->data[i] = grayscale_value;
+  }
+}
+
+
+
+
+
+ + +
+

4.2 How to call a C function from Zig

+
+

+Add those lines to your build.zig file: +

+
+
exe.addIncludePath(.{ .path = "c-src" }); // Folder containing the C files
+exe.linkLibC(); // Link the C standard library (which is zig own libc btw)
+
+
+ +

+Then you can call the C functions like this from your Zig code: +

+
+
const std = @import("std");
+const c_hello = @cImport({
+    @cInclude("hello.c");
+});
+
+pub fn main() !void {
+    c_hello.hello();
+
+    const res = c_hello.add(1, 2);
+    std.debug.print("1 + 2 = {d}\n", .{res});
+}
+
+
+

+Note that you can only do 1 @cImport per project. So what i recommend you to do is create a file containing all the c libraries you need in a file like so: +

+
+
pub const c = @cImport({
+    @cInclude("stdio.h");
+    @cInclude("stdlib.h");
+    @cInclude("image.h");
+});
+
+
+

+Then call this zig file in your other zig files. +

+
+
+ +
+

4.3 How to call a Zig function from C / Continuing a C project with Zig

+
+

+You can continue your C project without using Clang or GCC but by using Zig with all its toolchain it comes with. +

+ +

+In order to have a c file (main.c) as the entry point of your project using the zig build tool you have to add/comment the following lines to your build.zig file: +

+
+
const exe = b.addExecutable(.{
+    .name = "c_project",
+    // .root_source_file = b.path("src/main.zig"),
+    .target = target,
+    .optimize = optimize,
+});
+exe.root_module.addCSourceFile(.{ .file = .{ .path = "src/main.c" }, .flags = &.{"-std=c99"} });
+exe.linkLibC();
+
+
+ +

+If you want to have more C files than just main.c you can add them like so: +

+
+
exe.addCSourceFile(.{ .file = .{ .path = "c-src/image.c" }, .flags = &.{"-std=c99"} });
+
+
+ +

+TODO pros and cons of using zig toolchain instead of clang or gcc +

+
+
+ +
+

4.4 How is it done under the hood

+
+

+When you do @cImport(@cInclude("foo.h")) in your zig code it runs translate-c and exposes the function and type definitions. +

+ +

+TODO talk about C ABI +

+
+
+
+

4.5 Util to translate C code to Zig

+
+

+zig translate-c is an util built in the zig toolchains that allows you to translate C code to Zig code. +You can translate any code but the code is going to be UNREADABLE, so I would not recommend this tool if you plan on modifying the code afterwards. +You have better time importing the C code in your Zig code. +Note that if you want to translate a C file that uses the libc you have to add the -lc flag like so: +

+
+
zig translate-c main.c -lc
 
+ +
+

4.5.1 Comparison with other langauges that use C code

+
+

+To test if integrating C code in Zig projects is really as seemless as some claims, I have decided to compare the C integration with Python aswell. +

+ +

+In order to do that I wrote a small C library: +

+
+
int add(int a, int b) { return a + b; }
+
+

+What I am going to do is test how much time it takes each program to run this function x times. (in this case x = 100'000'000) +

-
-

2.6 Testing allocator

-

-The testing allocator is available in tests and the test runner will report all the memory leaks that have occurred during testing.[cite:@LearningZigHeap] [cite:@ZiglangZig2024] +Note: I didnt use any optimization in flag in python (because it did not change anything) and neither in Zig because I didnt want the compiler to try to optimize the code and be lazy.

-The example below shows how to use the testing allocator. +Then in order to compare the 2 languages I wrote 4 programs:

+
    +
  • Zig code that has and add function implementation in Zig
  • +
-
test "Test ArrayList" {
-    var array = std.ArrayList(i32).init(std.testing.allocator);
-    defer array.deinit();
+
const std = @import("std");
 
-    const expected: i32 = 42;
-    try array.append(expected);
+fn add(a: u32, b: u32) u32 {
+    return a + b;
+}
 
-    try std.testing.expectEqual(expected, array.items[0]);
+pub fn main() !void {
+    var i: usize = 0;
+    while (i < 100000000) : (i += 1) {
+	_ = add(3, 7);
+    }
+    std.debug.print("done\n", .{});
 }
 
-

-If the code below is run, the test will fail and it will display a leaked test memory. -Zig will help the programmer to detect memory leaks using code tests. +Result: ~0.38sec

+ +
    +
  • Vanilla Python code that has and add function implementation in Python
  • +
-
test "Test ArrayList" {
-    var array = std.ArrayList(i32).init(std.testing.allocator);
-    //defer array.deinit(); -> the array will not be free
+
def add(a, b):
+    return a + b
 
-    const expected: i32 = 42;
-    try array.append(expected);
 
-    try std.testing.expectEqual(expected, array.items[0]);
-}
+for i in range(100000000):
+    add(3, 7)
+print("done!")
 
-

-Under the hood, the testing allocator is an instance of the general purpose allocator. -Below, an extract of testing allocator of the standard library testing.zig. -If the testing allocator is used outside of the tests, a compilation error will be thrown. +Result: ~10sec

+ +
    +
  • Zig code that imports the C library
  • +
-
/// This should only be used in temporary test programs.
-pub const allocator = allocator_instance.allocator();
-pub var allocator_instance = b: {
-    if (!builtin.is_test)
-	@compileError("Cannot use testing allocator outside of test block");
-    break :b std.heap.GeneralPurposeAllocator(.{}){};
-};
+
const std = @import("std");
+pub const c = @cImport({
+    @cInclude("mylib.c");
+});
+
+pub fn main() !void {
+    var i: usize = 0;
+    while (i < 100000000) : (i += 1) {
+	_ = c.add(3, 7);
+    }
+    std.debug.print("done!\n", .{});
+}
 
-
-
- -
-

2.7 TODO Failing allocator

-

-The failing allocator can be used to ensure that the error.OutOfMemory is well handled. +Result: ~0.41sec

-

-The failling allocator need to have a child allocator to run. -In fact, the failing allocator can set in his init function the number of allocation that will be performed without errors (see the numberOfAllocation variable). -This pattern is pretty useful in restricted memory environments such as embedded development. -

+
    +
  • Python code that imports the C library
  • +
-
test "test alloc falling" {
-  const numberOfAllocation = 0;
-  var failingAlloc = std.testing.FailingAllocator.init(std.testing.allocator, numberOfAllocation);
-  var list = std.ArrayList(i32).init(failingAlloc.allocator());
-  defer list.deinit();
+
import ctypes
 
-  const expected = 45;
+mylib = ctypes.CDLL('./mylib.so')
 
-  try std.testing.expectError(std.mem.Allocator.Error.OutOfMemory, list.append(expected));
-}
+mylib.add.argtypes = (ctypes.c_int, ctypes.c_int)
+mylib.add.restype = ctypes.c_int
+
+for i in range(100000000):
+    result = mylib.add(3, 4)
+
+print("Result of last addition:", result)
 
-
+

+Result: ~50sec +

+ +

+TODO faire un joli graphique quand jaurai d autres languages +TODO idk if it would be possible to compile my c library and then use it in my python code, and all of that all in emacs, that would be cool :) +TODO do with other languages aswell +

-
-

2.8 TODO C allocator

-
+
    +
  1. Conclusion
    +

    -The C standard allocator can also be used, this allocator has high performance but it has less safety feature. +First thing that we notice immediately is how much faster the Zig code is compared to the Python code. This is not surprising since Zig is a compiled language and Python is an interpreted language.

    -However, to use this allocator, the libC is required. -Adding the libC in the project will add more dependencies. +The second interesting thing is that the 2 Zig codes dont vary that much (if they even do) compared to the 2 python codes which have a 5x ratio. This is interesting because it shows that the overhead of calling a C function from Zig is not that big (in fact it is even not existent since all the compiler does is translating the C code to Zig code at compilation time). +

    + +

    +We can conclude that calling C code from Zig is really seemless, because at runtime … everything is Zig code.

    +
  2. +
+
+
+

4.6 Notes

+
+

+TODO For some unkown reason yet my LSP becomes very slow when working in a Zig project with C files and sometimes crashes. I have to investigate this further. +

-
-

2.9 TODO How to use Zig to detect memory leaks

-
-
-
-
-

3 Compilation - build system

-
+
+

5 Compilation - build system

+
-
-

3.1 TODO Comptime

+
+

5.1 TODO Comptime

-
-

3.2 TODO How to use the build system

+
+

5.2 TODO How to use the build system

-
-

3.3 TODO Build modes

-
+
+

5.3 TODO Build modes

+
-
-

3.3.1 TODO Build steps

-
+
+

5.3.1 TODO Build steps

+
-
-

3.3.2 TODO Generate automatically documentation

-
+
+

5.3.2 TODO Generate automatically documentation

+

In Zig there is an experimental feature, it's automatically generated documentation. It will scan all the public structures and functions and will create documentation. @@ -886,9 +2073,9 @@

3.3.2 -

3.3.3 Strip output binary in Zig in Linux

-
+
+

5.3.3 Strip output binary in Zig in Linux

+

ELF format In an ELF executable there are various sections that hold program and control information. @@ -967,9 +2154,9 @@

3.3.3 Strip output bin

-
-

3.3.4 Separate debug symbols from ELF executable

-
+
+

5.3.4 Separate debug symbols from ELF executable

+

The chapter above shows how to remove all the symbols for the executable directly in the Zig build system. In this section, the interest is more about the Zig objcopy command. @@ -1102,13 +2289,13 @@

3.3.4 Separate debug s

-
-

3.4 Cross-compilation

-
+
+

5.4 Cross-compilation

+
-
-

3.4.1 TODO Cross-compile with an embedded linux

-
+
+

5.4.1 TODO Cross-compile with an embedded linux

+

TODO: Add iterate programming to cross compile automaticlly the app

@@ -1149,47 +2336,58 @@

3.4.1 -

4 Concurrency

-
+
+

6 Concurrency

+
-
-

4.1 Definitions

-
+
+

6.1 Definitions

+

-Coroutine (cooperative multitasking): +Before diving into the different ways to do concurrency in ZIG, let's first define some terms that are useful to understand the basics of concurrency (not related to Zig).

+
+ +
+

6.1.1 Coroutine (cooperative multitasking)

+
    -
  • a coroutine itself is not concurrent !, it allows to manage concurrent tasks in the same way as callbacks for example
  • +
  • a coroutine in itself is not concurrent , it allows to manage concurrent tasks in the same way as callbacks for example
  • single threaded
  • allows to write code in a sequential manner
  • allows not to have context switch
  • 2 types of coroutine: symmetric vs asymmetric (relative to the control-transfer mechanism, explained a bit below)
  • coroutines are not preemptive, they are cooperative, therefore they must explicitly give back control by yielding
  • usually uses keywords like yield, resume, suspend, …
  • -
  • Coroutines can be either stackful or stackless, we are not gonna dive deep into this concept since most of the time you are going to use stackful coroutines since they allow you to suspend from within a nested stackframe (strength of stacless coroutines: efficiency)
  • +
  • Coroutines can be either stackful or stackless, we are not gonna dive deep into this concept since most of the time you are going to use stackful coroutines since they allow you to suspend from within a nested stackframe (the only strength of stackless coroutines: efficiency)
+
-

-Symmetric coroutines: -

+
    +
  1. Symmetric coroutines
    +
    • Their only control-transfer mechanism is:
    • explicitly passing control to another coroutine
    +
    +
  2. -

    -Asymmetric coroutines (called asymmetric because the control-transfer can go both ways): -

    +
  3. Asymmetric coroutines (called asymmetric because the control-transfer can go both ways)
    +
    • They have two control-transfer mechanisms:
    • invoking another coroutine which is going to be the subcoroutine of the calling coroutine
    • suspending itself and giving control back to the caller
    +
    +
  4. +
+
-

-Green threads (userland threads): -

+
+

6.1.2 Green threads (userland threads)

+
  • preemptive multitasking (PAS VRAI selon appel !)
  • managed by a VM/runtime instead of the OS
  • @@ -1197,48 +2395,57 @@

    4.1 Definitions

  • still use several native threads behind the scenes
  • used more for short-lived tasks
+
+
-

-Fibers : -

+
+

6.1.3 Fibers

+
  • Same as green threads but cooperative multitasking instead of preemptive multitasking
+
+
-

-Preemptive multitasking: -

+
+

6.1.4 Preemptive multitasking

+
    -
  • The underlying architecture (NOT us) is going to be deciding what to execute and when
  • +
  • The underlying architecture (NOT us, but the OS for exemple) is going to be deciding what to execute and when
  • This means that our threads can be interrupted at any time
+
+
-

-Cooperative multitasking: -

+
+

6.1.5 Cooperative multitasking

+
  • A thread will continue to run until it decides to give up control (yielding)
  • We are in full control of the execution flow of our threads
  • The drawback of this method is that YOU have to think and code in a way to not have starving threads for exemple
+
+
-

-Thread (multithreading -> the threads are still blocking): -

+
+

6.1.6 Thread

+
    +
  • Multithreading
  • +
  • Each threads are still blocking
  • Main and historical way of doing concurrency
  • Parallelism (eventually): Threads allow for true parallel execution on multi-core CPUs, enabling better utilization of hardware resources and potentially improving performance.
  • Isolation: Threads provide strong isolation between concurrent tasks, with each thread having its own execution context, stack, and local variables.
  • Scalability: Managing a large number of threads can be challenging and may lead to scalability issues, as the operating system kernel has to allocate resources for each thread.
  • Usually used with a thread pool to avoid the overhead of creating and destroying threads for each task which is very expensive
-

-QUESTION: est ce que je parle pas des virtual threads la ? -

+
+
-

-Event-driven programming (ex: NodeJS): -

+
+

6.1.7 Event-driven programming (ex: NodeJS)

+
  • Event loop that regularly checks if "events" are launched
  • Scalability: Event-driven architectures are inherently scalable, as they can handle a large number of concurrent connections with low memory and CPU overhead.
  • @@ -1246,50 +2453,87 @@

    4.1 Definitions

+
-
-

4.2 Zig current state

-
+
+

6.2 Zig current state

+

-There a multiple ways you currently can do concurent code in ZIG: +There are multiple ways you currently can do concurent code in ZIG, we are going to explore a few here:

-
-
-

4.2.1 Feedbacks from thoses methods:

-
+
+

6.2.1 OS threads (std)

+
+

+Spawning OS threads (https://ziglang.org/documentation/master/std/#std.Thread) +TODO exemple +

+
+
+ +
+

6.2.2 Old async/await

+
+

+Using old async/await from before 0.11 (not recommanded because unstable !!! https://github.com/ziglang/zig/issues/6025 and might probably never get back in the language https://ziglang.org/download/0.12.0/release-notes.html#AsyncAwait-Feature-Status) +

-
    -
  1. OS threads
  2. -
  3. Old async/await
  4. -
  5. libxev
  6. -
  7. CHOISIR UNE DES 2 fibers
  8. -
  9. zigcoro
  10. -
+
+ +
+

6.2.3 libxev

+
+

+Using an event loop (by wrapping libuv or using libxev which is the equivalent buz in ZIG) +TODO exemple +

-
-

4.3 Function coloring

-
+
+

6.2.4 Fibers

+

-Green threads make function colors disapear ???? (dependences entre threads) +Using fibers (https://github.com/kprotty/zefi, https://github.com/kassane/fiber) +TODO exemple

-
-

4.4 TODO MES NOTES ---–— pas besoin de lire ca, cest juste pour moi pour approfondir certains sujets plus tard

-
+
+

6.2.5 zigcoro

+
+

+async/await built on top of libxev (https://github.com/rsepassi/zigcoro) +TODO exemple +

+
+
+ +
+

6.2.6 Using C libraries

+
+

+… obviously you can still use C libraries that do async stuff :) +TODO exemple +

+
+
+
+ +
+

6.3 Function coloring

+
+

+Green threads make function colors disapear ???? (dependences entre threads) +

+ +

+TODO MES NOTES ---–— pas besoin de lire ca, cest juste pour moi pour approfondir certains sujets plus tard +

+
  • "libuv and OSes will usually run background/worker threads and/or polling to perform tasks in a non-blocking manner." est ce que cest comment ca under the hood les non blocking async ?
  • @@ -1298,9 +2542,9 @@

    4.4 -

    4.5 Sources:

    -
    +
    +

    6.4 Sources:

    +
    -
    -

    5 Web frameworks

    -
    +
    +

    7 Web frameworks

    +

    In this section we are going to explore the different ways of doing WEB servers in Zig.

    -
    -

    5.1 Zap

    -
    +
    +

    7.1 Zap

    +
    -
    -

    5.1.1 Description

    -
    +
    +

    7.1.1 Description

    +

    TODO Callback based, we define certain callbacks, we configure from there @@ -1355,21 +2599,21 @@

    5.1.1 Description

    -
    -

    5.2 HTTP from the std

    -
    +
    +

    7.2 HTTP from the std

    +
    -
    -

    5.2.1 Description

    -
    +
    +

    7.2.1 Description

    +

    Http.zig: Dispatcher based, you create the dispatch chai (As far as I understand it)

    -
    -

    5.2.2 Exemples

    -
    +
    +

    7.2.2 Exemples

    +

    TODO

    @@ -1377,18 +2621,18 @@

    5.2.2 Exemples

    -
    -

    5.3 Tokamak

    -
    +
    +

    7.3 Tokamak

    +

    Middleware (scoped) and DI Based

    -
    -

    5.4 Jetzig

    -
    +
    +

    7.4 Jetzig

    +

    Middleware (as part of a request chain) and Convention based

    @@ -1407,13 +2651,13 @@

    5.4 Jetzig

    -
    -

    6 Utils

    -
    +
    +

    8 Utils

    +
    -
    -

    6.1 Zig package manager

    -
    +
    +

    8.1 Zig package manager

    +

    Since 0.11, Zig has now an official built-in package manager named.

    @@ -1427,9 +2671,9 @@

    6.1 Zig package manage

    -
    -

    6.1.1 Add a package in your project

    -
    +
    +

    8.1.1 Add a package in your project

    +
    1. Add the packages you want in your build.zig.zon file
    @@ -1453,8 +2697,15 @@

    6.1.1 Add a package in

    -TODO: comment trouver le hash ? zig fetch avec 0.12 ou ? -Jai des erreurs Tls +With 0.12.0 you can now use the "zig fetch" command from the root of your zig project to fetch the packages you need: +

    +
    +
    zig fetch --save git+https://github.com/zigzap/zap/#HEAD
    +
    +
    + +

    +If you are prior to 0.12.0, you can write the "url" parameter without the "hash", then "zig build" and the outpout will give you the correct hash that you can then put back in your build.zig.zon file.

      @@ -1490,9 +2741,9 @@

      6.1.1 Add a package in

    -
    -

    6.1.2 Sources

    -
    + -
    -

    6.2 Zig version manager

    -
    +
    +

    8.2 Zig version manager

    +

    Using ZIG before 1.0 might require you to often switch between master and the last official release.

    @@ -1526,9 +2777,9 @@

    6.2 Zig version manage

    -
    -

    6.2.1 Basic usage

    -
    +
    +

    8.2.1 Basic usage

    +

    Installing the tool:

    @@ -1581,17 +2832,17 @@

    6.2.1 Basic usage

    -
    -

    7 Standard library

    -
    +
    +

    9 Standard library

    +
    -
    -

    7.1 TCP stream

    -
    +
    +

    9.1 TCP stream

    +
    -
    -

    7.1.1 TODO How Zig manages stream

    -
    +
    +

    9.1.1 TODO How Zig manages stream

    +

    Does Zig manage tcp stream with the OS stream or has it created its own implementation ?

    @@ -1599,32 +2850,32 @@

    7.1.1 -

    7.2 Threading

    -
    +
    +

    9.2 Threading

    +
    -
    -

    7.2.1 Compare Zig threading library with p_thread

    +
    +

    9.2.1 Compare Zig threading library with p_thread

    -
    -

    8 Others

    -
    +
    +

    10 Others

    +
    -
    -

    8.1 Reading external files

    -
    +
    +

    10.1 Reading external files

    +

    There are multiple ways to do it in ZIG:

    -
    -

    8.1.1 @embedFile

    -
    +
    +

    10.1.1 @embedFile

    +

    -This method is simply going to embed the file in the binary. +This method is simply going to embed the file in the binary at compile time.

    @@ -1642,9 +2893,9 @@

    8.1.1 @embedFile

    -
    -

    8.1.2 Use an allocator to dynamically store the content of the file

    -
    +
    +

    10.1.2 Use an allocator to dynamically store the content of the file

    +

    By using the method: readToEndAlloc

    @@ -1670,9 +2921,9 @@

    8.1.2 Use an allocator

    -
    -

    8.1.3 Read the file and put it in a buffer

    -
    +
    +

    10.1.3 Read the file and put it in a buffer

    +

    By using the method readAll

    @@ -1709,21 +2960,30 @@

    8.1.3 Read the file an

    -
    -

    8.2 Errors

    -
    +
    +

    10.2 Errors

    +

    https://ziglang.org/documentation/0.11.1/#Errors +

    + +

    In ZIG errors are juste values. +

    + +

    Those values are those of an special type of enum "error". +

    + +

    When you declare your enum using this keyword instead of "enum", the compiler is going to know that those values are going to be errors, therefore they can be catched, tried, …

    -
    -

    8.2.1 Handling errors in the application flow

    -
    +
    +

    10.2.1 Handling errors in the application flow

    +

    Functions can return either a value or an error, this is done by adding the "!" to the return type of a function With the "!" the return type of the function below is the coerced "anyerror!u8" @@ -1739,7 +2999,7 @@

    8.2.1 Handling errors

    -
    fn maybeErrorMaybeValue(isError: bool) !u8 {
    +
    fn maybeErrorMaybeValue(isError: bool) !u8 {
         // ... might return an u8 or an error
         if (isError) {
     	return error.Bar;
    @@ -1766,15 +3026,15 @@ 

    8.2.1 Handling errors

    -
    -

    8.2.2 Errdefer

    -
    +
    +

    10.2.2 Errdefer

    +

    This exemple is taken from: https://ziglang.org/documentation/0.11.1/#errdefer Errdefer is a particularity of ZIG, it allows the user to execute some code when the function returns an error, it is useful exemple for deallocating variables you would have normally returned from the function, but since the function failed you deallocate that memory to avoid a memory leak.

    -
    fn createFoo(param: i32) !Foo {
    +
    fn createFoo(param: i32) !Foo {
         const foo = try tryToAllocateFoo();
         // now we have allocated foo. we need to free it if the function fails.
         // but we want to return it if the function succeeds.
    @@ -1797,15 +3057,15 @@ 

    8.2.2 Errdefer

    -
    -

    8.2.3 Coercing

    -
    +
    +

    10.2.3 Coercing

    +

    The rule is simple: you can coerce an error from a subset to a superset but you cannot coerce an error from a superset to a subset

    -
    const SuperErrors = error{
    +
    const SuperErrors = error{
         Foo,
         Bar,
         Baz,
    @@ -1837,15 +3097,15 @@ 

    8.2.3 Coercing

    -
    -

    9 Bibliography

    -
    +
    +

    11 Bibliography

    +

    Author: tetratrux

    -

    Created: 2024-04-25 gio 19:13

    +

    Created: 2024-04-26 ven 10:13

    Validate

    diff --git a/docs/index.org b/docs/index.org index 0f3bcd1..057649c 100644 --- a/docs/index.org +++ b/docs/index.org @@ -8,6 +8,10 @@ #+INCLUDE: "./allocators.org" :minlevel 1 +#+INCLUDE: "./comptime.org" :minlevel 1 + +#+INCLUDE: "./zig-and-c.org" :minlevel 1 + #+INCLUDE: "./build-system.org" :minlevel 1 #+INCLUDE: "./concurrency.org" :minlevel 1 diff --git a/docs/introduction.org b/docs/introduction.org index fd08800..592a04b 100644 --- a/docs/introduction.org +++ b/docs/introduction.org @@ -14,6 +14,8 @@ Zig offers different allocators that have different purposes, but some can detec In the sections below, certain aspects of this programming language will be explored in greater depth. +Note that this documentation is generated from emacs with org-mode and that you can find the Github repository [[https://pismice.github.io/HEIG_ZIG/][here]]. + ** What this documentation is not This is *not* a guide to learn Zig as first-language, this documentation wants to go in-depth in certain parts of the language. diff --git a/docs/zig-and-c.org b/docs/zig-and-c.org new file mode 100644 index 0000000..4902473 --- /dev/null +++ b/docs/zig-and-c.org @@ -0,0 +1,250 @@ + +https://ziglang.org/documentation/master/#cImport-vs-translate-c + +TODO enlever le import std car est juste un exemple de code + :imports '(std) + +cImport doit etre fait une seule fois sinon on peut avoir des générations à double (2 import qui devraient etre les memes vont creer 2 types différents) + +on utilise casi tout le temps les boucles while sauf si on itere sur une liste + +beaucoup de conversion explicites (aucune conversion implicite en zig sauf si on elargit le type -> zig ne nous cache rien) + +** Zig and C +*** Main differences between Zig and C in the syntax +There are a few main differences between the 2 languages that you have high chance of encountering but obviously not all of them are listed here. + +**** Types +Here you can find all the primitive types in Zig and their C equivalent: + +https://ziglang.org/documentation/master/#Primitive-Types + +https://ziglang.org/documentation/master/#C-Type-Primitives + +TODO would it be better if i do the array in the doc rather than having a link to it ? + +**** Loops +In Zig, the for loop is not used as much as in C. +Instead, the while loop is used more often. The for loop is used when iterating over multiples elements of a container (typically slices or arrays), so it basically is a kind of foreach loop. In all the other cases the while loop is used. + +#+begin_src zig :imports '(std) :main 'yes :testsuite 'no +const items = [_]u16 { 1, 4, 0, 1 }; +var sum: u16 = 0; +for (items) |value| { + sum += value; +} +std.debug.print("Sum: {}\n", .{sum}); +#+end_src + +#+begin_src zig :imports '(std) :main 'yes :testsuite 'no +for (0..3) |i| { + i += 1; + std.debug.print("i: {}\n", .{i}); +} +#+end_src + +**** Pointers +Zig has 2 different pointers: +- Single-item pointers: *T +- Many-item pointers: [*]T +Which can both be optional by adding a "?". + +But actually.... there is a third pointer type: +- The C pointer: [*c]T +This one is to be avoided as much as possible. The only reasons for its existence is for the translation from C code to Zig code, when the translater is not able to know what to convert it to in Zig (eg. sometimes it does not know if it can convert it to a non-optional pointer or not which could cause UBs). + +**** Type conversions +Since in Zig there is no implicit conversions, depending on the project you might end up having "ugly" code with a lot of explicit type conversions. The code is more verbose but it is also less error-prone. +Exemple: +ZIG: +#+begin_src zig +fn rgb_to_grayscale_1d(img: *imageh.img_1D_t, result: *imageh.img_1D_t) void { + var i: usize = 0; + while (i < @as(usize, @intCast(img.height * img.width))) : (i += 1) { + const index = i * @as(usize, @intCast(img.components)); + const grayscale_value: u8 = @intFromFloat(imageh.FACTOR_R * @as(f64, @floatFromInt(img.data[imageh.R_OFFSET + index])) + + imageh.FACTOR_G * @as(f64, @floatFromInt(img.data[imageh.G_OFFSET + index])) + + imageh.FACTOR_B * @as(f64, @floatFromInt(img.data[imageh.B_OFFSET + index]))); + result.data[i] = grayscale_value; + } +} +#+end_src +C: +#+begin_src c +void rgb_to_grayscale_1D(const struct img_1D_t *img, struct img_1D_t *result) { + printf("height: %d", img->height); + for (size_t i = 0; i < img->height * img->width; i++) { + int index = i * img->components; + uint8_t grayscale_value = (uint8_t)(FACTOR_R * img->data[R_OFFSET] + + FACTOR_G * img->data[G_OFFSET] + + FACTOR_B * img->data[B_OFFSET]); + result->data[i] = grayscale_value; + } +} +#+end_src + + +*** How to call a C function from Zig +Add those lines to your build.zig file: +#+begin_src zig +exe.addIncludePath(.{ .path = "c-src" }); // Folder containing the C files +exe.linkLibC(); // Link the C standard library (which is zig own libc btw) +#+end_src + +Then you can call the C functions like this from your Zig code: +#+begin_src zig +const std = @import("std"); +const c_hello = @cImport({ + @cInclude("hello.c"); +}); + +pub fn main() !void { + c_hello.hello(); + + const res = c_hello.add(1, 2); + std.debug.print("1 + 2 = {d}\n", .{res}); +} +#+end_src +Note that you can only do 1 @cImport per project. So what i recommend you to do is create a file containing all the c libraries you need in a file like so: +#+begin_src zig +pub const c = @cImport({ + @cInclude("stdio.h"); + @cInclude("stdlib.h"); + @cInclude("image.h"); +}); +#+end_src +Then call this zig file in your other zig files. + +*** How to call a Zig function from C / Continuing a C project with Zig +You can continue your C project without using Clang or GCC but by using Zig with all its toolchain it comes with. + +In order to have a c file (main.c) as the entry point of your project using the zig build tool you have to add/comment the following lines to your build.zig file: +#+begin_src zig +const exe = b.addExecutable(.{ + .name = "c_project", + // .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, +}); +exe.root_module.addCSourceFile(.{ .file = .{ .path = "src/main.c" }, .flags = &.{"-std=c99"} }); +exe.linkLibC(); +#+end_src + +If you want to have more C files than just main.c you can add them like so: +#+begin_src zig +exe.addCSourceFile(.{ .file = .{ .path = "c-src/image.c" }, .flags = &.{"-std=c99"} }); +#+end_src + +TODO pros and cons of using zig toolchain instead of clang or gcc + +*** How is it done under the hood +When you do @cImport(@cInclude("foo.h")) in your zig code it runs translate-c and exposes the function and type definitions. + +TODO talk about C ABI + +*** Util to translate C code to Zig +zig translate-c is an util built in the zig toolchains that allows you to translate C code to Zig code. +You can translate any code but the code is going to be UNREADABLE, so I would not recommend this tool if you plan on modifying the code afterwards. +You have better time importing the C code in your Zig code. +Note that if you want to translate a C file that uses the libc you have to add the -lc flag like so: +#+begin_src shell +zig translate-c main.c -lc +#+end_src + +**** Comparison with other langauges that use C code +To test if integrating C code in Zig projects is really as seemless as some claims, I have decided to compare the C integration with Python aswell. + +In order to do that I wrote a small C library: +#+begin_src c +int add(int a, int b) { return a + b; } +#+end_src + +What I am going to do is test how much time it takes each program to run this function x times. (in this case x = 100'000'000) + +Note: I didnt use any optimization in flag in python (because it did not change anything) and neither in Zig because I didnt want the compiler to try to optimize the code and be lazy. + +Then in order to compare the 2 languages I wrote 4 programs: +- Zig code that has and add function implementation in Zig +#+begin_src zig +const std = @import("std"); + +fn add(a: u32, b: u32) u32 { + return a + b; +} + +pub fn main() !void { + var i: usize = 0; + while (i < 100000000) : (i += 1) { + _ = add(3, 7); + } + std.debug.print("done\n", .{}); +} +#+end_src +Result: ~0.38sec + +- Vanilla Python code that has and add function implementation in Python +#+begin_src python +def add(a, b): + return a + b + + +for i in range(100000000): + add(3, 7) +print("done!") +#+end_src +Result: ~10sec + +- Zig code that imports the C library +#+begin_src zig +const std = @import("std"); +pub const c = @cImport({ + @cInclude("mylib.c"); +}); + +pub fn main() !void { + var i: usize = 0; + while (i < 100000000) : (i += 1) { + _ = c.add(3, 7); + } + std.debug.print("done!\n", .{}); +} +#+end_src +Result: ~0.41sec + +- Python code that imports the C library +#+begin_src python +import ctypes + +mylib = ctypes.CDLL('./mylib.so') + +mylib.add.argtypes = (ctypes.c_int, ctypes.c_int) +mylib.add.restype = ctypes.c_int + +for i in range(100000000): + result = mylib.add(3, 4) + +print("Result of last addition:", result) +#+end_src +Result: ~50sec + +TODO faire un joli graphique quand jaurai d autres languages +TODO idk if it would be possible to compile my c library and then use it in my python code, and all of that all in emacs, that would be cool :) +TODO do with other languages aswell + +***** Conclusion +First thing that we notice immediately is how much faster the Zig code is compared to the Python code. This is not surprising since Zig is a compiled language and Python is an interpreted language. + +TODO why is python so slow ? + +The second interesting thing is that the 2 Zig codes dont vary that much (if they even do) compared to the 2 python codes which have a 5x ratio. This is interesting because it shows that the overhead of calling a C function from Zig is not that big (in fact it is even not existent since all the compiler does is translating the C code to Zig code at compilation time). + +We can conclude that calling C code from Zig is really seemless, because at runtime ... everything is Zig code. + +*** Notes +TODO For some unkown reason yet my LSP becomes very slow when working in a Zig project with C files and sometimes crashes. I have to investigate this further. + +Sources: +- https://ziglang.org/documentation/master/#C-Pointers +- https://ziglang.org/documentation/master/#C-Type-Primitives +- https://zig.news/sobeston/using-zig-and-translate-c-to-understand-weird-c-code-4f8 +- https://mtlynch.io/notes/zig-call-c-simple/ diff --git a/docs/zig-package-manager.org b/docs/zig-package-manager.org index 1c37535..a4e0f76 100644 --- a/docs/zig-package-manager.org +++ b/docs/zig-package-manager.org @@ -23,8 +23,12 @@ In order to add packages to your project you are going to create and edit a file #+end_src You can get further understanding in how to write your build.zig.zon following the official doc: https://github.com/ziglang/zig/blob/master/doc/build.zig.zon.md -TODO: comment trouver le hash ? zig fetch avec 0.12 ou ? -Jai des erreurs Tls +With 0.12.0 you can now use the "zig fetch" command from the root of your zig project to fetch the packages you need: +#+begin_src shell +zig fetch --save git+https://github.com/zigzap/zap/#HEAD +#+end_src + +If you are prior to 0.12.0, you can write the "url" parameter without the "hash", then "zig build" and the outpout will give you the correct hash that you can then put back in your build.zig.zon file. 2. Add those added packages in your build.zig Simply add the following code after the "addExectuable" function