10 KiB
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
- 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!
- just
- 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
or0.13.0
(for now)
- install binary
- build zls
- sudo apt install zig
- with zvm https://www.zvm.app/
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
+%
,-%
- but explicit overflow operator
-
Note:
const a = 1;
is acomptime_int
-
floats
f16
,f32
,f64
,f128
andf80
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
- concatenatesarr ** 3
- repeats
structs
const MyStruct = struct {}
- fields
- always public
- rearranged to better fit memory
- unless
extern
- unless
- 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
- if first parameter is
- initialization
- uninitialized fields not allowed
- (but fields can be default initialized ^)
- force uninitialized with
= undefined
- explicitely shoot yourself in the foot! (unlike C)
- uninitialized fields not allowed
- 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)
- but can cast into compatible array
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 erroropt orelse 0
if null use another valueif (opt) |value| {}
if not null, unwrapswhile (opt) |value| {}
runs until null- can be used to implement iterators
while (iter.next()) ..
- can be used to implement iterators
- can be
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..? 🤷♂️
- dereferenced
-
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
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
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/