intro-to-zig-talk/talk.md

375 lines
10 KiB
Markdown

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