about things
0
fork

Configure Feed

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

update 0.16 notes: cancellation, evented backends, randomness split, zig-pkg

- fix function coloring claim: parameter-based coloring still exists
- add cancellation pattern (defer future.cancel) — major footgun
- document Evented backends (io_uring, GCD, kqueue) as landed/experimental
- note WASM limitation and planned stackless coroutines
- fix randomness: split into io.random() (PRNG) and io.randomSecure() (CSPRNG)
- add zig-pkg local cache, --fork flag, lazier type resolution

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

+120 -10
+79 -3
languages/ziglang/0.16/io.md
··· 38 38 39 39 use `async` when you want asynchrony. use `concurrent` when you need parallelism. 40 40 41 - ## no function coloring 41 + ## cancellation (major footgun) 42 + 43 + `io.async()` returns a future with its own stack. if you `try` an error before `await`ing, you leak. always defer cancel: 44 + 45 + ```zig 46 + const future = io.async(someFunction, .{args}); 47 + defer future.cancel(io) catch {}; // idempotent — safe even after await 48 + 49 + // ... work that might fail with try ... 50 + 51 + const result = future.await(); 52 + ``` 53 + 54 + `cancel` and `await` share identical semantics — calling either one consumes the future. the deferred cancel is a no-op if await already ran. this pattern prevents resource leaks on early error returns. 55 + 56 + `Cancelable!void` is the return type for cancellable operations (sleep, I/O). propagate it with `try` to let callers cancel subscriptions/loops from the outside. 57 + 58 + ## no function coloring (mostly) 42 59 43 60 `async` and `await` are library functions, not keywords. no viral async/await infection through call stacks. any function can be called with `io.async()`. 44 61 ··· 52 69 // or asynchronously 53 70 const future = io.async(foo, .{io}); 54 71 ``` 72 + 73 + **caveat**: coloring shifted from keyword-based to parameter-based. if a pure computation function later needs I/O (file read, random, sleep), it must accept an `Io` parameter, which propagates to all callers. the advantage: code stays agnostic to the execution model (sync vs async vs evented). 55 74 56 75 ## std.net moved 57 76 ··· 68 87 69 88 ## what doesn't change 70 89 71 - `std.Thread.Mutex`, atomics, and low-level threading primitives remain. `std.Io` is a higher-level abstraction built on top of them. you still need mutexes when multiple threads access shared state. 90 + atomics and `std.Thread.spawn`/`.detach()` remain. `posix.setsockopt`/`SOL`/`SO` and `posix.Sigaction`/`sigaction` are unchanged. 91 + 92 + **note**: `std.Thread.Mutex` is replaced by `std.Io.Mutex` which requires an `io` parameter for lock/unlock. see [migration.md](./migration.md) for details. 93 + 94 + ## std.Options.debug_io is NOT for application code 95 + 96 + `std.Options.debug_io` is backed by `Io.Threaded.global_single_threaded`: 97 + 98 + ```zig 99 + pub const init_single_threaded: Threaded = .{ 100 + .allocator = .failing, 101 + .async_limit = .nothing, 102 + .concurrent_limit = .nothing, 103 + // ... 104 + }; 105 + /// This instance does not support concurrency or cancelation. 106 + pub const global_single_threaded: *Threaded = &global_single_threaded_instance; 107 + ``` 108 + 109 + it's designed for `std.debug.print` and stack trace capture — not application I/O. using it for mutex locks, sleeps, or network ops in a multi-threaded program will silently serialize everything. 110 + 111 + **symptom**: coral went from ~60 events/s (pre-0.16 with `std.Thread.Mutex`) to ~4/s after migrating to `Io.Mutex` with `std.Options.debug_io`. no errors, just slow. 112 + 113 + ### override it in your root source file 114 + 115 + ```zig 116 + const std = @import("std"); 117 + const Io = std.Io; 118 + 119 + // global storage, initialized in main() 120 + var app_threaded_io: Io.Threaded = undefined; 121 + 122 + // tells std to use our instance instead of global_single_threaded 123 + pub const std_options_debug_threaded_io: ?*Io.Threaded = &app_threaded_io; 124 + 125 + // all std.Options.debug_io references now use the real threaded instance 126 + const io = std.Options.debug_io; 127 + 128 + pub fn main() !void { 129 + const allocator = std.heap.smp_allocator; 130 + app_threaded_io = Io.Threaded.init(allocator, .{}); 131 + // ... 132 + } 133 + ``` 134 + 135 + this works because `std.Options.debug_io` resolves to `debug_threaded_io.?.io()`, and `debug_threaded_io` checks for the `std_options_debug_threaded_io` decl in the root source file. the `Io` struct just holds a pointer to the `Threaded` — so even though the `Threaded` is `undefined` at comptime, the pointer is stable and the data is accessed at runtime after `main()` initializes it. 136 + 137 + **result**: zero changes to any other file. all existing `std.Options.debug_io` usage across the codebase automatically gets the multi-threaded implementation. 138 + 139 + ### or just pass io explicitly 140 + 141 + the more "correct" approach: create `Io.Threaded` in `main()`, call `.io()`, and thread it through your functions as a parameter. avoids globals entirely. but overriding `debug_threaded_io` is much less invasive for existing codebases. 72 142 73 143 ## implementations 74 144 75 145 - `std.Io.Threaded` - thread-based, no event loop 76 - - other implementations possible (io_uring, kqueue, etc.) 146 + - `std.Io.Evented` - experimental backends have landed: 147 + - **linux**: `io_uring` (`std.Io.Uring`) 148 + - **macOS/iOS**: Grand Central Dispatch (`std.Io.Dispatch`) 149 + - **BSD**: kqueue (`std.Io.Kqueue`) 150 + - these use userspace stack switching (fibers/green threads) for massive concurrency without OS thread overhead 151 + - currently experimental — `Threaded` is the production default 152 + - **WASM**: fiber-based backends can't work (no stack switching). stackless coroutines planned as a future compiler feature — compiler will infer suspension points and rewrite to state machines. 77 153 78 154 the interface is concrete, not generic - same benefits as 0.15's explicit buffer approach.
+41 -7
languages/ziglang/0.16/migration.md
··· 5 5 ## high-impact changes 6 6 7 7 - **std.net removed** — networking now via `Io.net` 8 - - **std.crypto.random removed** — randomness now via `io.random()` 8 + - **std.crypto.random removed** — split into `io.random()` (fast PRNG) and `io.randomSecure()` (CSPRNG, can block) 9 9 - **std.Thread.Pool removed** — thread-per-connection (`Thread.spawn` + `.detach()`) 10 10 - **std.Thread.Mutex removed** — `Io.Mutex` (requires `io` param for lock/unlock) 11 11 - **std.Thread.sleep removed** — `io.sleep(.{ .nanoseconds = N }, .awake) catch {}` 12 12 - **std.time.timestamp/milliTimestamp removed** — `Io.Timestamp.now(io, .real)` 13 13 - **posix.shutdown removed** — use `std.c.shutdown(fd, how)` 14 14 - **posix.getenv removed** — use `std.c.getenv` + `std.mem.span()` (no Io equivalent) 15 - - **posix.getrandom removed** — use `io.random()` 15 + - **posix.getrandom removed** — use `io.random()` or `io.randomSecure()` 16 16 - **posix.exit removed** — use `std.process.exit()` 17 17 - **GeneralPurposeAllocator removed** — `std.heap.DebugAllocator(.{}).init` (tests) or `smp_allocator` (prod) 18 18 19 19 **rule of thumb**: use Io-based APIs. only fall back to `std.c.*` when no Io equivalent exists (just `getenv` and `shutdown` so far). 20 20 21 + ### cross-compilation: `-Dtarget` strips CPU features 22 + 23 + `-Dtarget=x86_64-linux-musl` without `-Dcpu` defaults to generic x86_64 — **no SSE, no AVX, nothing**. `std.simd.suggestVectorLength(f32)` returns `null`. code using `@Vector` falls back to scalar. 24 + 25 + ```dockerfile 26 + # WRONG — scalar loops, 35ms per NER call 27 + RUN zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux-musl 28 + 29 + # RIGHT — AVX2 enabled, 1.6ms per NER call (21x faster) 30 + RUN zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux-musl -Dcpu=x86_64_v3 31 + ``` 32 + 33 + without `-Dtarget`, zig detects the build host's CPU features (which is why local builds are fast). the moment you cross-compile, you must specify the CPU model. 34 + 35 + common levels: `x86_64` (SSE2), `x86_64_v2` (SSE4.2), `x86_64_v3` (AVX2), `x86_64_v4` (AVX-512). all AMD EPYC (fly.io) support `x86_64_v3`. 36 + 21 37 these require significant refactoring as they move to the Io-based paradigm. 22 38 23 39 ## Alpine / musl deployment ··· 61 77 ``` 62 78 63 79 no need for placeholder hashes or manual hash computation. 80 + 81 + ### local `zig-pkg` directory 82 + 83 + fetched packages are now stored in a `zig-pkg/` directory in the project root (next to `build.zig`). replaces the opaque global cache. benefits: 84 + - self-contained offline source tarballs 85 + - IDE autocomplete works on dependency source 86 + - `zig-pkg/` should be gitignored 87 + 88 + ### `--fork` flag for local overrides 89 + 90 + `zig build --fork=dep_name=/path/to/local/checkout` temporarily overrides a dependency across the entire dependency tree using a local source checkout. useful for testing upstream fixes without modifying `build.zig.zon`. 91 + 92 + ## compiler improvements 93 + 94 + ### lazier type resolution 95 + 96 + the compiler no longer analyzes fields of types that are never initialized (e.g. structs used purely as namespaces). eliminates "over-analysis" bugs, speeds up incremental compilation. dependency loop errors now provide detailed multi-line messages showing exactly which fields/types caused the cycle. 64 97 65 98 ## removed APIs 66 99 ··· 299 332 300 333 ## randomness 301 334 302 - `std.crypto.random` removed. use `io.random()`: 335 + `std.crypto.random` removed. randomness split into two functions: 303 336 304 337 ```zig 305 338 // 0.15 306 339 var bytes: [16]u8 = undefined; 307 340 std.crypto.random.bytes(&bytes); 308 341 309 - // 0.16 - io.random() fills a buffer 310 - const io = std.Options.debug_io; 311 - var bytes: [16]u8 = undefined; 312 - io.random(&bytes); // fills buffer in-place 342 + // 0.16 — two options: 343 + io.random(&bytes); // fast PRNG, non-blocking, non-cancelable 344 + try io.randomSecure(&bytes); // CSPRNG, may block (OS entropy), returns Cancelable!void 313 345 ``` 346 + 347 + use `random()` for non-security purposes (shuffling, jitter). use `randomSecure()` for cryptographic keys, nonces, tokens. 314 348 315 349 ## file I/O 316 350