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">
- +page_allocator
)page_allocator
)Nowadays, multiple languages have emerged to compete with the well-established C language. @@ -228,10 +294,14 @@
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. +
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 @@
This documentation is built to guide to user through different examples. @@ -272,12 +342,12 @@
Use C-c C-c
(ctrl-c ctrl-c
) to evaluate the code below.
@@ -305,8 +375,8 @@
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 @@
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 @@
The Zig language doesn't hide the memory management, the mantra of Zig is to have no hidden control flow. @@ -399,8 +469,8 @@
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 @@
page_allocator
)page_allocator
)The page allocator is the basic allocator that will directly ask the OS for memory. @@ -467,8 +537,8 @@
The FixedBufferAllocator
will allocate memory into a fixed buffer, the size of the buffer needs to be known at comptime.
@@ -502,8 +572,8 @@
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 @@
Zig how arena allocator works @@ -602,8 +672,8 @@
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 @@
+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(.{}){}; +}; ++
+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)); +} ++
+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.
+
+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.
+
+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. +
++The following contexts are already IMPLICITLY evaluated at +compile time, and adding the 'comptime' keyword would be +superfluous, redundant, and smelly: +
+ ++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-runtime | +4.42 | +
variable-at-compile-time | +42 | +
+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); +} + ++
+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); +} + ++
@TypeOf
+The @TypeOf
builtin function can be used to take as a parameter an expression and return a type.
+
@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.
+
+inline for / while +
+
+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.
+
+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: +
+
+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); +} ++
+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()}); + +} + ++
sexe | +name | +date | +
M | +Lucas | +2000 | +
F | +Ava | +2020 | +
F | +Sophia | +1989 | +
+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) +
++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. +
++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 ? +
++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}); +} ++
+Zig has 2 different pointers: +
++Which can both be optional by adding a "?". +
+ ++But actually…. there is a third pointer type: +
++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). +
++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; + } +} ++
+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. +
++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 +
++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 +
++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
+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) +
--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:
+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
+ +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
+ +/// 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", .{}); +}
-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.
-
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 +
-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.
+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: +
+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 @@
ELF format
In an ELF
executable there are various sections that hold program and control information.
@@ -967,9 +2154,9 @@
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 @@
TODO: Add iterate programming to cross compile automaticlly the app
@@ -1149,47 +2336,58 @@-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).
+-Symmetric coroutines: -
+-Asymmetric coroutines (called asymmetric because the control-transfer can go both ways): -
+-Green threads (userland threads): -
+-Fibers : -
+-Preemptive multitasking: -
+-Cooperative multitasking: -
+-Thread (multithreading -> the threads are still blocking): -
+-QUESTION: est ce que je parle pas des virtual threads la ? -
+-Event-driven programming (ex: NodeJS): -
+-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:
-+Spawning OS threads (https://ziglang.org/documentation/master/std/#std.Thread) +TODO exemple +
++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) +TODO exemple +
-Green threads make function colors disapear ???? (dependences entre threads) +Using fibers (https://github.com/kprotty/zefi, https://github.com/kassane/fiber) +TODO exemple
+async/await built on top of libxev (https://github.com/rsepassi/zigcoro) +TODO exemple +
++… obviously you can still use C libraries that do async stuff :) +TODO exemple +
++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 +
+In this section we are going to explore the different ways of doing WEB servers in Zig.
TODO Callback based, we define certain callbacks, we configure from there @@ -1355,21 +2599,21 @@
Http.zig: Dispatcher based, you create the dispatch chai (As far as I understand it)
TODO
@@ -1377,18 +2621,18 @@Middleware (scoped) and DI Based
Middleware (as part of a request chain) and Convention based
@@ -1407,13 +2651,13 @@Since 0.11, Zig has now an official built-in package manager named.
@@ -1427,9 +2671,9 @@-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.
https://zig.news/edyu/zig-package-manager-wtf-is-zon-558e https://zig.news/edyu/zig-package-manager-wtf-is-zon-2-0110-update-1jo3 @@ -1506,9 +2757,9 @@
Using ZIG before 1.0 might require you to often switch between master and the last official release.
@@ -1526,9 +2777,9 @@Installing the tool:
@@ -1581,17 +2832,17 @@Does Zig manage tcp stream with the OS stream or has it created its own implementation ?
@@ -1599,32 +2850,32 @@p_thread
p_thread
There are multiple ways to do it in ZIG:
-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 @@
By using the method: readToEndAlloc
@@ -1670,9 +2921,9 @@By using the method readAll
@@ -1709,21 +2960,30 @@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, …
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 @@
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
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
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