├── example.txt ├── .gitignore ├── c-double_me.zig ├── c-libfunction.c ├── hello_world.zig ├── c-double_me.c ├── file_write.zig ├── tcp_client.zig ├── Makefile ├── c-main.c ├── stdin_read.zig ├── pointer_arithmetic.zig ├── c-libfunction.zig ├── generic_structs.zig ├── json_parse.zig ├── allocators_page.zig ├── file_read.zig ├── parsing.zig ├── c-double_me.build.zig ├── allocators_arena.zig ├── json_stringify.zig ├── allocators_fixed_buffer.zig ├── tcp_server.zig ├── c-ray.zig ├── generic_sum.zig ├── tcp_ping_pong.zig ├── allocators_arena_with_fixed_buffer.zig ├── allocators_general_purpose.zig ├── c-curl.c ├── slow_server.go ├── dangling_pointer.zig ├── async_await_simple.zig ├── stdin_while.zig ├── iterator.zig ├── async_await.zig ├── strings_concatenation.zig ├── c-libcurl.zig ├── parsing_tokenizer.zig ├── redis_client.zig ├── traits.zig ├── README.md ├── string_literals.zig ├── linked_list.zig ├── allocators_syscall.zig └── hash_table.zig /example.txt: -------------------------------------------------------------------------------- 1 | Hello world from file 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | *.o 3 | *.so 4 | *.out 5 | *.a -------------------------------------------------------------------------------- /c-double_me.zig: -------------------------------------------------------------------------------- 1 | export fn double_me(n: i32) i32 { 2 | return n*2; 3 | } -------------------------------------------------------------------------------- /c-libfunction.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int double_me(int value) 4 | { 5 | return value + value; 6 | } -------------------------------------------------------------------------------- /hello_world.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() void { 4 | std.debug.print("Hello world!\n", .{}); 5 | } -------------------------------------------------------------------------------- /c-double_me.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int double_me(int a); 4 | 5 | int main(int argc, char **argv) { 6 | int result = double_me(8); 7 | printf("%d\n", result); 8 | return 0; 9 | } -------------------------------------------------------------------------------- /file_write.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !void { 4 | const dir = try std.fs.openDirAbsolute("/tmp", std.fs.Dir.OpenDirOptions{}); 5 | const file = try dir.createFile("hello.txt", std.fs.File.CreateFlags{}); 6 | defer file.close(); 7 | 8 | try file.writeAll("Hello world\n"); 9 | } -------------------------------------------------------------------------------- /tcp_client.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const net = std.net; 3 | 4 | pub fn main() !void { 5 | const address = try net.Address.resolveIp("0.0.0.0", 8080); 6 | var stream = try net.tcpConnectToAddress(address); 7 | defer stream.close(); 8 | 9 | const msg = "hello from zig!"; 10 | _ = try stream.write(msg[0..]); 11 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | libfunction: 2 | gcc -shared -fPIC -o libmylib.so c-libfunction.c 3 | zig run c-libfunction.zig -lc -lmylib -L. -I. 4 | 5 | curl: 6 | zig run c-libcurl.zig -lcurl -lc $(pkg-config --cflags libcurl) 7 | 8 | ray: 9 | zig run c-ray.zig -lc -lraylib -I${HOME}/software/raylib 10 | 11 | clean: 12 | rm libmylib.so 13 | 14 | c-double_me: 15 | zig build test --build-file c-double_me.build.zig -------------------------------------------------------------------------------- /c-main.c: -------------------------------------------------------------------------------- 1 | // gcc -o c-main main.c -L. -lmylib 2 | #include 3 | 4 | // declare the function, ideally the library has a .h file for this 5 | int double_me(int); 6 | 7 | int main(void) 8 | { 9 | int i; 10 | for (i = 1; i <= 10; i++) { 11 | // call our library function 12 | printf("%d doubled is %d\n", i, double_me(i)); 13 | } 14 | return 0; 15 | } -------------------------------------------------------------------------------- /stdin_read.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() void { 4 | const stdin = std.io.getStdIn(); 5 | defer stdin.close(); 6 | 7 | std.debug.print("Enter your name\n", .{}); 8 | 9 | var buffer: [8]u8 = undefined; 10 | var reader = stdin.reader(); 11 | var line = reader.readUntilDelimiterOrEof(&buffer, '\n'); 12 | 13 | std.debug.print("Hello {s}\n", .{line}); 14 | } 15 | -------------------------------------------------------------------------------- /pointer_arithmetic.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expect = std.testing.expect; 3 | 4 | pub fn main() !void { 5 | var array = [_]i32{ 1, 2, 3, 4 }; 6 | 7 | var known_at_runtime_zero: usize = 0; 8 | const slice = array[known_at_runtime_zero..array.len]; 9 | 10 | var p = slice.ptr; 11 | try expect(p[0] == 1); 12 | 13 | // Increment pointer one step 14 | p += 1; 15 | try expect(p[0] == 2); 16 | } 17 | -------------------------------------------------------------------------------- /c-libfunction.zig: -------------------------------------------------------------------------------- 1 | // gcc -shared -fPIC -o libmylib.so c-libfunction.c 2 | // zig run c-libfunction.zig -lc -lmylib -L. 3 | const std = @import("std"); 4 | const libfn = @cImport({ 5 | @cInclude("c-libfunction.c"); 6 | }); 7 | 8 | pub fn main() !void { 9 | var i: i32 = 0; 10 | while (i < 10) : (i += 1) { 11 | var res = libfn.double_me(i); 12 | std.debug.print("Double of {d} is in C {d}\n", .{i, res}); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /generic_structs.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | fn AddTwo(comptime T: type) type { 4 | return struct { 5 | n: T, 6 | const Self = @This(); 7 | 8 | fn result(self: Self) T { 9 | return self.n+2; 10 | } 11 | }; 12 | } 13 | 14 | 15 | pub fn main() !void { 16 | const n = AddTwo(u32){ .n = 2 }; 17 | const res = n.result(); 18 | try std.testing.expect(res == 4); 19 | std.debug.print("R: {d}\n",.{res}); 20 | } -------------------------------------------------------------------------------- /json_parse.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const json = std.json; 3 | 4 | const Place = struct { lat: f32, long: f32 }; 5 | 6 | pub fn main() !void { 7 | var stream = json.TokenStream.init( 8 | \\{ "lat": 40.684540, "long": -74.401422 } 9 | ); 10 | 11 | const x = try json.parse(Place, &stream, .{}); 12 | 13 | if (x.lat != 40.684540) { 14 | unreachable; 15 | } 16 | 17 | if (x.long != -74.401422) { 18 | unreachable; 19 | } 20 | } -------------------------------------------------------------------------------- /allocators_page.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !void { 4 | const allocator = std.heap.page_allocator; 5 | 6 | const mem1 = try allocator.alloc(u8, 5); 7 | defer allocator.free(mem1); 8 | 9 | for ("hello") |c, i| { 10 | mem1[i] = c; 11 | } 12 | 13 | const mem2 = try allocator.alloc(u8, 7); 14 | defer allocator.free(mem2); 15 | 16 | for (" world!") |c, i| { 17 | mem2[i] = c; 18 | } 19 | 20 | std.debug.print("{s}{s}\n", .{mem1, mem2}); 21 | } -------------------------------------------------------------------------------- /file_read.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !void { 4 | const file = try std.fs.cwd().openFile("example.txt", std.fs.File.OpenFlags{.read = true}); 5 | defer file.close(); 6 | 7 | var buffer: [100]u8 = undefined; 8 | 9 | // Read first line only 10 | const line = try file.reader().readUntilDelimiterOrEof(&buffer, '\n'); 11 | std.debug.print("{s}\n", .{line}); 12 | 13 | // Read all 14 | // const bytes_read = try file.readAll(&buffer); 15 | // std.debug.print("{s}\n", .{buffer[0..bytes_read]}); 16 | } -------------------------------------------------------------------------------- /parsing.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expect = std.testing.expect; 3 | 4 | pub fn main() !void { 5 | const msg = "405323"; 6 | 7 | // Parse a unsigned 4 byte int number from a string 8 | var res = try std.fmt.parseUnsigned(u32, msg, 10); 9 | try expect(res == 405323); 10 | 11 | // Serialize a number into a bytes array 12 | var bytes = std.mem.toBytes(res); 13 | 14 | // Parse a 4 byte array to a unsigned int number from 15 | var n = std.mem.bytesToValue(u32, &bytes); 16 | try expect(n == 405323); 17 | } 18 | -------------------------------------------------------------------------------- /c-double_me.build.zig: -------------------------------------------------------------------------------- 1 | const Builder = @import("std").build.Builder; 2 | 3 | pub fn build(b: *Builder) void { 4 | const lib = b.addStaticLibrary("c-double_me", "c-double_me.zig"); 5 | 6 | const exe = b.addExecutable("test", null); 7 | exe.addCSourceFile("c-double_me.c", &[_][]const u8{"-std=c99"}); 8 | exe.linkLibrary(lib); 9 | exe.linkLibC(); 10 | 11 | b.default_step.dependOn(&exe.step); 12 | 13 | const run_cmd = exe.run(); 14 | 15 | const test_step = b.step("test", "Test the program"); 16 | test_step.dependOn(&run_cmd.step); 17 | } -------------------------------------------------------------------------------- /allocators_arena.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const arena = std.heap.ArenaAllocator; 3 | 4 | pub fn main() !void { 5 | var arena_alloc = arena.init(std.heap.page_allocator); 6 | defer arena_alloc.deinit(); 7 | 8 | var allocator = &arena_alloc.allocator; 9 | 10 | const mem1 = try allocator.alloc(u8, 5); 11 | 12 | for ("hello") |c, i| { 13 | mem1[i] = c; 14 | } 15 | 16 | const mem2 = try allocator.alloc(u8, 7); 17 | 18 | for (" world!") |c, i| { 19 | mem2[i] = c; 20 | } 21 | 22 | std.debug.print("{s}{s}\n", .{mem1, mem2}); 23 | } -------------------------------------------------------------------------------- /json_stringify.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const json = std.json; 3 | 4 | const Person = struct { name: []const u8, surname: []const u8 }; 5 | 6 | pub fn main() !void { 7 | const mario = Person{ 8 | .name = "Mario", 9 | .surname = "Castro", 10 | }; 11 | 12 | var buf: [100]u8 = undefined; 13 | var fba = std.heap.FixedBufferAllocator.init(&buf); 14 | var string = std.ArrayList(u8).init(&fba.allocator); 15 | defer string.deinit(); 16 | 17 | try json.stringify(&mario, .{}, string.writer()); 18 | 19 | std.debug.print("{s}\n", .{string.toOwnedSlice()}); 20 | } -------------------------------------------------------------------------------- /allocators_fixed_buffer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const fballoc = std.heap.FixedBufferAllocator; 3 | 4 | pub fn main() !void { 5 | var buffer: [12]u8 = undefined; 6 | var fba = fballoc.init(&buffer); 7 | var allocator = &fba.allocator; 8 | 9 | const mem1 = try allocator.alloc(u8, 5); 10 | defer allocator.free(mem1); 11 | 12 | for ("hello") |c, i| { 13 | mem1[i] = c; 14 | } 15 | 16 | const mem2 = try allocator.alloc(u8, 7); 17 | defer allocator.free(mem2); 18 | for (" world!") |c, i| { 19 | mem2[i] = c; 20 | } 21 | 22 | std.debug.print("{s}\n", .{buffer}); 23 | } -------------------------------------------------------------------------------- /tcp_server.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const net = std.net; 3 | 4 | pub fn main() !void { 5 | const address = try net.Address.resolveIp("0.0.0.0", 8080); 6 | var stream = try net.tcpConnectToAddress(address); 7 | defer stream.close(); 8 | 9 | // There's probably a much better/safe/common way to do this. 10 | var buffer: [128]u8 = undefined; 11 | while(true) { 12 | var bytes_read = try stream.read(&buffer); 13 | 14 | if (bytes_read>0){ 15 | std.debug.print("Read {d} bytes. {s}\n", .{bytes_read, buffer[0..bytes_read]}); 16 | bytes_read=0; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /c-ray.zig: -------------------------------------------------------------------------------- 1 | // take a look at 'ray' target in Makefile 2 | const ray = @cImport({ 3 | @cInclude("src/raylib.h"); 4 | }); 5 | 6 | pub fn main() void { 7 | const screenWidth = 800; 8 | const screenHeight = 450; 9 | 10 | ray.InitWindow(screenWidth, screenHeight, "raylib [core] example - basic window"); 11 | defer ray.CloseWindow(); 12 | 13 | ray.SetTargetFPS(60); 14 | 15 | while (!ray.WindowShouldClose()) { 16 | ray.BeginDrawing(); 17 | defer ray.EndDrawing(); 18 | 19 | ray.ClearBackground(ray.RAYWHITE); 20 | ray.DrawText("Hello, World!", 190, 200, 20, ray.LIGHTGRAY); 21 | } 22 | } -------------------------------------------------------------------------------- /generic_sum.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() void { 4 | var known_at_runtime: usize = 5; 5 | var ns = [_]u32{2}**5; 6 | var sl = ns[0..known_at_runtime]; 7 | 8 | const res_u32 = sum(u32, sl); 9 | std.debug.print("Result: {d}\n", .{res_u32}); 10 | 11 | var nsf = [_]f32{2.5}**5; 12 | const res_f32 = sum(f32, nsf[0..known_at_runtime]); 13 | std.debug.print("Result: {d}\n", .{res_f32}); 14 | } 15 | 16 | // generic function 17 | fn sum(comptime T: type, ar: []const T) T { 18 | var result: T = 0; 19 | for (ar) |n| { 20 | result += n; 21 | } 22 | 23 | return result; 24 | } -------------------------------------------------------------------------------- /tcp_ping_pong.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const net = std.net; 3 | 4 | pub fn main() !void { 5 | const address = try net.Address.resolveIp("0.0.0.0", 8080); 6 | var stream = try net.tcpConnectToAddress(address); 7 | defer stream.close(); 8 | 9 | 10 | const msg = "hello from zig!"; 11 | _ = try stream.write(msg[0..]); 12 | 13 | var res = getResponse(&stream); 14 | 15 | std.debug.print("{s}\nBye!\n", .{res}); 16 | } 17 | 18 | fn getResponse(stream: *std.net.Stream) ![]u8 { 19 | var buffer: [128]u8 = undefined; 20 | var bytes_read = try stream.read(&buffer); 21 | 22 | std.debug.print("Read {d} bytes\n", .{bytes_read}); 23 | 24 | return buffer[0..bytes_read]; 25 | } 26 | -------------------------------------------------------------------------------- /allocators_arena_with_fixed_buffer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const fballoc = std.heap.FixedBufferAllocator; 3 | const arena = std.heap.ArenaAllocator; 4 | 5 | pub fn main() !void { 6 | var buffer: [128]u8 = undefined; 7 | var fba = fballoc.init(&buffer); 8 | var fballocator = &fba.allocator; 9 | 10 | var arena_alloc = arena.init(fballocator); 11 | defer arena_alloc.deinit(); 12 | 13 | var allocator = &arena_alloc.allocator; 14 | 15 | var mem1 = try allocator.alloc(u8, 5); 16 | for ("hello") |c, i| { 17 | mem1[i] = c; 18 | } 19 | 20 | var mem2 = try allocator.alloc(u8, 6); 21 | for ("world!") |c, i| { 22 | mem2[i] = c; 23 | } 24 | 25 | std.debug.print("{s} {s}\n", .{mem1, mem2}); 26 | } -------------------------------------------------------------------------------- /allocators_general_purpose.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !void { 4 | var gpa = std.heap.GeneralPurposeAllocator(.{.enable_memory_limit = true}){}; 5 | var allocator = &gpa.allocator; 6 | defer { 7 | const leaked = gpa.deinit(); 8 | if (leaked) unreachable; 9 | } 10 | 11 | const mem1 = try allocator.alloc(u8, 5); 12 | defer allocator.free(mem1); 13 | 14 | for ("hello") |c, i| { 15 | mem1[i] = c; 16 | } 17 | 18 | const mem2 = try allocator.alloc(u8, 7); 19 | defer allocator.free(mem2); 20 | 21 | for (" world!") |c, i| { 22 | mem2[i] = c; 23 | } 24 | 25 | std.debug.print("Total requested bytes: {d}\nNever exceed bytes: {d}\n", .{gpa.total_requested_bytes, gpa.requested_memory_limit}); 26 | std.debug.print("{s}{s}\n", .{mem1, mem2}); 27 | } -------------------------------------------------------------------------------- /c-curl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(void) 6 | { 7 | CURL *curl; 8 | CURLcode res; 9 | 10 | static const char *postthis = "moo mooo moo moo"; 11 | 12 | curl = curl_easy_init(); 13 | if(curl) { 14 | curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); 15 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postthis); 16 | 17 | /* if we don't provide POSTFIELDSIZE, libcurl will strlen() by 18 | itself */ 19 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(postthis)); 20 | 21 | /* Perform the request, res will get the return code */ 22 | res = curl_easy_perform(curl); 23 | /* Check for errors */ 24 | if(res != CURLE_OK) 25 | fprintf(stderr, "curl_easy_perform() failed: %s\n", 26 | curl_easy_strerror(res)); 27 | 28 | /* always cleanup */ 29 | curl_easy_cleanup(curl); 30 | } 31 | return 0; 32 | } -------------------------------------------------------------------------------- /slow_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | ) 8 | 9 | const ( 10 | CONN_HOST = "0.0.0.0" 11 | CONN_PORT = "8080" 12 | CONN_TYPE = "tcp" 13 | ) 14 | 15 | func main() { 16 | l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT) 17 | if err != nil { 18 | panic(err) 19 | } 20 | defer l.Close() 21 | 22 | fmt.Println("Listening on " + CONN_HOST + ":" + CONN_PORT) 23 | 24 | for { 25 | conn, err := l.Accept() 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | go handleRequest(conn) 31 | } 32 | } 33 | 34 | func handleRequest(conn net.Conn) { 35 | buf := make([]byte, 1024) 36 | _, err := conn.Read(buf) 37 | if err != nil { 38 | fmt.Println("Error reading:", err.Error()) 39 | } 40 | defer conn.Close() 41 | 42 | fmt.Printf("Message received: %s\n", string(buf)) 43 | 44 | // Respond slowwwwly 45 | time.Sleep(2 * time.Second) 46 | conn.Write([]byte("Hello from Go!")) 47 | } 48 | -------------------------------------------------------------------------------- /dangling_pointer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !void { 4 | var p = pointer(); 5 | 6 | // here it works, which is a bit unexpected 7 | std.debug.print("{s}\n", .{p.msg}); 8 | 9 | // (core dump) but here it won't as expected, I don't 10 | // understand well why it works in the previous case. 11 | printMsg(p); 12 | 13 | var p2 = constPointer(); 14 | 15 | // here it works too 16 | std.debug.print("{s}\n", .{p2.msg}); 17 | 18 | // (core dump) but here it won't either 19 | printMsg(p); 20 | 21 | } 22 | 23 | const MyStruct = struct { 24 | msg: []const u8, 25 | }; 26 | 27 | fn pointer() *MyStruct { 28 | var m = MyStruct{ 29 | .msg = "hello", 30 | }; 31 | return &m; 32 | } 33 | 34 | fn constPointer()*const MyStruct{ 35 | const m = MyStruct{ 36 | .msg = "world", 37 | }; 38 | 39 | return &m; 40 | } 41 | 42 | fn printMsg(p: *const MyStruct) void { 43 | std.debug.print("{s}\n", .{p.msg}); 44 | } 45 | -------------------------------------------------------------------------------- /async_await_simple.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const debug = std.debug.print; 3 | const rand = std.rand; 4 | const fbaAlloc = std.heap.FixedBufferAllocator; 5 | 6 | pub const io_mode = .evented; 7 | 8 | pub fn main() !void { 9 | var i: usize = 0; 10 | 11 | var pa = std.heap.page_allocator; 12 | var buf = try pa.alloc(@Frame(print), 100); 13 | defer pa.free(buf); 14 | 15 | debug("{d} bytes allocated\n", .{@sizeOf(@Frame(print)) * 100}); 16 | 17 | const pcg = rand.Pcg.init(4); 18 | var random_number = rand.Random{ 19 | .fillFn = pcg.random.fillFn, 20 | }; 21 | 22 | while (i < 100) : (i += 1) { 23 | var n = random_number.uintLessThan(usize, 1_000_000_000); 24 | buf[i] = async print(i, n); 25 | } 26 | 27 | i = 0; 28 | while (i < 100) : (i += 1) { 29 | await buf[i]; 30 | } 31 | 32 | debug("Finish!\n", .{}); 33 | } 34 | 35 | fn print(i: usize, n: usize) void { 36 | std.time.sleep(n + 10); 37 | debug("{d}\n", .{i}); 38 | } 39 | -------------------------------------------------------------------------------- /stdin_while.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const MyEnum = enum { 4 | @".exit", 5 | @".help", 6 | @".unrecognized", 7 | }; 8 | 9 | pub fn main() !void { 10 | const stdin = std.io.getStdIn(); 11 | defer stdin.close(); 12 | 13 | std.debug.print("Enter your name\n", .{}); 14 | 15 | var buffer: [100]u8 = undefined; 16 | var reader = stdin.reader(); 17 | 18 | while (true) { 19 | var line = try reader.readUntilDelimiterOrEof(&buffer, '\n'); 20 | 21 | const conv: MyEnum = std.meta.stringToEnum(MyEnum, line.?) orelse MyEnum.@".unrecognized"; 22 | 23 | switch(conv){ 24 | MyEnum.@".exit" => { 25 | std.debug.print("Bye!\n", .{}); 26 | return; 27 | }, 28 | MyEnum.@".help" => { 29 | std.debug.print("Possible commands are:!\n.exit => exits the REPL\n.help => Show this help\n", .{}); 30 | }, 31 | MyEnum.@".unrecognized" => { 32 | std.debug.print("Unrecognized command '{s}'\n", .{line.?}); 33 | }, 34 | } 35 | 36 | 37 | } 38 | 39 | unreachable; 40 | } 41 | -------------------------------------------------------------------------------- /iterator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expect = std.testing.expect; 3 | 4 | fn Numbers() type { 5 | return struct { 6 | numbers: []u8, 7 | 8 | const Self = @This(); 9 | 10 | fn init(ns: []u8) Self { 11 | return Self{ 12 | .numbers = ns, 13 | }; 14 | } 15 | 16 | fn iterator(self: *Self) Iterator { 17 | return Iterator{ 18 | .numbers = &self.numbers, 19 | }; 20 | } 21 | }; 22 | } 23 | 24 | const Iterator = struct { 25 | numbers: *[]u8, 26 | pos: usize = 0, 27 | 28 | const Self = @This(); 29 | 30 | pub fn next(self: *Self) ?u8 { 31 | const index = self.pos; 32 | 33 | return if (index < self.numbers.len) { 34 | const temp = self.numbers.*[index]; 35 | self.pos += 1; 36 | return temp; 37 | } else null; 38 | } 39 | }; 40 | 41 | pub fn main() !void { 42 | var ar = [_]u8{ 1, 2, 3, 4 }; 43 | var outer = Numbers().init(ar[0..]); 44 | 45 | var iterator = outer.iterator(); 46 | while (iterator.next()) |item| { 47 | std.debug.print("{d}\n", .{item}); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /async_await.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const net = std.net; 3 | 4 | pub const io_mode = .evented; 5 | 6 | pub fn main() !void { 7 | const address = try net.Address.resolveIp("0.0.0.0", 8080); 8 | var stream = try net.tcpConnectToAddress(address); 9 | defer stream.close(); 10 | 11 | // Message 1 12 | const msg = "hello from zig!"; 13 | _ = try stream.write(msg[0..]); 14 | 15 | var frame = async getResponse(&stream); 16 | 17 | std.debug.print("Waiting...", .{}); 18 | 19 | // Response 1 20 | var res = try await frame; 21 | 22 | std.debug.print("{s}\n", .{res}); 23 | 24 | // Message 2 25 | _ = try stream.write(msg[0..]); 26 | var frame2 = async getResponse(&stream); 27 | } 28 | 29 | fn getResponse(stream: *std.net.Stream) ![]u8 { 30 | var buffer: [128]u8 = undefined; 31 | var bytes_read = try stream.read(&buffer); 32 | 33 | std.debug.print("({d} bytes received)\n", .{bytes_read}); 34 | 35 | return buffer[0..bytes_read]; 36 | } 37 | 38 | fn getResponseSuspend(stream: *std.net.Stream) ![]u8 { 39 | var buffer: [128]u8 = undefined; 40 | var bytes_read = try suspend stream.read(&buffer); 41 | 42 | std.debug.print("({d} bytes received)\n", .{bytes_read}); 43 | 44 | return buffer[0..bytes_read]; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /strings_concatenation.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !void { 4 | // Strings concatenation can be done in various ways. A "painful" one is using 5 | // std.mem.concat, this requires a multidimensional const slice which is not 6 | // very intuitive to build 7 | const multi = [_] []const u8{ 8 | "hello", 9 | " ", 10 | "world", 11 | }; 12 | var page = std.heap.page_allocator; 13 | 14 | const string1 = try std.mem.concat(page, u8, multi[0..]); 15 | defer page.free(string1); 16 | try std.testing.expectEqualSlices(u8, "hello world", string1); 17 | 18 | // A less "painful" one is to use std.fmt.format 19 | const string2 = try std.fmt.allocPrint(page, "{s}{s}{s}", .{"hello"," ","world"}); 20 | defer page.free(string2); 21 | try std.testing.expectEqualSlices(u8, "hello world", string2); 22 | 23 | // Both require allocation on the heap, you can workaround this by using an 24 | // stack based allocator if the string is "small" 25 | var buf: [256]u8 = undefined; 26 | var fba = std.heap.FixedBufferAllocator.init(&buf); 27 | var fba_allocator = &fba.allocator; 28 | 29 | const string3 = try std.fmt.allocPrint(fba_allocator, "{s}{s}{s}", .{"hello"," ","world"}); 30 | defer fba_allocator.free(string3); 31 | try std.testing.expectEqualSlices(u8, "hello world", string3); 32 | 33 | // How big the stack allocator can be? Well I'm not sure, but if I run a `ulimit -a` 34 | // (or `ulimit -s`) it is 8192 bytes in my linux OS (I got the info from here 35 | // https://softwareengineering.stackexchange.com/questions/310658/how-much-stack-usage-is-too-much) 36 | // Windows folks can maybe track how to check their size by following the link), 37 | // so the max buffer size is definitely less than this number, because we are 38 | // already using some space in the stack for other stuff. 39 | } -------------------------------------------------------------------------------- /c-libcurl.zig: -------------------------------------------------------------------------------- 1 | // look at the `curl` target in Makefile 2 | const std = @import("std"); 3 | const cURL = @cImport({ 4 | @cInclude("curl/curl.h"); 5 | }); 6 | 7 | pub fn main() !void { 8 | var arena_state = std.heap.ArenaAllocator.init(std.heap.c_allocator); 9 | defer arena_state.deinit(); 10 | var allocator = &arena_state.allocator; 11 | 12 | // global curl init, or fail 13 | if (cURL.curl_global_init(cURL.CURL_GLOBAL_ALL) != cURL.CURLE_OK) 14 | return error.CURLGlobalInitFailed; 15 | defer cURL.curl_global_cleanup(); 16 | 17 | // curl easy handle init, or fail 18 | const handle = cURL.curl_easy_init() orelse return error.CURLHandleInitFailed; 19 | defer cURL.curl_easy_cleanup(handle); 20 | 21 | var response_buffer = std.ArrayList(u8).init(allocator); 22 | 23 | // superfluous when using an arena allocator, but 24 | // important if the allocator implementation changes 25 | defer response_buffer.deinit(); 26 | 27 | // setup curl options 28 | if (cURL.curl_easy_setopt(handle, cURL.CURLOPT_URL, "https://ziglang.org") != cURL.CURLE_OK) 29 | return error.CouldNotSetURL; 30 | 31 | // set write function callbacks 32 | if (cURL.curl_easy_setopt(handle, cURL.CURLOPT_WRITEFUNCTION, writeToArrayListCallback) != cURL.CURLE_OK) 33 | return error.CouldNotSetWriteCallback; 34 | if (cURL.curl_easy_setopt(handle, cURL.CURLOPT_WRITEDATA, &response_buffer) != cURL.CURLE_OK) 35 | return error.CouldNotSetWriteCallback; 36 | 37 | // perform 38 | if (cURL.curl_easy_perform(handle) != cURL.CURLE_OK) 39 | return error.FailedToPerformRequest; 40 | 41 | std.log.info("Got response of {d} bytes", .{response_buffer.items.len}); 42 | std.debug.print("{s}\n", .{response_buffer.items}); 43 | } 44 | 45 | fn writeToArrayListCallback(data: *c_void, size: c_uint, nmemb: c_uint, user_data: *c_void) callconv(.C) c_uint { 46 | var buffer = @intToPtr(*std.ArrayList(u8), @ptrToInt(user_data)); 47 | var typed_data = @intToPtr([*]u8, @ptrToInt(data)); 48 | buffer.appendSlice(typed_data[0 .. nmemb * size]) catch return 0; 49 | return nmemb * size; 50 | } -------------------------------------------------------------------------------- /parsing_tokenizer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expect = std.testing.expect; 3 | 4 | pub fn main() !void { 5 | const text = "the lazy fox jump over 1 brown dog."; 6 | 7 | var pa = std.heap.page_allocator; 8 | // This actually allocates an array of optionals![]const u8, so 9 | // actually a multidimensional array. But in fact, this is an 10 | // array of optional slice pointers to portions of 'text' const. 11 | var tokens = try pa.alloc(?[]const u8, 10); 12 | defer pa.free(tokens); 13 | 14 | // The slice pointer has a size of 16 bytes: 8 bytes for the 15 | // pointer and 8 bytes for the slice size. 16 | try expect(@sizeOf(@TypeOf(tokens)) == 16); 17 | 18 | // The total size of the allocated space is 240 bytes, 24 bytes 19 | // per item: like before, 16 bytes of the slice pointer to elements 20 | // in the original array and 8 bytes for the optional == 24 bytes 21 | // multiplied by 10 items == 240 bytes; 22 | try expect(@sizeOf(@TypeOf(tokens[0])) == 24); 23 | try expect(std.mem.sliceAsBytes(tokens).len == 240); 24 | 25 | var last: usize = 0; 26 | var pos: usize = 0; 27 | for (text) |c, i| { 28 | // ASCII Space 29 | if (c == 32) { 30 | tokens[pos] = text[last..i]; 31 | pos += 1; 32 | last = i + 1; 33 | } 34 | } 35 | if (last < text.len) { 36 | tokens[pos] = text[last..]; 37 | } 38 | 39 | for (tokens) |maybe_token| { 40 | if (maybe_token) |token| 41 | std.debug.print("'{s}'\n", .{token}); 42 | } 43 | 44 | std.debug.print("\n", .{}); 45 | 46 | // The reason to use a optional for the previous array was 47 | // to work easily with iterators, but there's an overhead 48 | // of a 50% in size for the array size. I think that to avoid 49 | // the use of optionals, I need to keep a count in the 50 | // number of tokens extracted 51 | var light_tokens = try pa.alloc([]const u8, 10); 52 | defer pa.free(light_tokens); 53 | 54 | try expect(@sizeOf(@TypeOf(light_tokens)) == 16); 55 | try expect(@sizeOf(@TypeOf(light_tokens[0])) == 16); 56 | try expect(std.mem.sliceAsBytes(light_tokens).len == 160); 57 | 58 | var token_count: usize = 0; 59 | last = 0; 60 | pos = 0; 61 | for (text) |c, i| { 62 | // ASCII Space 63 | if (c == 32) { 64 | light_tokens[pos] = text[last..i]; 65 | pos += 1; 66 | last = i + 1; 67 | token_count += 1; 68 | } 69 | } 70 | 71 | if (last < text.len) { 72 | light_tokens[pos] = text[last..]; 73 | } 74 | token_count += 1; 75 | 76 | var i: usize = 0; 77 | while (i < token_count) : (i += 1) { 78 | std.debug.print("'{s}'\n", .{light_tokens[i]}); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /redis_client.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const net = std.net; 3 | 4 | pub fn main() !void { 5 | const address = try net.Address.resolveIp("172.17.0.2", 6379); 6 | var stream = try net.tcpConnectToAddress(address); 7 | defer stream.close(); 8 | 9 | // reusable response buffer 10 | var buffer: [128]u8 = undefined; 11 | 12 | // Commands to send to redis 13 | const commands = [_][]const u8{ 14 | "ECHO \"Hello world!\"\r\n", 15 | "EXISTS hello\r\n", 16 | "SET hello world\r\n", 17 | "GET hello\r\n", 18 | }; 19 | 20 | for(commands)|command|{ 21 | std.debug.print("$ {s}", .{command}); 22 | // Send command 23 | _ = try stream.write(command[0..]); 24 | 25 | // Read response 26 | _ = try stream.read(&buffer); 27 | try parseResponse(&buffer, buffer.len); 28 | } 29 | } 30 | 31 | fn parseResponse(buffer: [*]u8, len: usize) !void { 32 | // First byte of Redis is the response type 33 | const resp_type = buffer[0]; 34 | 35 | switch (resp_type) { 36 | '$' => { //bulk string 37 | var res = try parseBulkString(buffer, len); 38 | std.debug.print("'{s}'\n", .{res}); 39 | }, 40 | '+' => { //simple string 41 | var res = try parseSimpleString(buffer); 42 | std.debug.print("'{s}'\n", .{res}); 43 | }, 44 | '-' => { //error 45 | var res = try parseSimpleString(buffer); 46 | std.debug.print("Error: '{s}'\n", .{res}); 47 | }, 48 | ':' => { //integer 49 | var res = try parseSimpleString(buffer); 50 | var n = try std.fmt.parseInt(u32, res, 10); 51 | std.debug.print("{d}\n", .{n}); 52 | }, 53 | '*' => { //array 54 | //TODO 55 | }, 56 | else => { 57 | @panic("Redis response not recognized"); 58 | }, 59 | } 60 | } 61 | 62 | const RedisError = error{ClrNotFound}; 63 | 64 | // accepts a redis response and returns the message contained without the final \r\n 65 | fn parseSimpleString(in: [*]u8) ![]u8 { 66 | var i: usize = 0; 67 | while (true) : (i += 1) { 68 | if (in[i] == '\n' and i != 0 and in[i - 1] == '\r') { 69 | return in[1 .. i - 1]; 70 | } 71 | } 72 | 73 | return RedisError.ClrNotFound; 74 | } 75 | 76 | // accepts a redis bulk string response and returns the message inside without the final \r\n 77 | fn parseBulkString(in: [*]u8, size: usize) ![]u8 { 78 | var index: usize = undefined; 79 | var total_bytes: usize = undefined; 80 | 81 | var i: usize = 0; 82 | while (i < size) : (i += 1) { 83 | if (in[i] == '\n' and i != 0 and in[i - 1] == '\r') { 84 | index = i + 1; 85 | total_bytes = try std.fmt.parseInt(usize, in[1 .. i - 1], 10); 86 | 87 | return in[index .. index + total_bytes]; 88 | } 89 | } 90 | 91 | return RedisError.ClrNotFound; 92 | } -------------------------------------------------------------------------------- /traits.zig: -------------------------------------------------------------------------------- 1 | /// Zig doesn't have out of the box "traits". But it turns out that 2 | /// I have seen lots of uses for "writer" and "reader" so, how they 3 | /// work? 4 | const std = @import("std"); 5 | const fs = std.fs; 6 | const io = std.io; 7 | 8 | pub fn main() !void { 9 | // Files has readers and writers, so I'll use a file to investigate 10 | var file = try fs.createFileAbsolute("/tmp/test", fs.File.CreateFlags{}); 11 | defer file.close(); 12 | 13 | // writer() returns a io.Writer struct which uses a similar layout 14 | // than the "generic" examples, something like: 15 | // io.Writer(C, WE, fn (C, []const u8) WE!usize) 16 | // And the key thing is the third parameter, which "implements" the 17 | // function to write the data on the concrete type 18 | var writer = file.writer(); 19 | 20 | // Then I can just use the "write" method of io.Writer 21 | var bytes = try writer.write("hello 1\n"); 22 | try std.testing.expect(bytes == 8); 23 | 24 | // The point here is to provide "io.Writer"s to callers as a way 25 | // to transparently deal with writes into my "system" 26 | // (for example "my memory toy db", or "a file I own"). 27 | // It's some kind of structural typing that the compiler will 28 | // check once it's used. 29 | 30 | var stdout = std.io.getStdOut(); 31 | defer stdout.close(); 32 | var newline_writer = NewlineWriter.init(stdout.writer()); 33 | 34 | const mywriter = newline_writer.writer(); 35 | _ = try mywriter.write("hello 2"); 36 | 37 | // But how much can you abuse of this? Returning an anonymous struct 38 | // that mimics the structure of a known one. 39 | var my_struct = StructWithMsg{ .msg = "mario" }; 40 | sayHelloSomehow(my_struct); 41 | sayHelloSomehow(.{.msg = "'this looks like a hack'"}); 42 | 43 | // It looks like you can actually abuse a lot. But type coercion is 44 | // happening under the hood. So the anonymous struct is type casted 45 | // into a StructWithMsg. I checked this when I tried the following: 46 | // sayHelloSomehow(.{.msg = "'this looks like a hack'", a:"should not be here"}); 47 | // and it failed with 48 | // error: no member named 'a' in struct 'StructWithMsg' 49 | } 50 | 51 | const StructWithMsg = struct { 52 | msg: []const u8, 53 | }; 54 | 55 | fn sayHelloSomehow(s: StructWithMsg) void { 56 | std.debug.print("hello {s}\n", .{s.msg}); 57 | } 58 | 59 | // Get a writer that appends a new line to every message writes it to 60 | // the provided writer too. So it's like 61 | // 62 | // hey io.Writer, this is the method (fn writeWithNewline) you must 63 | // execute. The first argument of the method is a type NewLineWriter 64 | // but don't worry, I'll store in your .context field for you. And in 65 | // case of error, MyErrors is the type you'll receive. 66 | // 67 | // So, because NewlineWriter.writeWithNewline method requires a NewLineWriter 68 | // as first argument, I store it in the context field of the io.Writer. Many 69 | // languages don't allow this, to use an instance method you must call it with 70 | // dot notation or similar (whatever is called, frankly I don't remember now) 71 | const NewlineWriter = struct { 72 | const MyErrors = error{ 73 | BadError, 74 | VeryBadError, 75 | UnexpectedError, 76 | }; 77 | 78 | // io.Writer returns a type. A TYPE! not function, not a struct 79 | const Writer = io.Writer(*NewlineWriter, NewlineWriter.MyErrors, NewlineWriter.writeWithNewline); 80 | 81 | out: fs.File.Writer, 82 | 83 | pub fn init(o: fs.File.Writer) NewlineWriter { 84 | return NewlineWriter{ 85 | .out = o, 86 | }; 87 | } 88 | 89 | // This is the method that will be executed by io.Writer" every time I write 90 | pub fn writeWithNewline(self: *NewlineWriter, bytes: []const u8) NewlineWriter.MyErrors!usize { 91 | var written = self.out.write(bytes) catch return MyErrors.UnexpectedError; 92 | _ = self.out.write("\n") catch return MyErrors.UnexpectedError; 93 | return written + 1; 94 | } 95 | 96 | pub fn writer(self: *NewlineWriter) Writer { 97 | // THIS IS WERE MAGIC HAPPENS. This anonymous struct has the same 98 | // "structure" than the type returned by io.Writer(). Which is not 99 | // exactly true because it only represents the field `.context` 100 | // and no methods 101 | return .{ .context = self }; 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-snippets 2 | 3 | ## Mandatory hello world 4 | * [Hello world](hello_world.zig) 5 | 6 | ## Arrays and Slices 7 | * [Tokenizer:](parsing_tokenizer.zig) An exercise that started about something simple but it ended up teaching me a lot about array and pointer sizes and optionals. 8 | * [String Literals:](string_literals.zig) I wanted to understand better how string literals work and some interesting things about slices and arrays. 9 | * [String concatenation:](strings_concatenation.zig) Some example to do string concatenation. It's probably better to use a library like https://github.com/JakubSzark/zig-string but it's good snippet to have around when I just want to do something simple 10 | 11 | ## Reading, writing files and/or console: 12 | * [Read from StdIn:](stdin_read.zig) 13 | * [Read from StdIn in loop:](stdin_while.zig) 14 | * [Read a file:](file_read.zig) 15 | * [Write into a file:](file_write.zig) 16 | 17 | ## Allocators 18 | * [General purpose](allocators_general_purpose.zig) 19 | * [Page allocator:](allocators_page.zig) According to some video, this and the one above are probaly the easiest but the slower to use. 20 | * [Fixed Buffer:](allocators_fixed_buffer.zig) ~~Don't know why yet but I cannot make a "big" allocation with this one.~~ I could not make a big allocation with 21 | the fixed buffer because I was passing a buffer array created on the stack, not on the heap. See [this](https://zigforum.org/t/best-allocator-for-fixed-size-heap-allocation/789/3?u=sayden) conversation. 22 | * [Arena](allocators_arena.zig) 23 | * [Arena with fixed buffer](allocators_arena_with_fixed_buffer.zig) 24 | * [Allocators and Syscalls](allocators_syscall.zig) An example of the syscalls made to the underlying OS Linux to request memory with the output of `strace` 25 | 26 | ## Generics 27 | * [Generic Struct](generic_structs.zig) 28 | * [Generic Sum function](generic_sum.zig) 29 | * [Traits (sort of):](traits.zig) An exercise examining the use of io.Writer and the kind of structural typing to can do to have "traits". 30 | 31 | ## JSON 32 | * [Parse JSON](json_parse.zig) 33 | * [Stringify JSON](json_stringify.zig) 34 | 35 | ## Network 36 | * [TCP client](tcp_client.zig) TCP client that sends a single message. 37 | * [TCP server](tcp_server.zig) TCP Server that listens for incoming connections. 38 | * [Redis client](redis_client.zig) Toy Redis client using [Redis protocol](https://redis.io/topics/protocol) 39 | * [TCP Ping Pong](tcp_ping_pong.zig) TCP server that sends a message and listens for an incoming response. 40 | 41 | ## Async 42 | * [Simple Async/Await](async_await_simple.zig) A simple async/await example with sleeps without external pieces like the example below. 43 | * [Async/Await](async_await.zig) A clone of [TCP Ping Pong](tcp_ping_pong.zig) with async/await. 44 | 45 | ## Pointers 46 | * [Pointer arithmetic](pointer_arithmetic.zig) 47 | * [Dangling pointer](dangling_pointer.zig) 48 | * [Linked List with self managed memory](linked_list.zig) 49 | 50 | ## Parsing 51 | * [Parsing strings or bytes buffers](parsing.zig) Examples of parsing number in text `"1234"` into int `1234` and bytes arrays into known numbers. 52 | * [Tokenizer](parsing_tokenizer.zig) Small tokenizing exercise that ended up as a nice memory allocation exercise. 53 | 54 | ## Data structures 55 | * [Hash Table](hash_table.zig) 56 | * [Iterator](iterator.zig) 57 | * [Linked List with self managed memory](linked_list.zig) 58 | 59 | ## C interoperability 60 | Files that start with `c-*.*` 61 | 62 | ### Very basic example 63 | ```sh 64 | # messages like "Double of 4 is in C 8" should appear in stdout 65 | make libfunction 66 | ``` 67 | * [c-main.c](c-main.c) A simple main function in C just to see how it is done in plain C 68 | * [c-libfunction.c](c-libfunction.c) A very basic C library to load as a shared object `*.so` 69 | * [c-libfunction](c-libfunction.zig) The zig code that calls the `*.so` shared object that I created in [c-libfunction.c](c-libfunction.c) 70 | 71 | ### cURL example 72 | ```sh 73 | # a request is made and the plain HTML printed into stdout 74 | make curl 75 | ``` 76 | * [c-curl.c](c-curl.c) 77 | * [c-libcurl](c-libcurl.zig) 78 | 79 | ### Raylib example 80 | ```sh 81 | # NOTE: I need to download raylib to make this work. I placed it in ${HOME}/software/raylib 82 | # a window with a hello world message should open 83 | make ray 84 | ``` 85 | * [c-ray](c-ray.zig) Interop with the [Raylib](https://www.raylib.com) library (https://github.com/raysan5/raylib) 86 | 87 | ### Calling Zig from C 88 | At the moment of writing this README, the documentation is outdated and the header file is not generated as stated [in the docs](https://ziglang.org/documentation/master/#Exporting-a-C-Library). The workaround is to write the headers manually. Look at files `c-double_me*` for examples: 89 | ```sh 90 | ## Passing 8 in the c-double_me.c file 91 | make c-double_me 92 | 16 93 | ``` 94 | 95 | ## Misc 96 | * [example.txt](example.txt) To use in the [file_read](file_read.zig) snippet 97 | * [slow_server.go](slow_server.go) A TCP ping-pong server in Go that takes 2 seconds to respond (to play in [async](async.zig) and test the [Network](#network) snippets) -------------------------------------------------------------------------------- /string_literals.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const print = std.debug.print; 3 | 4 | pub fn main() !void { 5 | var string_literal:*const [30:0]u8 = "talk is cheap show me the code"; 6 | print("Type of string_literal {s} Size: {d}\n", .{ @TypeOf(string_literal), @sizeOf(@TypeOf(string_literal)) }); 7 | // Type of string_literal *const [30:0]u8 Size: 8 8 | 9 | // So why string literals are somehow hard on Zig? Well, looking at the type value of the first variable, `string_literal`, it is a `*const [30:0]u8` 10 | // or, in plain words: a pointer to a constant value represented by an array of 30 u8 items. The good thing is that it's a pointer, so it occupies 11 | // only 8 bytes if I want to move it around (the underlying array still is 31 bytes of size but I don't need to move it). The bad news is that it's 12 | // not very flexible to pass it around to other functions because the type has the size associated (so I can only use 30 bytes 'strings' on 13 | // functions for this example) which is the exact numbers of bytes of the sentence "talk is cheap show me the code" 14 | 15 | var string_literal_array = string_literal.*; 16 | print("Type of string_literal_array {s} Size: {d}\n", .{ @TypeOf(string_literal_array), @sizeOf(@TypeOf(string_literal_array)) }); 17 | // Type of string_literal_array [30:0]u8 Size: 31 18 | 19 | // Here the situation is a bit worse. Now I don't have a pointer but, instead, I have an array directly. This means that if I pass it to a function 20 | // which accepts 30 bytes arrays, Zig will copy the entire array to the function. But this is what slices are for, right? To create runtime pointers 21 | // to regions of an array that will contain a memory direction and a length. 22 | 23 | var string_literal_slice = string_literal_array[0..]; 24 | print("Type of string_literal_slice {s} Size: {d}\n", .{ @TypeOf(string_literal_slice), @sizeOf(@TypeOf(string_literal_slice)) }); 25 | // Type of string_literal_slice *[30:0]u8 Size: 8 26 | 27 | // Back to square zero? I kind of progressed a bit. Now I have a pointer to an array, like at the beginning, but the size of the array is still there. 28 | // The good news is that this is again only 8 bytes to move around. The bad news is that it is still not flexible to use it in a function that accepts 29 | // arbitrary size slices. And it's not constant... so... if I modify it, will the original be modified too? 30 | 31 | string_literal_slice[16] = '0'; 32 | print("'{s}' != '{s}' == {s}?\n", .{string_literal, string_literal_slice, string_literal_array}); 33 | // 'talk is cheap show me the code' != 'talk is cheap sh0w me the code' == talk is cheap sh0w me the code? 34 | 35 | // Nope. The original is left intact because when I created string_literal_array var, I copied the contents of `string_literal`. 36 | 37 | var string_literal_coerced: []u8 = string_literal_slice; 38 | print("Type of string_literal_coerced {s} Size: {d}\n", .{ @TypeOf(string_literal_coerced), @sizeOf(@TypeOf(string_literal_coerced)) }); 39 | // Type of string_literal_coerced []u8 Size: 16 40 | 41 | // And this is what we really wanted. The size is not part of the type anymore, but that doesn't mean that the size is unknown, it's still stored in 42 | // the slice but, and I repeat, **the size is not part of the type**. The slice occupies 16 bytes, 8 bytes for the pointer and 8 bytes to store the 43 | // slice size. 44 | 45 | var string_literal_size_unknown: [*]u8 = string_literal_slice; 46 | print("Type of string_literal_coerced {s} Size: {d}\n", .{ @TypeOf(string_literal_size_unknown), @sizeOf(@TypeOf(string_literal_size_unknown)) }); 47 | 48 | // This is the last thingy. Here the size of the slice **is not part of the type** AND it is also **unknown*, so just a pointer to the beginning of an array 49 | // without size information, effectively 8 bytes in memory. Why using this if I won't know when to stop? Because the size might be stored somewhere else 50 | // like in a local variable or something like that. Why use it? I might want to use this in situations where the number of items in an array are not the 51 | // same that the value returned by `array.len`. For example when allocating an array of values, the slice returned is []u8: 52 | 53 | var page = std.heap.page_allocator; 54 | 55 | var mem: []u8 = try page.alloc(u8,8); 56 | defer page.free(mem); 57 | mem[0] = 'a'; 58 | print("Type of mem {s} Size: {d}. Content: {s}\n", .{ @TypeOf(mem), @sizeOf(@TypeOf(mem)), mem }); 59 | // Type of mem []u8 Size: 16. Content: a������� 60 | 61 | // Like this. My slice has size 10 but only the first byte has valid values. One way to solve this is to pass a "slice of the slice" like mem[0..1] which 62 | // effectively says "this slice actually starts in 0 and has length 1" which can be coerced to []u8. At the end, it's some kind of type restriction to 63 | // disallow the use of `len` in some parts of the code. 64 | 65 | var unknown_size: [*]u8 = mem.ptr; 66 | _ = unknown_size; 67 | 68 | // BTW I can get an unknown size slice using slice.ptr 69 | print("Type of unknown_size {s} Size: {d}\n", .{ @TypeOf(unknown_size), @sizeOf(@TypeOf(unknown_size)) }); 70 | } -------------------------------------------------------------------------------- /linked_list.zig: -------------------------------------------------------------------------------- 1 | //! This snippet was painful for my lack of knowledge about memory allocation. 2 | //! It ended up being a VERY NICE EXERCISE once everything *clicked* in my head. 3 | //! The main pain point was the `Cell` allocation. I got many mistakes like 4 | //! overriding the allocated pointer with a local pointer, for example. 5 | //! The "turning point" was the `Cell(T).initAlloc` function and its structure. 6 | //! 7 | //! At first I was using the Arena allocator, which is OK for a small example. 8 | //! Yes, it handles the free of all memory allocations at once but if I 9 | //! remove a single item, that memory won't be freed until the end of the 10 | //! program execution. So I rewrote the script using a FixedBufferAllocator 11 | //! and manually checking how much memory I needed to provoke a OutOfMemory 12 | //! error. 13 | 14 | const std = @import("std"); 15 | const expect = std.testing.expect; 16 | const Allocator = std.mem.Allocator; 17 | 18 | fn Cell(comptime T: type) type { 19 | return struct { 20 | data: T, 21 | 22 | next: ?*Cell(T), 23 | 24 | const Self = @This(); 25 | 26 | fn initAlloc(data: T, alloc: *Allocator) !*Self { 27 | var new_item = try alloc.create(Cell(T)); 28 | new_item.data = data; 29 | new_item.next = null; 30 | return new_item; 31 | } 32 | }; 33 | } 34 | 35 | fn LinkedList(comptime T: type) type { 36 | return struct { 37 | first: ?*Cell(T) = null, 38 | 39 | alloc: *Allocator, 40 | 41 | const Self = @This(); 42 | 43 | const Error = error{ 44 | EmptyList, 45 | NotFound, 46 | }; 47 | 48 | var this: Self = undefined; 49 | 50 | pub fn init(alloc: *Allocator) Self { 51 | this = Self{ 52 | .alloc = alloc, 53 | }; 54 | 55 | return this; 56 | } 57 | 58 | pub fn add(self: *Self, data: T) !void { 59 | var new_item = try Cell(T).initAlloc(data, self.alloc); 60 | 61 | if (self.first) |first| { 62 | var p = first; 63 | 64 | while (p.next) |next| { 65 | p = next; 66 | } 67 | 68 | p.next = new_item; 69 | } else { 70 | self.first = new_item; 71 | } 72 | 73 | return; 74 | } 75 | 76 | // this method can probably be simpler and it DOESN'T 77 | // DEALLOCATES THE MEMORY 78 | pub fn remove(self: *Self, data: T) !void { 79 | var iter = self.iterator(); 80 | 81 | var maybe_previous: ?*Cell(T) = null; 82 | var candidate = self.first; 83 | var found = false; 84 | while (iter.next()) |item| { 85 | if (item.data == data) { 86 | candidate = item; 87 | found = true; 88 | break; 89 | } 90 | maybe_previous = candidate; 91 | candidate = item; 92 | } 93 | 94 | if (found) { 95 | defer self.alloc.destroy(candidate.?); 96 | 97 | if (maybe_previous) |prev| { 98 | prev.next = candidate.?.next; 99 | // TODO Deallocate candidate memory 100 | } else { 101 | //Removing first item 102 | self.first = candidate.?.next; 103 | // TODO Deallocate candidate memory 104 | } 105 | } else { 106 | return Error.NotFound; 107 | } 108 | } 109 | 110 | pub fn iterator(self: *Self) Iterator(T) { 111 | return Iterator(T).init(self.first); 112 | } 113 | 114 | pub fn display(self: *Self) void { 115 | var iter = self.iterator(); 116 | 117 | while (iter.next()) |item| { 118 | std.debug.print("'{d}'->", .{item.data}); 119 | } 120 | 121 | std.debug.print("\n", .{}); 122 | } 123 | }; 124 | } 125 | 126 | fn Iterator(comptime T: type) type { 127 | return struct { 128 | const Self = @This(); 129 | 130 | current: ?*Cell(T), 131 | 132 | fn init(first: ?*Cell(T)) Self { 133 | return Self{ 134 | .current = first, 135 | }; 136 | } 137 | 138 | fn next(self: *Self) ?*Cell(T) { 139 | if (self.current) |cur| { 140 | const temp = cur; 141 | self.current = if (temp.next) |next_item| next_item else null; 142 | return temp; 143 | } 144 | 145 | return null; 146 | } 147 | }; 148 | } 149 | 150 | pub fn main() !void { 151 | // With some trial and error I discovered that for this toy example 152 | // with 4 items I needed exactly 102 bytes of ~~heap~~ stack allocation. 153 | // (`buffer` is created on the stack) 154 | var buffer:[102]u8 = undefined; 155 | var fba = std.heap.FixedBufferAllocator.init(&buffer); 156 | 157 | var ll = LinkedList(u64).init(&fba.allocator); 158 | 159 | try ll.add(1); 160 | try ll.add(2); 161 | try ll.add(3); 162 | 163 | ll.display(); 164 | 165 | ll.remove(2) catch unreachable; 166 | ll.display(); 167 | 168 | try ll.add(4); 169 | ll.display(); 170 | 171 | ll.remove(1) catch unreachable; 172 | ll.display(); 173 | 174 | ll.remove(4) catch unreachable; 175 | ll.display(); 176 | 177 | ll.remove(4) catch |err| try expect(err == LinkedList(u64).Error.NotFound); 178 | // GOTCHA, the `try` clause above is working over the `expect` call, not on `remove` 179 | // but I can also place it at the beginning with the same result 180 | try ll.remove(4) catch |err| expect(err == LinkedList(u64).Error.NotFound); 181 | 182 | // This will be a 5th item but we are deallocating nicely so it fits 183 | try ll.add(5); 184 | ll.display(); 185 | 186 | // This is too much memory because I allocated only 102 bytes for 5 items. 187 | // Handle this ugly out of memory error nicely 188 | ll.add(6) catch |err| try expect(err == error.OutOfMemory); 189 | } 190 | -------------------------------------------------------------------------------- /allocators_syscall.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !void { 4 | var page = std.heap.page_allocator; 5 | const mem = try page.alloc(u8, 5); 6 | defer page.free(mem); 7 | 8 | const mem1 = try page.alloc(u8, 5); 9 | defer page.free(mem1); 10 | 11 | // If I build this program with `zig build-exe -O ReleaseSmall` and execute the binary with `strace` this is what I get: 12 | // $ zig build-exe -O ReleaseSmall allocators_syscall.zig && strace ./allocators_syscall.zig 13 | // 14 | // (strace output) 15 | // 16 | // 17 | // mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcb42632000 18 | // ^^^^ 19 | // the first call to page.alloc on line 5 20 | // 21 | // mmap(0x7fcb42633000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcb42633000 22 | // ^^^^ 23 | // the second call to page.alloc on line 8 24 | // 25 | // And two `free` deferred calls 26 | // munmap(0x7fcb42633000, 4096) = 0 27 | // munmap(0x7fcb42632000, 4096) = 0 28 | // ^^^^^^ 29 | // deferred calls to free memory 30 | // 31 | // exit_group(0) = ? 32 | // +++ exited with 0 +++ 33 | 34 | // So, every time I execute a `page.alloc` a `mmap` syscall if done, which is "slow". So I thought 35 | // "does it also happen with a fixed buffer allocator? I guess not but I can check it with strace too" 36 | 37 | // std.mem.page_size is a constant that will return the page size for the current operating system, which 38 | // is a value that DIFFERS and QUITE A LOT between OS. In my case on a linux it's returning a 4096 bytes 39 | // but on a macosx it's a 16384, 4 times more. This is, as far as I know, the size of a page in the 40 | // operating system (which in linux is hardcoded in the kernel but you can change it). 41 | // https://unix.stackexchange.com/questions/128213/how-is-page-size-determined-in-virtual-address-space 42 | 43 | // I also realized, when checked the first 2 `mmap` calls requesting only 5 bytes each (above), that a 44 | // full 4096 page is requested and 4091 of those bytes are "nullified". You can see the explanation of 45 | // this in the `alloc` function. But, the address of the first and the second call are 4096 bytes apart. 46 | // So, does that mean that those 4091 bytes are "lost"? It's some kind of empty space that will be reused 47 | // somehow at some moment under the hood? 48 | 49 | // Ok great, let's continue. 50 | 51 | // So, I'll do another alloc call but without "wasted" bytes, using the OS page size. One single syscall, 52 | // and I'll get a single page too. 53 | var buffer = try page.alloc(u8, std.mem.page_size); 54 | defer page.free(buffer); 55 | 56 | var fba = std.heap.FixedBufferAllocator.init(buffer); 57 | var fba_allocator = &fba.allocator; 58 | 59 | var mem2 = try fba_allocator.alloc(u8, 5); 60 | defer fba_allocator.free(mem2); 61 | 62 | var mem3 = try fba_allocator.alloc(u8, 5); 63 | defer fba_allocator.free(mem3); 64 | 65 | var mem4 = try fba_allocator.alloc(u8, 5); 66 | defer fba_allocator.free(mem4); 67 | 68 | // (strace output) 69 | // 70 | // mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f790fc1b000 71 | // mmap(0x7f790fc1c000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f790fc1c000 72 | // ^^^^ 73 | // Original 2 calls 74 | // 75 | // mmap(0x7f790fc1d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f790fc1d000 76 | // ^^^^ 77 | // New call to get a buffer, line 51. After this I "alloc" 3 times using the Fixed Buffer Allocator, 78 | // and I don't get more syscalls to get memory. 79 | // 80 | // munmap(0x7f790fc1d000, 4096) = 0 81 | // munmap(0x7f790fc1c000, 4096) = 0 82 | // munmap(0x7f790fc1b000, 4096) = 0 83 | // ^^^^^^ 84 | // Finally I free everything, the original two calls and the last page. 85 | // 86 | // exit_group(0) = ? 87 | // +++ exited with 0 +++ 88 | 89 | // Last think I checked, what if I request memory bigger than a page size? So a single syscall is made, but 90 | // I guess that many pages are returned, but definitely there's a single syscall. 91 | 92 | // Wait a sec, I'm gonna check the arena allocator: 93 | 94 | var arena_alloc = std.heap.ArenaAllocator.init(std.heap.page_allocator); 95 | defer arena_alloc.deinit(); 96 | 97 | var allocator = &arena_alloc.allocator; 98 | 99 | const mem5 = try allocator.alloc(u8, 5); 100 | _ = mem5; 101 | 102 | const mem6 = try allocator.alloc(u8, 4096); 103 | _ = mem6; 104 | 105 | // Arena allocator behaves similarly to the fixed allocator but, interestingly if you request "too much memory" 106 | // a new syscall is done under the hood, in this example of 4 pages or 16384 bytes: 107 | // 108 | // ➜ zig-snippets git:(main) ✗ zig build-exe -O ReleaseSmall allocators_syscall.zig && strace ./allocators_syscall 109 | // 110 | // mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1d276ea000 111 | // mmap(0x7f1d276eb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1d276eb000 112 | // mmap(0x7f1d276ec000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1d276ec000 113 | // 114 | // mmap(0x7f1d276ed000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1d276ed000 115 | // ^^^^ 116 | // Initial call from are allocator, a single page 117 | // mmap(0x7f1d276ee000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1d276ee000 118 | // ^^^^ 119 | // This is the extra call made when I requested 4096 bytes which is higher than the initial page size 120 | // 121 | // munmap(0x7f1d276ee000, 16384) = 0 122 | // munmap(0x7f1d276ed000, 4096) = 0 123 | // munmap(0x7f1d276ec000, 4096) = 0 124 | // munmap(0x7f1d276eb000, 4096) = 0 125 | // munmap(0x7f1d276ea000, 4096) = 0 126 | // 127 | // exit_group(0) = ? 128 | // +++ exited with 0 +++ 129 | } 130 | -------------------------------------------------------------------------------- /hash_table.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const expect = std.testing.expect; 4 | 5 | fn DataItem(comptime T: type) type { 6 | return struct { 7 | data: T, 8 | key: u64, 9 | }; 10 | } 11 | 12 | fn HashArray(comptime T: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | const HashError = error{ 17 | NotFound, 18 | }; 19 | 20 | ar: []?DataItem(T), 21 | SIZE: u64, 22 | 23 | fn hashCode(self: *Self, key: u64) u64 { 24 | return key % self.SIZE; 25 | } 26 | 27 | pub fn init(size: u64, alloc: *Allocator) !Self { 28 | return Self{ 29 | .ar = try alloc.alloc(?DataItem(T), size), 30 | .SIZE = size, 31 | }; 32 | } 33 | 34 | pub fn insert(self: *Self, key: u64, data: T) void { 35 | const item = DataItem(T){ 36 | .data = data, 37 | .key = key, 38 | }; 39 | 40 | //get the hash 41 | var hash_index = self.hashCode(key); 42 | 43 | while (self.itemAt(hash_index)) |_| : (hash_index += 1) { 44 | hash_index %= self.SIZE; //wrap around the table 45 | } 46 | 47 | self.ar[hash_index] = item; 48 | } 49 | 50 | pub fn delete(self: *Self, key: u64) !DataItem(T) { 51 | var hash_index = self.hashCode(key); //get the hash 52 | 53 | //move in array until an empty 54 | return while (self.itemAt(hash_index)) |cur_item| : ({ 55 | hash_index += 1; 56 | hash_index %= self.SIZE; 57 | }) { 58 | if (cur_item.key == key) { 59 | self.setItemAt(hash_index, null); 60 | return cur_item; 61 | } 62 | } else HashError.NotFound; 63 | } 64 | 65 | fn search(self: *Self, key: u64) ?DataItem(T) { 66 | var hash_index = self.hashCode(key); //get the hash 67 | 68 | //move in array until empty 69 | return while (self.itemAt(hash_index)) |cur_item| : ({ 70 | hash_index += 1; 71 | hash_index %= self.SIZE; 72 | }) { 73 | if (cur_item.key == key) return cur_item; 74 | } else null; 75 | } 76 | 77 | fn setItemAt(self: *Self, index: u64, item: ?DataItem(T)) void { 78 | self.ar[index] = item; 79 | } 80 | 81 | fn itemAt(self: *Self, index: u64) ?DataItem(T) { 82 | return self.ar[index]; 83 | } 84 | 85 | fn display(self: *Self) void { 86 | for (self.ar) |maybe_item| { 87 | if (maybe_item) |item| 88 | std.debug.print(" ({d},{any})", .{ item.key, item.data }) 89 | else 90 | std.debug.print(" ~~ ", .{}); 91 | } 92 | 93 | std.debug.print("\n", .{}); 94 | } 95 | }; 96 | } 97 | 98 | // using a wrapper to allow printing pretty names. By default the formatter {any} 99 | // in display() on HashArray will print the array of bytes instead of the text. 100 | const MyFormattedString = struct { 101 | innerString: []const u8, 102 | 103 | pub fn new(text: []const u8) MyFormattedString { 104 | return MyFormattedString{ 105 | .innerString = text, 106 | }; 107 | } 108 | 109 | pub fn format( 110 | self: MyFormattedString, 111 | comptime fmt: []const u8, 112 | options: std.fmt.FormatOptions, 113 | writer: anytype, 114 | ) !void { 115 | //We won't use this 116 | _ = fmt; 117 | _ = options; 118 | 119 | try writer.print("{s}", .{self.innerString}); 120 | } 121 | }; 122 | 123 | pub fn main() !void { 124 | // Using arena allocator over the page allocator to free only once (as an exercise) 125 | var arena_alloc = std.heap.ArenaAllocator.init(std.heap.page_allocator); 126 | defer arena_alloc.deinit(); 127 | var allocator = &arena_alloc.allocator; 128 | 129 | try withU64(allocator); 130 | try withStrings(allocator); 131 | } 132 | 133 | fn withStrings(alloc: *Allocator) !void { 134 | std.debug.print("\n",.{}); 135 | 136 | var hash_array = try HashArray(MyFormattedString).init(20, alloc); 137 | 138 | hash_array.insert(1, MyFormattedString.new("hello")); 139 | hash_array.insert(2, MyFormattedString.new("world")); 140 | hash_array.insert(42, MyFormattedString.new("mario")); 141 | hash_array.insert(4, MyFormattedString.new("tyrion")); 142 | hash_array.insert(12, MyFormattedString.new("tesla")); 143 | hash_array.insert(14, MyFormattedString.new("ula")); 144 | 145 | hash_array.display(); 146 | 147 | var deleted_item = try hash_array.delete(2); 148 | std.debug.print("Item with key '{d}' deleted\n", .{deleted_item.key}); 149 | 150 | hash_array.display(); 151 | 152 | _ = hash_array.delete(111) catch |err| std.debug.print("could not delete record '111': {s}\n", .{err}); 153 | 154 | var item = hash_array.search(12) orelse unreachable; 155 | std.debug.print("Item found: key={1d} value={0d}\n", item); 156 | 157 | _ = hash_array.search(111) orelse std.debug.print("Value '{d}' not found\n", .{111}); 158 | } 159 | 160 | fn withU64(alloc: *Allocator) !void { 161 | var hash_array = try HashArray(u64).init(30, alloc); 162 | 163 | hash_array.insert(1, 20); 164 | hash_array.insert(2, 70); 165 | hash_array.insert(42, 80); 166 | hash_array.insert(4, 25); 167 | hash_array.insert(12, 44); 168 | hash_array.insert(14, 32); 169 | hash_array.insert(17, 11); 170 | hash_array.insert(13, 78); 171 | hash_array.insert(37, 97); 172 | 173 | hash_array.display(); 174 | 175 | var deleted_item = try hash_array.delete(17); 176 | std.debug.print("Item with key '{d}' deleted\n", .{deleted_item.key}); 177 | 178 | hash_array.display(); 179 | 180 | _ = hash_array.delete(111) catch |err| std.debug.print("could not delete record '111': {s}\n", .{err}); 181 | 182 | var item = hash_array.search(13) orelse unreachable; 183 | std.debug.print("Item found: key={1d} value={0d}\n", item); 184 | 185 | _ = hash_array.search(111) orelse std.debug.print("Value '{d}' not found\n", .{111}); 186 | } 187 | --------------------------------------------------------------------------------