+8
-481
languages/ziglang/0.15/README.md
+8
-481
languages/ziglang/0.15/README.md
···
1
1
# zig 0.15
2
2
3
-
patterns from zig 0.15+ development. the 0.15 release was a [major breaking change](https://ziglang.org/download/0.15.1/release-notes.html) with a complete i/o overhaul.
4
-
5
-
## contents
6
-
7
-
- [build system](#build-system) - module imports, dependencies
8
-
- [memory](#memory) - allocators, arenas, lifecycle
9
-
- [error handling](#error-handling) - try, catch, labeled blocks
10
-
- [concurrency](#concurrency) - threads, pools, atomics, backoff
11
-
- [http server](#http-server) - new reader/writer interfaces
12
-
- [websockets](#websockets) - tls client pattern
13
-
- [json](#json) - parsing and formatting
14
-
- [comptime](#comptime) - type-returning functions, reflection
15
-
- [strings](#strings) - splitting, searching, encoding
16
-
17
-
---
18
-
19
-
## build system
20
-
21
-
### executable with dependencies
22
-
23
-
the 0.15 pattern uses `createModule` with an `imports` array instead of `addModule`:
24
-
25
-
```zig
26
-
const exe = b.addExecutable(.{
27
-
.name = "myapp",
28
-
.root_module = b.createModule(.{
29
-
.root_source_file = b.path("src/main.zig"),
30
-
.target = target,
31
-
.optimize = optimize,
32
-
.imports = &.{
33
-
.{ .name = "websocket", .module = websocket.module("websocket") },
34
-
.{ .name = "zqlite", .module = zqlite.module("zqlite") },
35
-
},
36
-
}),
37
-
});
38
-
```
3
+
[release notes](https://ziglang.org/download/0.15.1/release-notes.html) · [migration guide](https://sngeth.com/zig/systems-programming/breaking-changes/2025/10/24/zig-0-15-migration-roadblocks/)
39
4
40
-
see: [music-atmosphere-feed/build.zig](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/build.zig)
5
+
major breaking release with i/o overhaul ("writergate") and build system changes.
41
6
42
-
### library module
7
+
## notes
43
8
44
-
for libraries that expose a module to consumers:
45
-
46
-
```zig
47
-
const mod = b.addModule("zql", .{
48
-
.root_source_file = b.path("src/root.zig"),
49
-
.target = target,
50
-
.optimize = optimize,
51
-
});
52
-
53
-
const tests = b.addTest(.{ .root_module = mod });
54
-
```
55
-
56
-
see: [zql/build.zig](https://tangled.sh/@zzstoatzz.io/zql/tree/main/build.zig)
57
-
58
-
### build.zig.zon
59
-
60
-
dependency declaration format:
61
-
62
-
```zig
63
-
.{
64
-
.name = .my_project,
65
-
.version = "0.0.1",
66
-
.minimum_zig_version = "0.15.0",
67
-
.dependencies = .{
68
-
.websocket = .{
69
-
.url = "https://github.com/karlseguin/websocket.zig/archive/refs/heads/master.tar.gz",
70
-
.hash = "...",
71
-
},
72
-
},
73
-
}
74
-
```
75
-
76
-
---
77
-
78
-
## memory
79
-
80
-
### general purpose allocator
81
-
82
-
use for application-lifetime allocations. check for leaks on deinit:
83
-
84
-
```zig
85
-
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
86
-
defer _ = gpa.deinit();
87
-
const allocator = gpa.allocator();
88
-
```
89
-
90
-
see: [music-atmosphere-feed/src/main.zig:14-16](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/main.zig#L14)
91
-
92
-
### arena allocator
93
-
94
-
use for request-scoped memory where everything can be freed at once:
95
-
96
-
```zig
97
-
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
98
-
defer arena.deinit();
99
-
const alloc = arena.allocator();
100
-
// all allocations freed when arena deinits
101
-
```
102
-
103
-
this pattern appears throughout http handlers where we need temporary allocations for building responses:
104
-
105
-
see: [leaflet-search/backend/src/server.zig:65-67](https://tangled.sh/@zzstoatzz.io/leaflet-search/tree/main/backend/src/server.zig#L65)
106
-
107
-
### arraylist
108
-
109
-
0.15 changed arraylist to require allocator on each method call:
110
-
111
-
```zig
112
-
var buf: std.ArrayList(u8) = .{};
113
-
defer buf.deinit(alloc);
114
-
const w = buf.writer(alloc);
115
-
try w.writeAll("data");
116
-
```
117
-
118
-
see: [leaflet-search/backend/src/server.zig:129-132](https://tangled.sh/@zzstoatzz.io/leaflet-search/tree/main/backend/src/server.zig#L129)
119
-
120
-
---
121
-
122
-
## error handling
123
-
124
-
### try (propagation)
125
-
126
-
most common pattern - propagate errors to caller:
127
-
128
-
```zig
129
-
const result = try doSomething();
130
-
```
131
-
132
-
### catch with specific handling
133
-
134
-
handle specific errors differently:
135
-
136
-
```zig
137
-
const n = tls_client.reader.stream(&w, .limited(len)) catch |err| {
138
-
if (err == error.EndOfStream) break :outer;
139
-
return error.Failed;
140
-
};
141
-
```
142
-
143
-
### catch with default
144
-
145
-
provide fallback value:
146
-
147
-
```zig
148
-
const port = std.fmt.parseInt(u16, port_str, 10) catch 3000;
149
-
const host = std.posix.getenv("HOST") orelse "localhost";
150
-
```
151
-
152
-
see: [music-atmosphere-feed/src/main.zig:37-40](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/main.zig#L37)
153
-
154
-
### labeled blocks for complex fallbacks
155
-
156
-
when you need multi-step fallback logic:
157
-
158
-
```zig
159
-
const port: u16 = blk: {
160
-
const port_str = posix.getenv("PORT") orelse "3000";
161
-
break :blk std.fmt.parseInt(u16, port_str, 10) catch 3000;
162
-
};
163
-
```
164
-
165
-
see: [music-atmosphere-feed/src/main.zig:37-40](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/main.zig#L37)
166
-
167
-
### defer / errdefer
168
-
169
-
cleanup resources on scope exit:
170
-
171
-
```zig
172
-
const resource = try allocate();
173
-
errdefer deallocate(resource); // only runs if error after this point
174
-
defer deallocate(resource); // always runs
175
-
```
176
-
177
-
---
178
-
179
-
## concurrency
180
-
181
-
### thread pool
182
-
183
-
for handling concurrent http connections:
184
-
185
-
```zig
186
-
var pool: Thread.Pool = undefined;
187
-
try pool.init(.{ .allocator = allocator, .n_jobs = 16 });
188
-
defer pool.deinit();
189
-
190
-
pool.spawn(handleConnection, .{conn}) catch |err| {
191
-
conn.stream.close();
192
-
};
193
-
```
194
-
195
-
see: [music-atmosphere-feed/src/main.zig:29-34](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/main.zig#L29)
196
-
197
-
### background thread
198
-
199
-
for long-running tasks like stream consumers:
200
-
201
-
```zig
202
-
const thread = try Thread.spawn(.{}, consumer, .{allocator});
203
-
defer thread.join();
204
-
```
205
-
206
-
see: [music-atmosphere-feed/src/main.zig:25-26](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/main.zig#L25)
207
-
208
-
### mutex
209
-
210
-
for protecting shared state across threads:
211
-
212
-
```zig
213
-
const BotState = struct {
214
-
mutex: Thread.Mutex = .{},
215
-
// ... other fields
216
-
};
217
-
218
-
state.mutex.lock();
219
-
defer state.mutex.unlock();
220
-
// critical section
221
-
```
222
-
223
-
see: [find-bufo/bot/src/main.zig:21](https://tangled.sh/@zzstoatzz.io/find-bufo/tree/main/bot/src/main.zig#L21), [find-bufo/bot/src/main.zig:102-103](https://tangled.sh/@zzstoatzz.io/find-bufo/tree/main/bot/src/main.zig#L102)
224
-
225
-
### atomic values
226
-
227
-
for lock-free counters and flags:
228
-
229
-
```zig
230
-
messages: std.atomic.Value(u64) = .init(0),
231
-
232
-
// increment
233
-
_ = self.messages.fetchAdd(1, .monotonic);
234
-
235
-
// read
236
-
const count = self.messages.load(.monotonic);
237
-
```
238
-
239
-
see: [music-atmosphere-feed/src/stats.zig:12](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/stats.zig#L12)
240
-
241
-
### exponential backoff
242
-
243
-
for reconnection loops:
244
-
245
-
```zig
246
-
var backoff: u64 = 1;
247
-
const max_backoff: u64 = 60;
248
-
249
-
while (true) {
250
-
connect() catch {};
251
-
posix.nanosleep(backoff, 0);
252
-
backoff = @min(backoff * 2, max_backoff);
253
-
}
254
-
```
255
-
256
-
see: [music-atmosphere-feed/src/jetstream.zig:22-31](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/jetstream.zig#L22)
257
-
258
-
---
259
-
260
-
## http server
261
-
262
-
0.15 completely rewrote i/o interfaces. the new pattern uses explicit buffers:
263
-
264
-
```zig
265
-
var read_buffer: [8192]u8 = undefined;
266
-
var write_buffer: [8192]u8 = undefined;
267
-
268
-
var reader = conn.stream.reader(&read_buffer);
269
-
var writer = conn.stream.writer(&write_buffer);
270
-
271
-
var server = http.Server.init(reader.interface(), &writer.interface);
272
-
273
-
while (true) {
274
-
var request = server.receiveHead() catch return;
275
-
handleRequest(&request) catch return;
276
-
if (!request.head.keep_alive) return;
277
-
}
278
-
```
279
-
280
-
see: [music-atmosphere-feed/src/http.zig:12-36](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/http.zig#L12)
281
-
282
-
### responding
283
-
284
-
```zig
285
-
try request.respond(body, .{
286
-
.status = .ok,
287
-
.extra_headers = &.{
288
-
.{ .name = "content-type", .value = "application/json" },
289
-
},
290
-
});
291
-
```
292
-
293
-
see: [leaflet-search/backend/src/server.zig:137-147](https://tangled.sh/@zzstoatzz.io/leaflet-search/tree/main/backend/src/server.zig#L137)
294
-
295
-
### socket timeouts
296
-
297
-
```zig
298
-
const timeout = std.mem.toBytes(posix.timeval{ .sec = 30, .usec = 0 });
299
-
try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout);
300
-
try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout);
301
-
```
302
-
303
-
see: [music-atmosphere-feed/src/main.zig:65-71](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/main.zig#L65)
304
-
305
-
---
306
-
307
-
## websockets
308
-
309
-
using [websocket.zig](https://github.com/karlseguin/websocket.zig) for tls websocket connections:
310
-
311
-
```zig
312
-
var client = websocket.Client.init(allocator, .{
313
-
.host = host,
314
-
.port = 443,
315
-
.tls = true,
316
-
.max_size = 1024 * 1024,
317
-
}) catch return err;
318
-
defer client.deinit();
319
-
320
-
client.handshake(path, .{ .headers = host_header }) catch return err;
321
-
322
-
var handler = Handler{ .allocator = allocator };
323
-
client.readLoop(&handler) catch return err;
324
-
```
325
-
326
-
the handler struct implements `serverMessage` and `close`:
327
-
328
-
```zig
329
-
const Handler = struct {
330
-
allocator: Allocator,
331
-
332
-
pub fn serverMessage(self: *Handler, data: []const u8) !void {
333
-
// process message
334
-
}
335
-
336
-
pub fn close(_: *Handler) void {
337
-
std.debug.print("connection closed\n", .{});
338
-
}
339
-
};
340
-
```
341
-
342
-
see: [music-atmosphere-feed/src/jetstream.zig:40-66](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/jetstream.zig#L40)
343
-
344
-
---
345
-
346
-
## json
347
-
348
-
### parsing
349
-
350
-
```zig
351
-
const parsed = json.parseFromSlice(json.Value, allocator, data, .{}) catch return;
352
-
defer parsed.deinit();
353
-
354
-
const root = parsed.value.object;
355
-
const val = root.get("key") orelse return;
356
-
if (val != .string) return;
357
-
const s = val.string;
358
-
```
359
-
360
-
see: [find-bufo/bot/src/jetstream.zig:95-98](https://tangled.sh/@zzstoatzz.io/find-bufo/tree/main/bot/src/jetstream.zig#L95)
361
-
362
-
### type checking pattern
363
-
364
-
common pattern for safely extracting nested json:
365
-
366
-
```zig
367
-
const kind = root.get("kind") orelse return error.NotAPost;
368
-
if (kind != .string or !mem.eql(u8, kind.string, "commit")) return error.NotAPost;
369
-
370
-
const commit = root.get("commit") orelse return error.NotAPost;
371
-
if (commit != .object) return error.NotAPost;
372
-
```
373
-
374
-
see: [music-atmosphere-feed/src/jetstream.zig:99-109](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/jetstream.zig#L99)
375
-
376
-
---
377
-
378
-
## comptime
379
-
380
-
### type-returning functions
381
-
382
-
create types at compile time based on input:
383
-
384
-
```zig
385
-
pub fn Query(comptime sql: []const u8) type {
386
-
comptime {
387
-
const parsed = parser.parse(sql);
388
-
return struct {
389
-
pub const raw = sql;
390
-
pub const params: []const []const u8 = parsed.params[0..parsed.params_len];
391
-
pub const columns: []const []const u8 = parsed.columns[0..parsed.columns_len];
392
-
393
-
pub fn validateArgs(comptime Args: type) void {
394
-
// compile-time validation
395
-
}
396
-
};
397
-
}
398
-
}
399
-
```
400
-
401
-
see: [zql/src/Query.zig:10-108](https://tangled.sh/@zzstoatzz.io/zql/tree/main/src/Query.zig#L10)
402
-
403
-
### inline for
404
-
405
-
loop unrolled at compile time:
406
-
407
-
```zig
408
-
inline for (params) |p| {
409
-
if (!hasField(fields, p)) {
410
-
@compileError("missing param :" ++ p);
411
-
}
412
-
}
413
-
```
414
-
415
-
see: [zql/src/Query.zig:23-27](https://tangled.sh/@zzstoatzz.io/zql/tree/main/src/Query.zig#L23)
416
-
417
-
### reflection
418
-
419
-
access struct fields at comptime:
420
-
421
-
```zig
422
-
const fields = @typeInfo(Args).@"struct".fields;
423
-
inline for (fields) |f| {
424
-
@field(result, f.name) = @field(args, f.name);
425
-
}
426
-
```
427
-
428
-
see: [zql/src/Query.zig:55-64](https://tangled.sh/@zzstoatzz.io/zql/tree/main/src/Query.zig#L55)
429
-
430
-
### branch quota
431
-
432
-
for complex comptime parsing, increase the default branch quota:
433
-
434
-
```zig
435
-
@setEvalBranchQuota(sql.len * 100);
436
-
```
437
-
438
-
see: [zql/src/parse.zig:48](https://tangled.sh/@zzstoatzz.io/zql/tree/main/src/parse.zig#L48)
439
-
440
-
---
441
-
442
-
## strings
443
-
444
-
### splitting
445
-
446
-
```zig
447
-
var parts = mem.splitScalar(u8, data, '/');
448
-
const first = parts.next() orelse return error.Invalid;
449
-
```
450
-
451
-
### prefix/suffix checks
452
-
453
-
```zig
454
-
if (mem.startsWith(u8, target, "/api/")) { }
455
-
if (mem.endsWith(u8, id, ".gif")) { }
456
-
```
457
-
458
-
see: [find-bufo/bot/src/main.zig:142](https://tangled.sh/@zzstoatzz.io/find-bufo/tree/main/bot/src/main.zig#L142)
459
-
460
-
### searching
461
-
462
-
```zig
463
-
if (mem.indexOf(u8, response, "\r\n\r\n")) |idx| {
464
-
const body = response[idx + 4 ..];
465
-
}
466
-
```
467
-
468
-
### url decoding
469
-
470
-
```zig
471
-
const buf = try alloc.dupe(u8, encoded);
472
-
const decoded = std.Uri.percentDecodeInPlace(buf);
473
-
```
474
-
475
-
see: [leaflet-search/backend/src/server.zig:115-116](https://tangled.sh/@zzstoatzz.io/leaflet-search/tree/main/backend/src/server.zig#L115)
476
-
477
-
### multi-line strings
478
-
479
-
```zig
480
-
const sql =
481
-
\\CREATE TABLE IF NOT EXISTS posts (
482
-
\\ uri TEXT PRIMARY KEY,
483
-
\\ cid TEXT NOT NULL
484
-
\\)
485
-
;
486
-
```
9
+
- [arraylist](./arraylist.md) - now requires allocator on every method call
10
+
- [io](./io.md) - new reader/writer interfaces with explicit buffers
11
+
- [build](./build.md) - module imports pattern with `createModule`
12
+
- [comptime](./comptime.md) - type-returning functions, inline for, reflection
13
+
- [concurrency](./concurrency.md) - thread pools, mutexes, atomics, backoff
+64
languages/ziglang/0.15/arraylist.md
+64
languages/ziglang/0.15/arraylist.md
···
1
+
# arraylist
2
+
3
+
in 0.15, `std.ArrayList` became "unmanaged" by default - it no longer stores the allocator internally. you pass the allocator to every method call.
4
+
5
+
## why this changed
6
+
7
+
the [release notes](https://ziglang.org/download/0.15.1/release-notes.html) explain the reasoning: "having an extra field is more complicated than not having an extra field, so not having it is the null hypothesis."
8
+
9
+
storing the allocator had downsides:
10
+
- worse method signatures when dealing with capacity reservations
11
+
- can't statically initialize (no allocator available at comptime)
12
+
- extra memory cost, especially for nested containers (arraylist of arraylists)
13
+
14
+
the supposed upside - "avoiding accidentally using the wrong allocator" - isn't worth it since the correct allocator is always nearby and misuse can be safety-checked.
15
+
16
+
## the pattern
17
+
18
+
```zig
19
+
var buf: std.ArrayList(u8) = .{};
20
+
defer buf.deinit(alloc);
21
+
22
+
try buf.append(alloc, 'x');
23
+
try buf.appendSlice(alloc, "hello");
24
+
25
+
const w = buf.writer(alloc);
26
+
try w.print("{d}", .{42});
27
+
```
28
+
29
+
every mutating method takes allocator as the first argument. this is the part i keep forgetting - `append(alloc, item)` not `append(item)`.
30
+
31
+
## initialization
32
+
33
+
you can now statically initialize because there's no allocator field:
34
+
35
+
```zig
36
+
var buf: std.ArrayList(u8) = .{}; // zero-init, no allocator needed yet
37
+
```
38
+
39
+
vs the old managed pattern which required an allocator upfront.
40
+
41
+
## writer
42
+
43
+
the writer also needs the allocator:
44
+
45
+
```zig
46
+
const w = buf.writer(alloc);
47
+
try w.writeAll("data");
48
+
try w.print("{s}: {d}", .{ name, value });
49
+
```
50
+
51
+
see: [leaflet-search/backend/src/server.zig#L129](https://tangled.sh/@zzstoatzz.io/leaflet-search/tree/main/backend/src/server.zig#L129)
52
+
53
+
## managed still exists
54
+
55
+
if you really want the old behavior:
56
+
57
+
```zig
58
+
var buf = std.array_list.Managed(u8).init(alloc);
59
+
defer buf.deinit();
60
+
61
+
try buf.append('x'); // no allocator argument
62
+
```
63
+
64
+
but this is marked for eventual removal. get used to passing the allocator.
+95
languages/ziglang/0.15/build.md
+95
languages/ziglang/0.15/build.md
···
1
+
# build system
2
+
3
+
0.15 changed how you declare module dependencies in build.zig. the new pattern uses `createModule` with an `imports` array.
4
+
5
+
## executable with dependencies
6
+
7
+
```zig
8
+
const websocket = b.dependency("websocket", .{
9
+
.target = target,
10
+
.optimize = optimize,
11
+
});
12
+
13
+
const exe = b.addExecutable(.{
14
+
.name = "myapp",
15
+
.root_module = b.createModule(.{
16
+
.root_source_file = b.path("src/main.zig"),
17
+
.target = target,
18
+
.optimize = optimize,
19
+
.imports = &.{
20
+
.{ .name = "websocket", .module = websocket.module("websocket") },
21
+
},
22
+
}),
23
+
});
24
+
```
25
+
26
+
the key difference from pre-0.15:
27
+
- use `root_module` with `createModule()` instead of just `root_source_file`
28
+
- dependencies go in the `imports` array, not via `addModule()` calls
29
+
30
+
see: [music-atmosphere-feed/build.zig](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/build.zig)
31
+
32
+
## library module
33
+
34
+
for a library that exposes a module to consumers:
35
+
36
+
```zig
37
+
const mod = b.addModule("zql", .{
38
+
.root_source_file = b.path("src/root.zig"),
39
+
.target = target,
40
+
.optimize = optimize,
41
+
});
42
+
43
+
const tests = b.addTest(.{ .root_module = mod });
44
+
```
45
+
46
+
the module name ("zql") is what consumers use in their `imports` array.
47
+
48
+
see: [zql/build.zig](https://tangled.sh/@zzstoatzz.io/zql/tree/main/build.zig)
49
+
50
+
## build.zig.zon
51
+
52
+
dependencies are declared in build.zig.zon:
53
+
54
+
```zig
55
+
.{
56
+
.name = .my_project,
57
+
.version = "0.0.1",
58
+
.minimum_zig_version = "0.15.0",
59
+
.dependencies = .{
60
+
.websocket = .{
61
+
.url = "https://github.com/karlseguin/websocket.zig/archive/refs/heads/master.tar.gz",
62
+
.hash = "...",
63
+
},
64
+
},
65
+
}
66
+
```
67
+
68
+
to get the hash, run `zig build` with a wrong hash - it'll tell you the correct one.
69
+
70
+
## linking system libraries
71
+
72
+
for sqlite, link both libc and the system library:
73
+
74
+
```zig
75
+
exe.linkLibC();
76
+
exe.linkSystemLibrary("sqlite3");
77
+
```
78
+
79
+
see: [music-atmosphere-feed/build.zig#L30-31](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/build.zig#L30)
80
+
81
+
## run step
82
+
83
+
```zig
84
+
const run_cmd = b.addRunArtifact(exe);
85
+
run_cmd.step.dependOn(b.getInstallStep());
86
+
87
+
if (b.args) |args| {
88
+
run_cmd.addArgs(args);
89
+
}
90
+
91
+
const run_step = b.step("run", "Run the server");
92
+
run_step.dependOn(&run_cmd.step);
93
+
```
94
+
95
+
this lets you do `zig build run -- --port 8080`.
+109
languages/ziglang/0.15/comptime.md
+109
languages/ziglang/0.15/comptime.md
···
1
+
# comptime
2
+
3
+
zig's comptime is where it gets interesting. you can generate types, validate inputs, and unroll loops at compile time.
4
+
5
+
## type-returning functions
6
+
7
+
the pattern: a function that takes comptime parameters and returns a `type`. the returned type is a struct with fields and methods derived from the input.
8
+
9
+
```zig
10
+
pub fn Query(comptime sql: []const u8) type {
11
+
comptime {
12
+
const parsed = parser.parse(sql);
13
+
return struct {
14
+
pub const raw = sql;
15
+
pub const params: []const []const u8 = parsed.params[0..parsed.params_len];
16
+
pub const columns: []const []const u8 = parsed.columns[0..parsed.columns_len];
17
+
18
+
pub fn bind(args: anytype) BindTuple(@TypeOf(args)) {
19
+
// ...
20
+
}
21
+
};
22
+
}
23
+
}
24
+
```
25
+
26
+
usage:
27
+
28
+
```zig
29
+
const Q = Query("SELECT id, name FROM users WHERE age > :min_age");
30
+
// Q.params = ["min_age"]
31
+
// Q.columns = ["id", "name"]
32
+
```
33
+
34
+
the entire SQL is parsed at compile time. no runtime overhead, and typos in parameter names are compile errors.
35
+
36
+
see: [zql/src/Query.zig](https://tangled.sh/@zzstoatzz.io/zql/tree/main/src/Query.zig)
37
+
38
+
## inline for
39
+
40
+
`inline for` unrolls the loop at compile time. each iteration becomes separate code:
41
+
42
+
```zig
43
+
inline for (params) |p| {
44
+
if (!hasField(fields, p)) {
45
+
@compileError("missing param :" ++ p);
46
+
}
47
+
}
48
+
```
49
+
50
+
this checks at compile time that your args struct has all required SQL parameters. missing one? compile error with the exact parameter name.
51
+
52
+
see: [zql/src/Query.zig#L23](https://tangled.sh/@zzstoatzz.io/zql/tree/main/src/Query.zig#L23)
53
+
54
+
## reflection
55
+
56
+
access struct fields at comptime with `@typeInfo`:
57
+
58
+
```zig
59
+
const fields = @typeInfo(Args).@"struct".fields;
60
+
inline for (fields) |f| {
61
+
@field(result, f.name) = @field(args, f.name);
62
+
}
63
+
```
64
+
65
+
note the `@"struct"` syntax - struct is a keyword, so it needs quoting.
66
+
67
+
this pattern maps an arbitrary struct's fields to another structure, checking types and names at compile time.
68
+
69
+
see: [zql/src/Query.zig#L55](https://tangled.sh/@zzstoatzz.io/zql/tree/main/src/Query.zig#L55)
70
+
71
+
## compile-time validation
72
+
73
+
`@compileError` stops compilation with a message:
74
+
75
+
```zig
76
+
if (!hasColumn(f.name)) {
77
+
@compileError("struct field '" ++ f.name ++ "' not found in query columns");
78
+
}
79
+
```
80
+
81
+
this is how you make invalid states unrepresentable - if your code compiles, your query parameters match your struct fields.
82
+
83
+
## branch quota
84
+
85
+
complex comptime parsing can hit the default branch quota (1000 backwards branches). increase it:
86
+
87
+
```zig
88
+
@setEvalBranchQuota(sql.len * 100);
89
+
```
90
+
91
+
put this at the start of your comptime block. without it, complex SQL parsing fails with "evaluation exceeded maximum branch quota."
92
+
93
+
see: [zql/src/parse.zig#L48](https://tangled.sh/@zzstoatzz.io/zql/tree/main/src/parse.zig#L48)
94
+
95
+
## why this matters
96
+
97
+
the zql library parses SQL at compile time and generates type-safe bindings:
98
+
99
+
```zig
100
+
const Q = Query("INSERT INTO users (name, age) VALUES (:name, :age)");
101
+
102
+
// this is checked at compile time:
103
+
const args = Q.bind(.{ .name = "alice", .age = 25 });
104
+
// args is a tuple: ("alice", 25) in parameter order
105
+
106
+
// if you typo .nme instead of .name, compile error
107
+
```
108
+
109
+
no runtime SQL parsing, no string concatenation, no injection vulnerabilities (because the SQL is a comptime string literal).
+107
languages/ziglang/0.15/concurrency.md
+107
languages/ziglang/0.15/concurrency.md
···
1
+
# concurrency
2
+
3
+
zig's concurrency primitives are in `std.Thread`. no async/await - just threads, mutexes, and atomics.
4
+
5
+
## thread pool
6
+
7
+
for handling concurrent connections without spawning unbounded threads:
8
+
9
+
```zig
10
+
var pool: Thread.Pool = undefined;
11
+
try pool.init(.{
12
+
.allocator = allocator,
13
+
.n_jobs = 16,
14
+
});
15
+
defer pool.deinit();
16
+
17
+
// in accept loop:
18
+
pool.spawn(handleConnection, .{conn}) catch |err| {
19
+
conn.stream.close(); // cleanup on spawn failure
20
+
};
21
+
```
22
+
23
+
the pool maintains a fixed number of worker threads. `spawn` queues work; if the pool is busy, it blocks or fails depending on configuration.
24
+
25
+
see: [music-atmosphere-feed/src/main.zig#L29](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/main.zig#L29)
26
+
27
+
## background threads
28
+
29
+
for long-running tasks like stream consumers:
30
+
31
+
```zig
32
+
const thread = try Thread.spawn(.{}, consumer, .{allocator});
33
+
defer thread.join(); // wait for completion on shutdown
34
+
```
35
+
36
+
the third argument is a tuple of arguments passed to the function. `join()` blocks until the thread exits.
37
+
38
+
see: [music-atmosphere-feed/src/main.zig#L25](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/main.zig#L25)
39
+
40
+
## mutex
41
+
42
+
for protecting shared state:
43
+
44
+
```zig
45
+
const State = struct {
46
+
mutex: Thread.Mutex = .{},
47
+
data: SomeData,
48
+
};
49
+
50
+
state.mutex.lock();
51
+
defer state.mutex.unlock();
52
+
// critical section - only one thread at a time
53
+
```
54
+
55
+
always use `defer` for unlock. if you return early or error, the mutex still unlocks.
56
+
57
+
see: [find-bufo/bot/src/main.zig#L102](https://tangled.sh/@zzstoatzz.io/find-bufo/tree/main/bot/src/main.zig#L102)
58
+
59
+
## atomics
60
+
61
+
for lock-free counters and flags:
62
+
63
+
```zig
64
+
const Stats = struct {
65
+
messages: std.atomic.Value(u64) = .init(0),
66
+
};
67
+
68
+
// increment (returns previous value)
69
+
_ = self.messages.fetchAdd(1, .monotonic);
70
+
71
+
// read
72
+
const count = self.messages.load(.monotonic);
73
+
74
+
// write
75
+
self.messages.store(42, .monotonic);
76
+
```
77
+
78
+
`.monotonic` is the memory ordering - sufficient for simple counters. use stricter orderings (`.acquire`, `.release`, `.seq_cst`) when you need synchronization guarantees between threads.
79
+
80
+
see: [music-atmosphere-feed/src/stats.zig#L12](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/stats.zig#L12)
81
+
82
+
## exponential backoff
83
+
84
+
for reconnection loops that don't hammer servers:
85
+
86
+
```zig
87
+
var backoff: u64 = 1;
88
+
const max_backoff: u64 = 60;
89
+
90
+
while (true) {
91
+
connect() catch |err| {
92
+
std.debug.print("error: {}, retry in {}s\n", .{ err, backoff });
93
+
};
94
+
posix.nanosleep(backoff, 0);
95
+
backoff = @min(backoff * 2, max_backoff);
96
+
}
97
+
```
98
+
99
+
starts at 1 second, doubles each failure, caps at 60 seconds. simple and effective.
100
+
101
+
see: [music-atmosphere-feed/src/jetstream.zig#L22](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/jetstream.zig#L22)
102
+
103
+
## no async
104
+
105
+
zig removed async/await. the reasoning: it added complexity without clear benefits over threads for most use cases. if you need high concurrency, use a thread pool or io_uring (linux) / kqueue (macos).
106
+
107
+
for bluesky bots and feed generators, a thread pool with 16 workers handles thousands of concurrent connections fine.
+105
languages/ziglang/0.15/io.md
+105
languages/ziglang/0.15/io.md
···
1
+
# i/o interfaces
2
+
3
+
0.15 completely rewrote `std.io.Reader` and `std.io.Writer`. this was called "writergate" (referencing the earlier "allocgate" breaking change). the [release notes](https://ziglang.org/download/0.15.1/release-notes.html) acknowledge it as "extremely breaking" but necessary.
4
+
5
+
## what changed
6
+
7
+
the old interfaces were generic (`anytype`), which forced every function that touched a reader/writer to also be generic. this "poisoned" APIs throughout codebases.
8
+
9
+
the new interfaces are concrete types with explicit buffers. the buffer is part of the interface, not the implementation.
10
+
11
+
## the pattern
12
+
13
+
you now create explicit buffers and pass them to readers/writers:
14
+
15
+
```zig
16
+
var read_buffer: [8192]u8 = undefined;
17
+
var write_buffer: [8192]u8 = undefined;
18
+
19
+
var reader = conn.stream.reader(&read_buffer);
20
+
var writer = conn.stream.writer(&write_buffer);
21
+
22
+
var server = http.Server.init(reader.interface(), &writer.interface);
23
+
```
24
+
25
+
notice:
26
+
- buffers are stack-allocated arrays you own
27
+
- `.interface()` extracts the concrete interface type
28
+
- http.Server no longer depends on `std.net` - it just takes interfaces
29
+
30
+
see: [music-atmosphere-feed/src/http.zig#L14-21](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/src/http.zig#L14)
31
+
32
+
## why this matters
33
+
34
+
before 0.15, if you wrote a function that accepted a writer, you'd write:
35
+
36
+
```zig
37
+
fn writeStuff(writer: anytype) !void { ... }
38
+
```
39
+
40
+
now you write:
41
+
42
+
```zig
43
+
fn writeStuff(writer: *std.Io.Writer) !void { ... }
44
+
```
45
+
46
+
concrete types mean better error messages, actual type checking, and no generic explosion through your codebase.
47
+
48
+
## flushing
49
+
50
+
with buffered i/o, you need to flush explicitly. the buffer accumulates writes and only sends when full or flushed:
51
+
52
+
```zig
53
+
try writer.print("hello", .{});
54
+
try writer.flush(); // actually sends
55
+
```
56
+
57
+
forgetting to flush is a common bug - data sits in the buffer forever.
58
+
59
+
## allocating writer
60
+
61
+
for dynamic output (like building json responses), use an allocating writer:
62
+
63
+
```zig
64
+
var aw: std.Io.Writer.Allocating = .init(allocator);
65
+
defer aw.deinit();
66
+
67
+
const result = client.fetch(.{
68
+
.response_writer = &aw.writer,
69
+
...
70
+
});
71
+
72
+
const response = aw.toArrayList().items;
73
+
```
74
+
75
+
see: [find-bufo/bot/src/main.zig#L202-220](https://tangled.sh/@zzstoatzz.io/find-bufo/tree/main/bot/src/main.zig#L202)
76
+
77
+
## fixed writer
78
+
79
+
for writing into a fixed-size buffer:
80
+
81
+
```zig
82
+
var buf: [256]u8 = undefined;
83
+
var w: std.Io.Writer = .fixed(&buf);
84
+
try w.print("{s}: {d}", .{ name, value });
85
+
```
86
+
87
+
## tls reading quirk
88
+
89
+
when reading from tls streams, you must loop until data arrives. a return of 0 doesn't mean EOF - it means "try again":
90
+
91
+
```zig
92
+
outer: while (total_read < response_buf.len) {
93
+
var w: std.Io.Writer = .fixed(response_buf[total_read..]);
94
+
while (true) {
95
+
const n = tls_client.reader.stream(&w, .limited(remaining)) catch break :outer;
96
+
if (n != 0) {
97
+
total_read += n;
98
+
break;
99
+
}
100
+
// n == 0 means keep trying, not EOF
101
+
}
102
+
}
103
+
```
104
+
105
+
this pattern comes from [websocket.zig](https://github.com/karlseguin/websocket.zig). without the inner loop, you'll read 0 bytes and think you're done.