+413
languages/ziglang/README.md
+413
languages/ziglang/README.md
···
1
+
# zig 0.15+
2
+
3
+
notes on zig 0.15+ patterns. pre-0.15 patterns are not covered here.
4
+
5
+
sources: leaflet-search, find-bufo, zql, pollz (dec 2025 - jan 2026)
6
+
7
+
---
8
+
9
+
## build system
10
+
11
+
### module imports (0.15+ pattern)
12
+
13
+
```zig
14
+
const exe = b.addExecutable(.{
15
+
.name = "myapp",
16
+
.root_module = b.createModule(.{
17
+
.root_source_file = b.path("src/main.zig"),
18
+
.imports = &.{
19
+
.{ .name = "websocket", .module = websocket.module("websocket") },
20
+
.{ .name = "zqlite", .module = zqlite.module("zqlite") },
21
+
},
22
+
}),
23
+
});
24
+
```
25
+
26
+
### build.zig.zon
27
+
28
+
```zig
29
+
.{
30
+
.name = .my_project,
31
+
.version = "0.0.1",
32
+
.minimum_zig_version = "0.15.0",
33
+
.dependencies = .{
34
+
.websocket = .{
35
+
.url = "https://github.com/karlseguin/websocket.zig/archive/refs/heads/master.tar.gz",
36
+
.hash = "...",
37
+
},
38
+
},
39
+
}
40
+
```
41
+
42
+
### library modules
43
+
44
+
```zig
45
+
const mod = b.addModule("zql", .{
46
+
.root_source_file = b.path("src/root.zig"),
47
+
.target = target,
48
+
.optimize = optimize,
49
+
});
50
+
51
+
const tests = b.addTest(.{ .root_module = mod });
52
+
```
53
+
54
+
---
55
+
56
+
## memory management
57
+
58
+
### general purpose allocator (application lifetime)
59
+
60
+
```zig
61
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
62
+
defer _ = gpa.deinit();
63
+
const allocator = gpa.allocator();
64
+
```
65
+
66
+
### arena allocator (request-scoped)
67
+
68
+
```zig
69
+
fn handleRequest(request: *http.Server.Request) !void {
70
+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
71
+
defer arena.deinit();
72
+
const alloc = arena.allocator();
73
+
// all allocations freed when arena deinits
74
+
}
75
+
```
76
+
77
+
### arraylist lifecycle
78
+
79
+
```zig
80
+
var list: std.ArrayList(u8) = .{};
81
+
defer list.deinit(allocator);
82
+
try list.appendSlice(allocator, data);
83
+
```
84
+
85
+
---
86
+
87
+
## error handling
88
+
89
+
### try with propagation
90
+
91
+
```zig
92
+
const result = try doSomething(); // propagates error
93
+
```
94
+
95
+
### catch with specific error handling
96
+
97
+
```zig
98
+
const n = tls_client.reader.stream(&w, .limited(len)) catch |err| {
99
+
if (err == error.EndOfStream) break :outer;
100
+
return error.Failed;
101
+
};
102
+
```
103
+
104
+
### catch with default
105
+
106
+
```zig
107
+
const port = std.fmt.parseInt(u16, port_str, 10) catch 443;
108
+
```
109
+
110
+
### labeled blocks for fallback values
111
+
112
+
```zig
113
+
const count: i64 = blk: {
114
+
const row = db.conn.row("SELECT COUNT(*) FROM x", .{}) catch break :blk 0;
115
+
if (row) |r| {
116
+
defer r.deinit();
117
+
break :blk r.int(0);
118
+
}
119
+
break :blk 0;
120
+
};
121
+
```
122
+
123
+
---
124
+
125
+
## concurrency
126
+
127
+
### thread pool
128
+
129
+
```zig
130
+
var pool: Thread.Pool = undefined;
131
+
try pool.init(.{ .allocator = allocator, .n_jobs = 16 });
132
+
defer pool.deinit();
133
+
134
+
pool.spawn(handleConnection, .{conn}) catch |err| {
135
+
conn.stream.close();
136
+
};
137
+
```
138
+
139
+
### mutex for shared state
140
+
141
+
```zig
142
+
pub var mutex: Thread.Mutex = .{};
143
+
144
+
pub fn insert(data: []const u8) !void {
145
+
mutex.lock();
146
+
defer mutex.unlock();
147
+
// critical section
148
+
}
149
+
```
150
+
151
+
### atomic values
152
+
153
+
```zig
154
+
posts_checked: std.atomic.Value(u64) = .init(0),
155
+
156
+
// increment
157
+
_ = self.posts_checked.fetchAdd(1, .monotonic);
158
+
159
+
// read
160
+
const count = self.posts_checked.load(.monotonic);
161
+
```
162
+
163
+
### background thread
164
+
165
+
```zig
166
+
const thread = try Thread.spawn(.{}, consumer, .{allocator});
167
+
```
168
+
169
+
### exponential backoff
170
+
171
+
```zig
172
+
var backoff: u64 = 1;
173
+
const max_backoff: u64 = 60;
174
+
175
+
while (true) {
176
+
connect() catch {};
177
+
posix.nanosleep(backoff, 0);
178
+
backoff = @min(backoff * 2, max_backoff);
179
+
}
180
+
```
181
+
182
+
---
183
+
184
+
## networking
185
+
186
+
### tcp + tls (0.15 pattern)
187
+
188
+
```zig
189
+
const stream = net.tcpConnectToHost(allocator, host, 443) catch return error.Failed;
190
+
defer stream.close();
191
+
192
+
var arena = std.heap.ArenaAllocator.init(allocator);
193
+
defer arena.deinit();
194
+
const aa = arena.allocator();
195
+
196
+
var ca_bundle: std.crypto.Certificate.Bundle = .{};
197
+
ca_bundle.rescan(aa) catch return error.Failed;
198
+
199
+
const buf_len = std.crypto.tls.max_ciphertext_record_len;
200
+
const buf = aa.alloc(u8, buf_len * 4) catch return error.Failed;
201
+
202
+
var stream_writer = stream.writer(buf.ptr[0..buf_len][0..buf_len]);
203
+
var stream_reader = stream.reader(buf.ptr[buf_len .. 2 * buf_len][0..buf_len]);
204
+
205
+
var tls_client = tls.Client.init(
206
+
stream_reader.interface(),
207
+
&stream_writer.interface,
208
+
.{
209
+
.ca = .{ .bundle = ca_bundle },
210
+
.host = .{ .explicit = host },
211
+
.read_buffer = buf.ptr[2 * buf_len .. 3 * buf_len][0..buf_len],
212
+
.write_buffer = buf.ptr[3 * buf_len .. 4 * buf_len][0..buf_len],
213
+
},
214
+
) catch return error.Failed;
215
+
```
216
+
217
+
### tls read loop (must loop until data)
218
+
219
+
```zig
220
+
outer: while (total_read < response_buf.len) {
221
+
var w: std.Io.Writer = .fixed(response_buf[total_read..]);
222
+
while (true) {
223
+
const n = tls_client.reader.stream(&w, .limited(remaining)) catch break :outer;
224
+
if (n != 0) {
225
+
total_read += n;
226
+
break;
227
+
}
228
+
}
229
+
}
230
+
```
231
+
232
+
### http server
233
+
234
+
```zig
235
+
var read_buffer: [8192]u8 = undefined;
236
+
var write_buffer: [8192]u8 = undefined;
237
+
238
+
var reader = conn.stream.reader(&read_buffer);
239
+
var writer = conn.stream.writer(&write_buffer);
240
+
241
+
var server = http.Server.init(reader.interface(), &writer.interface);
242
+
243
+
while (true) {
244
+
var request = server.receiveHead() catch return;
245
+
handleRequest(&request) catch return;
246
+
if (!request.head.keep_alive) return;
247
+
}
248
+
```
249
+
250
+
### socket timeouts
251
+
252
+
```zig
253
+
const timeout = std.mem.toBytes(posix.timeval{ .sec = 30, .usec = 0 });
254
+
try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout);
255
+
try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout);
256
+
```
257
+
258
+
---
259
+
260
+
## json
261
+
262
+
### parsing
263
+
264
+
```zig
265
+
const parsed = json.parseFromSlice(json.Value, allocator, data, .{}) catch return;
266
+
defer parsed.deinit();
267
+
268
+
const root = parsed.value.object;
269
+
const val = root.get("key") orelse return;
270
+
if (val != .string) return;
271
+
const s = val.string;
272
+
```
273
+
274
+
### formatting output
275
+
276
+
```zig
277
+
try writer.print("{f}", .{json.fmt(value, .{})});
278
+
```
279
+
280
+
---
281
+
282
+
## comptime
283
+
284
+
### type-returning functions
285
+
286
+
```zig
287
+
pub fn Query(comptime sql: []const u8) type {
288
+
comptime {
289
+
const parsed = parser.parse(sql);
290
+
return struct {
291
+
pub const raw = sql;
292
+
pub const positional = parsed.positional[0..parsed.positional_len];
293
+
};
294
+
}
295
+
}
296
+
```
297
+
298
+
### inline for (unrolled at comptime)
299
+
300
+
```zig
301
+
inline for (params) |p| {
302
+
if (!hasField(fields, p)) {
303
+
@compileError("missing param :" ++ p);
304
+
}
305
+
}
306
+
```
307
+
308
+
### reflection
309
+
310
+
```zig
311
+
const fields = @typeInfo(Args).@"struct".fields;
312
+
inline for (fields) |f| {
313
+
@field(result, f.name) = @field(args, f.name);
314
+
}
315
+
```
316
+
317
+
### compile-time validation
318
+
319
+
```zig
320
+
@compileError("missing param :" ++ p ++ " in args struct")
321
+
```
322
+
323
+
### branch quota for complex parsing
324
+
325
+
```zig
326
+
@setEvalBranchQuota(sql.len * 100);
327
+
```
328
+
329
+
---
330
+
331
+
## strings
332
+
333
+
### splitting
334
+
335
+
```zig
336
+
var parts = mem.splitScalar(u8, data, '/');
337
+
const first = parts.next() orelse return error.Invalid;
338
+
```
339
+
340
+
### prefix/suffix checks
341
+
342
+
```zig
343
+
if (mem.startsWith(u8, target, "/api/")) { }
344
+
if (mem.endsWith(u8, id, "#atproto")) { }
345
+
```
346
+
347
+
### searching
348
+
349
+
```zig
350
+
if (mem.indexOf(u8, response, "\r\n\r\n")) |idx| {
351
+
const body = response[idx + 4 ..];
352
+
}
353
+
```
354
+
355
+
### url decoding
356
+
357
+
```zig
358
+
const buf = try alloc.dupe(u8, encoded);
359
+
const decoded = std.Uri.percentDecodeInPlace(buf);
360
+
```
361
+
362
+
---
363
+
364
+
## common dependencies
365
+
366
+
- **websocket.zig** (karlseguin) - websocket client with tls
367
+
- **zqlite.zig** (karlseguin) - sqlite wrapper
368
+
369
+
---
370
+
371
+
## idioms
372
+
373
+
### defer for cleanup
374
+
375
+
```zig
376
+
var arena = std.heap.ArenaAllocator.init(allocator);
377
+
defer arena.deinit();
378
+
```
379
+
380
+
### errdefer for error cleanup
381
+
382
+
```zig
383
+
const resource = try allocate();
384
+
errdefer deallocate(resource);
385
+
// if error after this, resource is cleaned up
386
+
```
387
+
388
+
### optional access with type check
389
+
390
+
```zig
391
+
if (obj.get("key")) |val| {
392
+
if (val == .string) {
393
+
use(val.string);
394
+
}
395
+
}
396
+
```
397
+
398
+
### environment variables with defaults
399
+
400
+
```zig
401
+
const host = std.posix.getenv("HOST") orelse "localhost";
402
+
```
403
+
404
+
### multi-line strings
405
+
406
+
```zig
407
+
const sql =
408
+
\\CREATE TABLE IF NOT EXISTS polls (
409
+
\\ uri TEXT PRIMARY KEY,
410
+
\\ text TEXT NOT NULL
411
+
\\)
412
+
;
413
+
```