This commit is contained in:
nothke 2024-08-08 20:39:18 +02:00
commit 64be90de80
11 changed files with 726 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.vscode/
zig-*/
.zig-*/

7
README.md Normal file
View File

@ -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"`

47
build.zig Normal file
View File

@ -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);
}

72
build.zig.zon Normal file
View File

@ -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 <url>`, 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 <url>` 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",
},
}

9
c/malloc.c Normal file
View File

@ -0,0 +1,9 @@
#include <stdlib.h>
int main() {
int* ptr = malloc(sizeof(int));
*ptr = 4;
free(ptr);
}

27
c/raii.cpp Normal file
View File

@ -0,0 +1,27 @@
#include <iostream>
#include <vector>
class MyClass
{
public:
void* ptr;
MyClass()
{
ptr = malloc(sizeof(100000000));
}
~MyClass()
{
free(ptr);
}
};
MyClass myClass;
int main()
{
std::vector<MyClass> vec;
vec.push_back({});
}

8
c/string_append_or.c Normal file
View File

@ -0,0 +1,8 @@
#include <stdio.h>
int main() {
for (size_t i = 0; i < 5; i++)
{
printf("Number is: " + i);
}
}

43
src/main.zig Normal file
View File

@ -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});
}

126
src/main2.zig Normal file
View File

@ -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!", .{});
}
}

9
src/other.zig Normal file
View File

@ -0,0 +1,9 @@
const main = @import("main.zig");
pub const pi = 3.14;
pub fn add() i32 {
try main.main();
return 3;
}

375
talk.md Normal file
View File

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