about things
0
fork

Configure Feed

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

add zig 0.15 structs note: copy semantics, internal storage pattern

- document slice vs array behavior when struct is copied
- add copy-safe pattern from logfire-zig (internal [max_len]u8 storage)
- add logfire-zig to sources table

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

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

zzstoatzz d3242aa8 3feeaf3a

+103
+1
languages/ziglang/0.15/README.md
··· 22 22 - [database](./database.md) - zqlite, connection patterns, transactions 23 23 - [interfaces](./interfaces.md) - comptime duck typing, type-returning functions, vtables 24 24 - [modules](./modules.md) - file + directory pattern (foo.zig + foo/) 25 + - [structs](./structs.md) - copy semantics, internal storage for strings 25 26 - [testing](./testing.md) - zig test vs build test, arena for leaky apis
+101
languages/ziglang/0.15/structs.md
··· 1 + # structs 2 + 3 + designing structs that behave correctly when copied. 4 + 5 + ## slices vs arrays in struct fields 6 + 7 + when you assign a struct, zig copies all fields by value. this has different implications depending on field type: 8 + 9 + **arrays** are value types - the data lives inside the struct: 10 + ```zig 11 + const Foo = struct { 12 + buffer: [64]u8, // 64 bytes stored in the struct 13 + }; 14 + 15 + var a = Foo{ .buffer = "hello".* ++ .{0} ** 59 }; 16 + var b = a; // copies all 64 bytes - b has its own data 17 + ``` 18 + 19 + **slices** are fat pointers - they store a pointer and length, not data: 20 + ```zig 21 + const Bar = struct { 22 + data: []const u8, // 16 bytes: pointer + length 23 + }; 24 + 25 + var a = Bar{ .data = "hello" }; 26 + var b = a; // copies the pointer - b.data points to same memory as a.data 27 + ``` 28 + 29 + this is documented in [zig.guide/slices](https://zig.guide/language-basics/slices/): "slices can be thought of as many-item pointers with a length... the validity and lifetime of the backing memory is in the hands of the programmer." 30 + 31 + ## the copy problem 32 + 33 + if you build a struct, store a slice pointing to temporary data, then copy that struct - the copy's slice becomes dangling: 34 + 35 + ```zig 36 + fn makeAttribute(temp_string: []const u8) Attribute { 37 + return .{ .value = temp_string }; // slice points to temp_string 38 + } 39 + 40 + // later, after temp_string goes out of scope: 41 + const attr = makeAttribute(some_temp); 42 + const copy = attr; // copy.value is now a dangling pointer 43 + ``` 44 + 45 + this happened multiple times in logfire-zig when attributes were queued for batch export - by the time they were serialized, the original strings were gone. 46 + 47 + ## copy-safe pattern: internal storage 48 + 49 + if a struct must survive being copied and needs string data, store the data internally: 50 + 51 + ```zig 52 + pub const Attribute = struct { 53 + key: []const u8, 54 + value: Value, 55 + _string_storage: [max_len]u8 = undefined, 56 + _string_len: usize = 0, 57 + 58 + pub const max_len = 512; 59 + 60 + pub const Value = union(enum) { 61 + string, // data in _string_storage[0.._string_len] 62 + int: i64, 63 + float: f64, 64 + bool_val: bool, 65 + }; 66 + 67 + pub fn getString(self: *const Attribute) ?[]const u8 { 68 + return switch (self.value) { 69 + .string => self._string_storage[0..self._string_len], 70 + else => null, 71 + }; 72 + } 73 + 74 + fn setString(self: *Attribute, str: []const u8) void { 75 + const len = @min(str.len, max_len); 76 + @memcpy(self._string_storage[0..len], str[0..len]); 77 + self._string_len = len; 78 + self.value = .string; 79 + } 80 + }; 81 + ``` 82 + 83 + key points: 84 + - `_string_storage` is a fixed array, not a slice - data is copied with the struct 85 + - `_string_len` tracks how much of the buffer is used 86 + - `getString()` reconstructs the slice from internal storage 87 + - the slice returned by `getString()` points into `self`, so copies get slices into their own storage 88 + 89 + see: [logfire-zig/attribute.zig](https://tangled.sh/@zzstoatzz.io/logfire-zig/tree/main/src/attribute.zig) 90 + 91 + ## when to use this pattern 92 + 93 + use internal storage when: 94 + - structs are queued, batched, or stored for later processing 95 + - structs are copied into collections (ArrayList, hashmap values) 96 + - ownership of string data is unclear or temporary 97 + 98 + use slices when: 99 + - struct lifetime is shorter than the data it references 100 + - data is compile-time constant (string literals) 101 + - you explicitly manage the backing memory
+1
languages/ziglang/README.md
··· 20 20 | [pollz](https://tangled.sh/@zzstoatzz.io/pollz) | bluesky polls (zqlite + transactions) | 21 21 | [zql](https://tangled.sh/@zzstoatzz.io/zql) | comptime sql parsing | 22 22 | [zat](https://tangled.sh/@zzstoatzz.io/zat) | atproto primitives (jwt, crypto) | 23 + | [logfire-zig](https://tangled.sh/@zzstoatzz.io/logfire-zig) | OTLP observability client | 23 24 | [prefect-zig](https://tangled.sh/@zzstoatzz.io/prefect-zig) | prefect orchestration server | 24 25 | [ghostty](https://github.com/ghostty-org/ghostty) | terminal emulator (build system) | 25 26 | [bun](https://github.com/oven-sh/bun) | javascript runtime (build system) |