openmymind.net

Programming blog exploring Zig, Elixir, Go, Testing, Design and Performance
https://www.openmymind.net/ (RSS)
visit blog
Zig's new declaration literals
14 Nov 2024 | original ↗

In the last post, we looked at some of Zig' weirder syntax. Specifically, this line of code: var gpa = std.heap.GeneralPurposeAllocator(.{}){}; While you'll commonly run into the above when looking at Zig code or going through Zig learning resources, some people pointed out that the code could be improved by doing: var gpa:...

Zig's (.{}){} syntax
5 Nov 2024 | original ↗

One of the first pieces of Zig code that you're likely to see, and write, is this beginner-unfriendly line: var gpa = std.heap.GeneralPurposeAllocator(.{}){}; While we can reason that we're creating an allocator, the (.{}){} syntax can seem a bit much. This is a combination of three separate language features: generics, anonymous struct literals...

TCP Server in Zig - Part 8 - Epoll & Kqueue
30 Oct 2024 | original ↗

Now that we're more familiar with epoll and kqueue individually, it's time to bring everything together. We'll begin by looking at the possible interaction between evented I/O and threads and then look at writing a basic cross-platform abstraction over the platform-specific epoll and kqueue. Evented I/O + Multithreading We began our journey with...

TCP Server in Zig - Part 7 - Kqueue
27 Oct 2024 | original ↗

kqueue is a BSD/MacOS alternative over poll. In most ways, kqueue is similar to the Linux-specific epoll, which itself is important, but important, incremental upgrade to poll. Because kqueue has a single function it superficially looks like poll. But, as we'll soon see, that single function can behave in two different ways, making its API and...

TCP Server in Zig - Part 6 - Epoll
23 Oct 2024 | original ↗

In the last two parts we introduced the poll system call and, with it, patterns around evented I/O. poll has the advantage of being simple to use and supported on most platforms. However, as we saw, we need to iterate through all monitored sockets to figure out which is ready. Another awkwardness with poll's API is associating...

TCP Server in Zig - Part 5b - Poll
17 Oct 2024 | original ↗

In the previous part we introduced non-blocking sockets and used them, along with the poll system call, to maximize the efficiency of our server. Rather than having a thread-per-connection, waiting on data, a single thread can now manage multiple client connections. But this performance leap doesn't come for free: our code has gotten more...

TCP Server in Zig - Part 5a - Poll
15 Oct 2024 | original ↗

One of the reasons we introduced multithreading was to get around that fact that our read and, to a lesser extent, accept and write, block. In our initial single-threaded implementation, rather than pushing our server to its limits, we spent a lot of time idle, waiting for data to come in. Multithreading helped to unblock the main thread so that...

TCP Server in Zig - Part 4 - Multithreading
11 Oct 2024 | original ↗

We finished Part 1 with a simple single-threaded server, which we could describe as: Create our socket Bind it to an address Put it in "server" mode (i.e. call listen on it) Accept a connection Application logic involving reading/writing to the socket Close the connection Goto step 4 While this approach is useful for getting...

TCP Server in Zig - Part 3 - Minimizing Writes & Reads
8 Oct 2024 | original ↗

Before we look at making our server multi-threaded, and then move to polling, there are two optimization techniques worth exploring. You might think that we should finalize our code before applying optimizations, but I think optimizations in general can teach us things to look out for / consider, and it's particularly true in both these cases. In...

TCP Server in Zig - Part 2 - Message Boundaries
5 Oct 2024 | original ↗

This part isn't Zig-specific. If you're familiar with length-prefixed messages and binary encoding vs text encoding, you can probably skip it. When thinking about communication between systems using TCP, we generally think about it in terms of messages: an HTTP response, a database record, etc. The implementation though comes down to writing and...

TCP Server in Zig - Part 1 - Single Threaded
2 Oct 2024 | original ↗

In this series we're going to look at building a TCP server in Zig. We're going to start with a simple single-threaded server so that we can focus on basics. In following parts, we'll make our server multi-threaded and then introduce polling (poll, epoll and kqueue). We begin with a little program that compiles and runs but doesn't do much: const...

Creating enums at comptime
19 Sept 2024 | original ↗

In Basic MetaProgramming in Zig we saw how std.meta.hasFn uses @typeInfo to determine if the type is a struct, union or enum. In this post, we'll take expand that introduction and use std.builtin.Type to create our own type. Spoiler: as far as I know, you cannot (yet) define methods on comptime-generated structs, unions or enums. Only fields....

Closures in Zig
10 Sept 2024 | original ↗

Zig doesn't have a simple way to create closures because closures require allocation, and Zig has a "no hidden allocation" policy. The values that you want to capture need to live somewhere (i.e. the heap), but there's no way for Zig (the language) to create the necessary heap memory. However, outside of the language, either in the standard...

Zig's @memcpy, copyForwards and copyBackwards
3 Sept 2024 | original ↗

If you've used Zig for a bit, you've probably come across the @memcpy builtin. It copies bytes from one region of memory to another. For example, if we wanted to concat two arrays, we could write a little helper: fn concat(comptime T: type, allocator: std.mem.Allocator, arr1: []const T, arr2: []const T) ![]T { var combined = try...

Zig's BoundedArray
27 Aug 2024 | original ↗

In Zig an array always has a compile-time length. The length of the array is part of the type, so a [4]u32 is a different type than a [5]u32. But in real-life code, the length that we need is often known only at runtime so we rely on dynamic allocations via allocator.alloc. In some cases the length isn't even known until after we're done adding...

Custom String Formatting and JSON [De]Serializing in Zig
23 Aug 2024 | original ↗

In our last blog post, we saw how builtins like @hasDecl and functions like std.meta.hasMethod can be used to inspect a type to determine its capabilities. Zig's standard library makes use of these in a few place to allow developers to opt-into specific behavior. In particular, both std.fmt and std.json provide developers the ability to define...

Basic MetaProgramming in Zig
13 Aug 2024 | original ↗

While I've written a lot about Zig, I've avoided talking about Zig's meta programming capabilities which, in Zig, generally falls under the "comptime" umbrella. The idea behind "comptime" is to allow Zig code to be run at compile time in order to generate code. It's often said that an advantage of Zig's comptime is that it's just Zig code, as...

Zig's HashMap - Part 3
7 Aug 2024 | original ↗

Previously On... In Part 1 we saw how Zig's StringHashMap and AutoHashMap are wrappers around a HashMap. HashMap works across different key types by requiring a Context. Here's the built-in context that StringHashMap uses: pub const StringContext = struct { pub fn hash(_: StringContext, s: []const u8) u64 { return std.hash.Wyhash.hash(0,...

Zig's Temporary Variable are Const
23 Jul 2024 | original ↗

I frequently run into a silly compilation error which, embarrassingly, always takes me a couple of seconds to decipher. This most commonly happens when I'm writing tests. Here's a simple example: fn add(values: []i64) i64 { var total: i64 = 0; for (values) |v| { total += v; } return total; } test "add" { const actual = add(&.{1, 2,...

Using a custom test runner in Zig
15 Jul 2024 | original ↗

Depending on what you're used to, Zig's built-in test runner might seem a little bare. For example, you might prefer a more verbose output, maybe showing your slowest tests. You might need control over the output in order to integrate it into a larger build process or want to have global setup and teardown functions. Maybe you just want to be...

Zig's @constCast
6 Jul 2024 | original ↗

In the Coding in Zig section of my Learning Zig series, an invalid snippet was recently pointed out to me. The relevant part was: if (try stdin.readUntilDelimiterOrEof(&buf, '\n')) |line| { var name = line; if (builtin.os.tag == .windows) { name = std.mem.trimRight(u8, name, "\r"); } if (name.len == 0) { break; } try...

Be Careful When Assigning ArenaAllocators
26 Jun 2024 | original ↗

A bug was recently reported in pg.zig which was the result of a dangling pointer to an ArenaAllocator (1). This amused me since (a) I write a lot about dangling pointers (b) I write a bit about ArenaAllocators and (c) it isn't the first time I've messed this up. (1) Actually, multiple bugs were reported (and fixed), but lets not dwell on that....

Zig's std.json.parseFormSlice and std.json.Parsed(T)
18 Jun 2024 | original ↗

In Zig's discord server, I see a steady stream of developers new to Zig struggling with parsing JSON. I like helping with this problem because you can learn a lot about Zig through it. A typical, but incorrect, first attempt looks something like: const std = @import("std"); const Allocator = std.mem.Allocator; const Config = struct { db_path:...

Leveraging Zig's Allocators
5 Jun 2024 | original ↗

Let's say we wanted to write an HTTP server library for Zig. At the core of this library, we might have a pool of threads to handle requests. Keeping things simple, it might look something like: fn run(worker: *Worker) void { while (queue.pop()) |conn| { const action = worker.route(conn.req.url); action(conn.req, conn.res) catch { //...

Zig: Freeing resources referenced in multiple threads
21 May 2024 | original ↗

As you learn Zig, you'll see examples of memory being allocated and through the use of defer, freed. Often, these allocations and deallocations are wrapped in init and deinit functions. But whatever specific implementation is used, the point is to show a common pattern which is suitable in simple cases. It isn't too much of a leap to take such...

Writing a task scheduler in Zig
9 May 2024 | original ↗

I'm working on an application that needs the ability to schedule tasks. Many applications have a similar need, but requirements can vary greatly. Advanced cases might require persistence and distribution, typically depending on external systems (like a database or queue) to do much of the heavy lifting. My needs are simpler: I don't have a huge...

Zig: Use The Heap to Know A Value's Address
15 Mar 2024 | original ↗

You'll often find yourself wanting to know the address of a newly created value which gets returned from a function. This can happen for a number of reasons, but the most common is creating bidirectional references. For example, if we create a pool of objects, we often want those objects to reference the pool. You might end up with something...

Being Mindful of Memory Allocations
20 Feb 2024 | original ↗

One consequence of programming without a garbage collector is that you're generally more aware of every memory allocation your program makes. Ideally, this increased awareness results in more prudent memory use. Developers often lament that while hardware has gotten faster, computers feel more sluggish. I don't know how true this is, and if it is...

Compile-Time Configuration For Zig Libraries
5 Feb 2024 | original ↗

If you're writing a Zig library, you might find yourself wishing to expose a compile-time configuration option to application developers. One of the reasons you might want to do this for performance reasons, preferring to do something at compile-time versus runtime. Consider this example using my PostgreSQL library: var result = try...

Why is Simple Code So Often Bad?
30 Jan 2024 | original ↗

I recently wrote a Prometheus client library for Zig. I had to write a custom hash map context (what Zig calls the eql and hash functions) which led to my last couple blog posts exploring Zig's hash maps in more details. The last topic covered was the getOrPut method which returns a pointer to the key and value arrays of where the entry is or...

Zig's HashMap - Part 2
22 Jan 2024 | original ↗

In part 1 we explored how the six HashMap variants relate to each other and what each offered to developers. We largely focused on defining and initializing HashMaps for various data type and utilizing custom hash and eql functions for types not supported by the StringHashMap or AutoHashMap. In this part we'll focus on keys and values, how...

Zig's HashMap - Part 1
16 Jan 2024 | original ↗

This blog posts assumes that you're comfortable with Zig's implementation of Generics. Like most hash map implementations, Zig's std.HashMap relies on 2 functions, hash(key: K) u64 and eql(key_a: K, key_b: K) bool. The hash function takes a key and returns an unsigned 64 bit integer, known as the hash code. The same key always returns the same...

Your Website Search Hurts My Feelings
26 Dec 2023 | original ↗

img{border: 1px solid black;margin:0 auto 40px;display:block} I wouldn't mind this huge ad so much if the pancake mix was made with lentil flour...on second thought, that doesn't sound good: And if I complain about not being able to sort by price/quality (or weight), I assume some people will point out that choice adds complexity. But it's hard...

Zig's MemoryPool Allocator
22 Dec 2023 | original ↗

You're probably familiar with Zig's GeneralPurposeAllocator, ArenaAllocator and FixedBufferAllocator, but Zig's standard library has another allocator you should be keeping at the ready: std.heap.MemoryPool. What makes the MemoryPool allocator special is that it can only create one type. As compensation for this limitation, it's very fast when...

Zig: Tiptoeing Around @ptrCast
11 Dec 2023 | original ↗

A variable associates memory with a specific type. The compiler uses this information to generate the correct instructions (or to tell us that our code is invalid). For example, given this code: const std = @import("std"); const User = struct { id: u32, name: []const u8, }; pub fn main() !void { const user1 = User{.id = 1, .name =...

Performance of reading a file line by line in Zig
27 Nov 2023 | original ↗

Maybe I'm wrong, but I believe the canonical way to read a file, line by line, in Zig is: const std = @import("std"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); var file = try std.fs.cwd().openFile("data.txt", .{}); defer file.close(); // Things are _a lot_ slower if we...

Zig's std.json.Parsed(T)
17 Nov 2023 | original ↗

When parsing JSON using one of Zig's std.json.parseFrom* functions, you'll have to deal with the return type: an std.json.Parsed(T): const file = try std.fs.openFileAbsolute(file_path, .{}); defer file.close(); var buffered = std.io.bufferedReader(file.reader()); var reader = std.json.reader(allocator, buffered.reader()); defer reader.deinit();...

Fast Path to Burnout - Delaying Deploys
27 Oct 2023 | original ↗

Deploying code to production is either the funnest part of my day or the most stressful. Where I land on that spectrum largely comes down to how much control I have over the process. Specifically, I like being able to deploy code when I want to deploy it. Process, and people, that don't get this, don't understand software development (and I'm not...

Zig Interfaces
8 Oct 2023 | original ↗

If you're picking up Zig, it won't be long before you realize there's no syntactical sugar for creating interfaces. But you'll probably notice interface-like things, such as std.mem.Allocator. This is because Zig doesn't have a simple mechanism for creating interfaces (e.g. interface and implements keywords), but the language itself can be used...

Learning Zig - Language Overview - Part 2
8 Sept 2023 | original ↗

Language Overview - Part 1 Intro Style Guide Language Overview - Part 2 This part continues where the previous left off: familiarizing ourselves with the language. We'll explore Zig's control flow and types beyond structures. Together with the first part, we'll have covered most of the language's syntax allowing us to tackle more of the...

Learning Zig - Style Guide
8 Sept 2023 | original ↗

Language Overview - Part 2 Intro Pointers Style Guide In this short part, we'll cover two coding rules enforced by the compiler as well as the standard library's naming convention. Unused Variables Zig does not allow variables to go unused. The following gives two compile-time errors: const std = @import("std"); pub fn main() void {...

Learning Zig - Pointers
8 Sept 2023 | original ↗

Style Guide Intro Stack Memory Pointers Zig doesn't include a garbage collector. The burden of managing memory is on you, the developer. It's a big responsibility as it has a direct impact on the performance, stability and security of your application. We'll begin by talking about pointers, which is an important topic to discuss in and of...

Learning Zig - Stack Memory
8 Sept 2023 | original ↗

Pointers Intro Heap Memory & Allocators Stack Memory Diving into pointers provided insight into the relationship between variables, data and memory. So we're getting a sense of what the memory looks like, but we've yet to talk about how data and, by extension, memory is managed. For short lived and simple scripts, this likely doesn't...

Learning Zig - Heap Memory & Allocators
8 Sept 2023 | original ↗

Stack Memory Intro Generics Heap Memory and Allocators Everything we've seen so far has been constrained by requiring an upfront size. Arrays always have a compile-time known length (in fact, the length is part of the type). All of our strings have been string literals, which have a compile-time known length. Furthermore, the two types...

Learning Zig - Generics
8 Sept 2023 | original ↗

Heap Memory & Allocators Intro Coding In Zig Generics In the previous part we built a bare-boned dynamic array called IntList. The goal of the data structure was to store a dynamic number of values. Although the algorithm we used would work for any type of data, our implementation was tied to i64 values. Enter generics, the goal of which...

Learning Zig - Coding in Zig
8 Sept 2023 | original ↗

Generics Intro Conclusion Coding In Zig With much of the language now covered, we're going to wrap things up by revisiting a few topics and looking at a few more practical aspects of using Zig. In doing so, we're going to introduce more of the standard library and present less trivial code snippets. Dangling Pointers We begin by...

Learning Zig - Conclusion
8 Sept 2023 | original ↗

Coding In Zig Intro Conclusion Some readers might recognize me as the author of various "The Little $TECH Book" and wonder why this isn't called "The Little Zig Book". The truth is I'm not sure Zig fits in a "The Little" format. Part of the challenge is that Zig's complexity and learning curve will vary greatly depending on your own...

↑ these items are from RSS. Visit the blog itself at https://www.openmymind.net/ to find other articles and to appreciate the author's digital home.