## 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/