commit 64be90de80dd0f47ed45503539cbb71250e15b90 Author: nothke Date: Thu Aug 8 20:39:18 2024 +0200 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..298c70a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/ +zig-*/ +.zig-*/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a9a393c --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +See [talk notes](talk.md) + +To run `zig build run` + +To build C samples, run: +- `zig cc c\string_append_or.c -o "bin\string_append_or.exe"` +- `zig cc c\malloc.c -o "bin\malloc.exe"` \ No newline at end of file diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..451e8ad --- /dev/null +++ b/build.zig @@ -0,0 +1,47 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "decentrala-live", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const exe_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const exe_check = b.addExecutable(.{ + .name = "check_step", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const check = b.step("check", "Check if project compiles"); + check.dependOn(&exe_check.step); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..7ac4852 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,72 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = "decentrala-intro-talk", + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/c/malloc.c b/c/malloc.c new file mode 100644 index 0000000..6a24b65 --- /dev/null +++ b/c/malloc.c @@ -0,0 +1,9 @@ +#include + +int main() { + int* ptr = malloc(sizeof(int)); + + *ptr = 4; + + free(ptr); +} \ No newline at end of file diff --git a/c/raii.cpp b/c/raii.cpp new file mode 100644 index 0000000..ce989c8 --- /dev/null +++ b/c/raii.cpp @@ -0,0 +1,27 @@ +#include +#include + +class MyClass +{ +public: + void* ptr; + + MyClass() + { + ptr = malloc(sizeof(100000000)); + } + + ~MyClass() + { + free(ptr); + } +}; + +MyClass myClass; + +int main() +{ + std::vector vec; + vec.push_back({}); + +} \ No newline at end of file diff --git a/c/string_append_or.c b/c/string_append_or.c new file mode 100644 index 0000000..bb760e2 --- /dev/null +++ b/c/string_append_or.c @@ -0,0 +1,8 @@ +#include + +int main() { + for (size_t i = 0; i < 5; i++) + { + printf("Number is: " + i); + } +} \ No newline at end of file diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..57d7d8a --- /dev/null +++ b/src/main.zig @@ -0,0 +1,43 @@ +const std = @import("std"); + +fn calc(a: i32) i32 { + return a; +} + +const SOmethin = enum { + One, + Two, + Three, +}; + + +fn Optional(comptime T: type) type { + return struct { + valid: bool, + value: T, + }; +} + +fn add(a: anytype, b: anytype) @TypeOf(a + b) { + return a + b; +} + +const DivisionError = error{DivisionByZero}; + +fn divide(a: f32, b: f32) !f32 { + if (b == 0) + return DivisionError.DivisionByZero; + + return a / b; +} + +pub fn main() !void { + + + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + const vec = @Vector(4, i32){ 1, 0, 1, 0 }; + const vec2 = @Vector(4, i32){ 0, 1, 0, 1 }; + + std.debug.print("vec: {}", .{vec + vec2}); +} diff --git a/src/main2.zig b/src/main2.zig new file mode 100644 index 0000000..43ed6f5 --- /dev/null +++ b/src/main2.zig @@ -0,0 +1,126 @@ +const std = @import("std"); +const Other = @import("other.zig"); +const WRB = @import("rbuff.zig").WrappingRingBuffer; + +const Vector2D = packed struct { + x: i5 = 2, + y: i23, + + fn name(self: Vector2D) void { + _ = self; // autofix + + } + + fn init() void {} + fn deinit() void {} +}; + +fn add(a: *i32, b: i32) i32 { + a.* = 3; + return a.* + b; +} + +const DivideError = error{ + DivivisionError, +}; + +fn divide(a: f32, b: f32) !f32 { + if (b == 0) { + return DivideError.DivivisionError; + } else { + return a / b; + } +} + +fn Optional(comptime T: type) type { + return struct { + value: T, + exists: bool, + }; +} + +const Person = struct { + name: []const u8, +}; + +const Horse = struct { + canRide: bool = false, + rider: ?*const Person = null, +}; + +const Duck = struct { + friend: ?*Animal = null, + gone: bool = false, +}; + +const AnimalType = enum { + Horse, + Duck, + Person, +}; + +const Animal = union(AnimalType) { + Horse: Horse, + Duck: Duck, + Person: Person, +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const alloc = gpa.allocator(); + defer _ = gpa.deinit(); + + var list = std.ArrayList(i32).init(alloc); + defer list.deinit(); + try list.append(324); + try list.append(456); + + for (list.items) |item| { + std.log.debug("item: {}", .{item}); + } + + var animal = Animal{ .Horse = .{ .canRide = true } }; + + const person = Person{ .name = "Dragutin" }; + + if (animal == .Horse) { + animal.Horse.rider = &person; + } + + switch (animal) { + .Horse => |*horse| horse.rider = &person, + else => {}, + } + + var personAnimal = Animal{ .Person = .{ .name = "Aleksa" } }; + + var duck = Duck{ .friend = &personAnimal }; + + duck.gone = true; + + std.log.debug("Rider name: {s}", .{if (animal.Horse.rider) |rider| rider.name else "none"}); + + { + std.log.info("Ring buffer!", .{}); + + var rbuff = try WRB(i32).initCapacity(alloc, 32); + defer rbuff.deinit(); + + if (rbuff.isEmpty()) + try rbuff.pushBack(14); + + try rbuff.pushBack(16); + try rbuff.pushBack(2); + + const front = rbuff.frontPtr(); + front.* = 333; + + var iter = rbuff.iter(); + + while (iter.next()) |*value| { + std.log.info("rbuff value: {}", .{value}); + } + + std.log.info("the end!", .{}); + } +} diff --git a/src/other.zig b/src/other.zig new file mode 100644 index 0000000..69ebfba --- /dev/null +++ b/src/other.zig @@ -0,0 +1,9 @@ +const main = @import("main.zig"); + +pub const pi = 3.14; + +pub fn add() i32 { + try main.main(); + + return 3; +} diff --git a/talk.md b/talk.md new file mode 100644 index 0000000..85a5c98 --- /dev/null +++ b/talk.md @@ -0,0 +1,375 @@ +## Intro + +Before we start: +- might be running slow - streaming + old laptop +- no slides, just text +- live coding demo +- will try to cover most cool things + - but no multithreading/async/atomics and C interop details in talk + +Who am I?: +- Ivan Notaroš - nothke +- indie gamedev for 10+ years +- studied architecture +- started by modding - 3D modeling +- Unity, C# since ~2013 +- many game jams: https://nothke.itch.io/ +- Unity had too many limitations, esp for procgen, progressively went lower level +- custom engine in C++ since 2020 + - making a rally game: Shakedown Rally in it +- heard of Zig a few years ago + - kept hearing of cooler and cooler features + - started making an engine just to see how easy it would be to move C++ to Zig +- I stream gamedev on twitch too: https://twitch.tv/nothke +- Windows user all my life 😢 + +## Zig +- Hard to put in an elevator pitch +- Fell in love with language and community - https://ziglang.org +- Don't know where to start, I'll try my best + +Zig (language) facts: +- Zig is an imperative, +- structured, +- general-purpose, +- statically typed, +- compiled, +- system programming language + +History: +- designed by Andrew Kelley + - https://andrewkelley.me +- first version (0.1) since 2016 +- currently at 0.14 dev, 0.13.0 stable +- since 0.10 - self-hosted +- since 2020, Zig software foundation: https://ziglang.org/news/announcing-zig-software-foundation/ + +Goals: +- "better C" +- simple, clear, readable +- very explicit +- maintainable +- robust +- sane defaults +- fast: runtime AND compile +- no hidden allocations +- write all in Zig +- minimal dependencies +- no UB in debug or safe +- unambigous, context-free syntax +- one obvious way to do things + +Some really cool bits (apart from already mentioned): +- all-in-one cross-compiling toolchain +- include and seamlessly interoperate with C code +- comptime - powerful metaprogramming - written in zig +- integrated build system - written in zig +- no hidden allocations - easy to reason about memory +- amazing standard library + - which is completely comprehensible! (unlike C++ STL 😛) +- decentralized package manager +- zls +- pointer disambiguation +- no exceptions - error types +- no header files (!!!) +- bit-packing types +- SIMD vectors +- no function or operator overloads +- strong formatting convention +- OOP-lite +- no macros +- if it looks like a function, its a function. Fields are just fields! + +### The toolchain +- all-in-one +- cross compile from-to any system +- compile C and C++ +- include C code + - translate-c - C to zig + - -ofmt=c - zig to C +- very fast +- build system written in Zig + - just `zig build run` + - no CMake! +- integrated tests +- 4 compiler modes `--release=[mode]` : + - debug + - safe + - fast + - small +- lazy compiler + - only compiles what you use + +## Live demo! + +### installing +- with VSCode: + - install Zig extension + - ctrl+shift+P + - install zig + - install zls +- alternatively: + - with zvm https://www.zvm.app/ + - `zvm i --zls master` or `0.13.0` (for now) + - install binary + - build zls + - sudo apt install zig + +### Language walkthrough + +const and var +- `: T =` +- initialization +- `const` - immutable + - const resolved at compile time if it can be + - can act as an alias to types +- `var` - mutable + +integers +- unsigned: `u8`, `u16`, `u32`.. +- signed: `i8`, `u16`, `u32`.. +- but also arbitrary width: `i2`, `u6`, `i31`.. +- casting - peer type resolution + - narrow to wide - allowed + - wide to narrow - disallowed, must be explicit @intCast + - if compile-time known - casting checked at compile time +- overflow is an error + - but explicit overflow operator `+%`, `-%` +- Note: `const a = 1;` is a `comptime_int` + +- floats `f16`, `f32`, `f64`, `f128` and `f80` + +pointers (single item): +- `*T` +- `&` - address of operator +- can't be null +- (..we'll come back to more pointers..) + +`[N]T` - arrays +- on stack, compile-time known size + - size part of type +- `[3]T` +- `[_]T` - inferred size from initialization +- unambiguously values + - unlike C which pretend to be pointers + +`[]T` - slices +- `const slice: []T = &arr;` +- or `const slice = arr[0..];` + +strings +- what strings..? They're just `[]u8` +- "string class" not needed, `std.mem.-` functions are rich enough for every need +- For unicode there's `std.unicode.-` + +`++` `**` - compile-time array operators +- `arr ++ arr2` - concatenates +- `arr ** 3` - repeats + +structs +- `const MyStruct = struct {}` +- fields + - always public + - rearranged to better fit memory + - unless `extern` + - can be default initialized +- are namespaces +- namespaced functions + - if first parameter is `MyStruct`, acts as a "method" + - if self needs to mutate, pass as pointer `self: *MyStruct` +- initialization + - uninitialized fields not allowed + - (but fields can be default initialized ^) + - force uninitialized with `= undefined` + - explicitely shoot yourself in the foot! (unlike C) +- files are structs too! +- `packed` + - bools become 1 bits + - arbitrary-sized ints become bit-sized +- no ctors/dtors - more on that later.. + +enums and unions +- enums +- (bare) unions + - known which type + - no type punning +- tagged unions + - use a backing enum + - switch + - polymorphism on stack! +- ^ enums and unions also act as namespaces that can have functions! 😉 + +Zig has no operator overloads! +- = is always a (shallow) copy +- [] only works on arrays, slices and multi-item pointers +- & always a pointer to - looking at C++ + +But I'm a gamedev and really need my vector arithmetic overloads 😭 +- Don't worry! There are built-in Vectors! 😍 + +`@Vector(3, i32)` +- SIMD out of the box +- can index +- cannot loop through + - but can cast into compatible array `[3]f32 = @Vector(3, f32)` + +functions +- `fn` +- parameters always const + - compiler can infer copy or const-ref, depending on size + - params +- return must be known + - but comptime expressions possible +- function pointers + +control flow +- `if (bool)` +- `while (bool)` +- `for (array) |x| {}` + - `|x|` capture bars + - expression creates variable + - range `0..` + - multi-item + - inline - unrolls loops +- switch + - exhaustive + +!? +- `!T` errors and `?T` optionals +- ! error unions + - error enum + - type example: divide by zero + - handling errors: + - try + - catch + - switch +- ? optionals + - can be `null` + - `?*T` - nullable pointers + - don't consume extra space + - `opt.?` unwraps value, if null throws error + - `opt orelse 0` if null use another value + - `if (opt) |value| {}` if not null, unwraps + - `while (opt) |value| {}` runs until null + - can be used to implement iterators `while (iter.next()) ..` + +### Disambiguation of Pointers +- in C/C++, a pointer can be + - dereferenced `*ptr` + - indexed `ptr[3]` - and not bounds checked + - added to `ptr + 1` + - `nullptr` + - not known if it points to a null terminated string..? 🤷‍♂️ + +- Zig disambiguates all these and puts them in separate types: + - `*T` - single-item pointer - cannot be indexed + - `[]T` - slice - bounded, cannot be dereferenced with t.* + - `[*]T` - multi-item pointer - unbounded + - `[:0]` - sentinel-terminated, 0 in this case + - `?*T` `?[]T` - nullable + - `*anyopaque` - equivalent to void* + - `*align(1:0:1) T` - pointers can also hold alignment and bit offset in their type + - `[*c]` - can do all - but use it only for interoperating with C code! + +- Why? intent is much clearer! + +### Comptime +- no macros, no templates, no constexpr +- call any function at compile time! +- types are types + - (at compile time only) + - can be used to return types to create generics + - example: Optional(T) +- full static reflection + - e.g. looping through fields + - https://gist.github.com/travisstaloch/71a7a2bc260997abe06016c619b40bf2 +- `anytype` + - function parameter type that can ONLY be inferred at compile-time + - similar to template function `auto` parameters in C++ +- `comptime var` + - can be mutated but only at compile-time + - acts as const at runtime + +## Memory Management + +Memory management in other languages: +- manual (C) - malloc() free() +- RAII (C++) +- GC (Java/C#/python/Go) +- compile time resolved (rust) + +Zig - manual, BUT, unlike C: +- `defer` + - executes at the end of the scope + - like RAII - BUT controlled by the user + - `errdefer` like defer but only when error is thrown +- has allocators + +### Allocators +- interface used in std with anything that allocates heap memory +- PageAllocator + - the "dumb" one + - makes syscalls +- FixedBufferAllocator + - provide a buffer, can be on stack + - not resizeable +- GeneralPurposeAllocator + - can catch leaks in debug +- ArenaAllocator + - can discard entire chunks +- TestingAllocator + - only available in tests + +- Demo how to use with `std.ArrayList` + +## Built-in Tests +- `test "My Test" {}` +- `std.testing.expect-` +- just `zig build test` + +## Package manager +- build.zig.zon +- not centralized +- hash to check code correctness +- `zig fetch --save "link"` + +## Cons +- not 1.0 yet + - changes from version to version + - async not there yet +- C/C++ ecosystem is much older and versatile +- no built-in vtable-inheritance (dynamic dispatch) + - but you can do your own in comptime + - example (old): https://github.com/alexnask/interface.zig +- no lambdas + - but https://github.com/ziglang/zig/issues/6965 + +## Community and Resources +- Online: + - discord: https://discord.gg/invite/zig + - ziggit: https://ziggit.dev/ + - Dude the Builder: https://www.youtube.com/@dudethebuilder/videos +- Offline: + - Software you can love https://sycl.it/ + - meetups on zig + +## Future of Zig +- goal of 0.14.0: super fast compile times +- getting rid of LLVM: + - with LLVM 170MB, without 4.4 + +## Conclusions +- wHeRe dOeS zIg fIt iN tHe mOdeRn wOrLd?? + - Aren't you convinced by the talk..? + - Reduces friction +- Companies already using Zig: + - Tigerbeetle https://tigerbeetle.com/ + - Bun https://bun.sh/ + - Uber (build system) +- `zig zen` +- What about Odin, jai..? +- Will do the same talk in Hamburg for code.talks + +Epilogue: +- Thanks! Now you can code in Zig⚡ ! +- nothke: https://nothke.github.io/ \ No newline at end of file