about things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

restructure zig notes with 0.15/ directory and links

- ziglang/README.md: summary with project table and dependency links
- ziglang/0.15/README.md: patterns with specific file/line references

all links verified against source files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

zzstoatzz d2c7c7ad 9ac9a230

+501 -405
+486
languages/ziglang/0.15/README.md
··· 1 + # zig 0.15 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 + ``` 39 + 40 + see: [music-atmosphere-feed/build.zig](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed/tree/main/build.zig) 41 + 42 + ### library module 43 + 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 + ```
+15 -405
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 - ``` 1 + # zig 316 2 317 - ### compile-time validation 3 + notes on [zig](https://ziglang.org/) patterns from building atproto/bluesky infrastructure. 318 4 319 - ```zig 320 - @compileError("missing param :" ++ p ++ " in args struct") 321 - ``` 5 + ## versions 322 6 323 - ### branch quota for complex parsing 7 + - [0.15](./0.15/) - major i/o overhaul, new build system patterns ([release notes](https://ziglang.org/download/0.15.1/release-notes.html)) 324 8 325 - ```zig 326 - @setEvalBranchQuota(sql.len * 100); 327 - ``` 9 + ## projects 328 10 329 - --- 11 + these notes are derived from: 330 12 331 - ## strings 13 + | project | description | 14 + |---------|-------------| 15 + | [music-atmosphere-feed](https://tangled.sh/@zzstoatzz.io/music-atmosphere-feed) | bluesky feed generator - http server, jetstream consumer, jwt verification | 16 + | [find-bufo](https://tangled.sh/@zzstoatzz.io/find-bufo) | bluesky bot - websocket client, bluesky api, image posting | 17 + | [leaflet-search](https://tangled.sh/@zzstoatzz.io/leaflet-search) | fts search backend - sqlite fts5, http server, dashboard | 18 + | [zql](https://tangled.sh/@zzstoatzz.io/zql) | comptime sql parsing - parameter extraction, struct mapping | 332 19 333 - ### splitting 20 + ## dependencies 334 21 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 - ``` 22 + - [websocket.zig](https://github.com/karlseguin/websocket.zig) - websocket client with tls support 23 + - [zqlite.zig](https://github.com/karlseguin/zqlite.zig) - sqlite wrapper