From 3d8375cf399b84c57bbef8a7d113235930fa3dbc Mon Sep 17 00:00:00 2001 From: peterino2 Date: Mon, 13 Jul 2026 09:01:11 -0700 Subject: [PATCH] saving --- .gitignore | 2 + SKILL.md | 30 +- references/builtins.md | 20 +- references/c-interop.md | 47 +- references/code-review.md | 28 +- references/comptime.md | 26 +- references/language.md | 32 +- references/patterns.md | 36 +- references/std-allocators.md | 59 +- references/std-array-hash-map.md | 70 ++- references/std-arraylist.md | 42 +- references/std-ascii.md | 31 +- references/std-atomic.md | 27 +- references/std-base64.md | 18 +- references/std-bit-set.md | 26 +- references/std-buf-map.md | 30 +- references/std-build.md | 49 +- references/std-c.md | 107 ++-- references/std-compress.md | 109 ++-- references/std-crypto.md | 73 ++- references/std-debug.md | 122 ++--- references/std-enums.md | 29 +- references/std-fmt.md | 25 +- references/std-fs.md | 28 +- references/std-hash.md | 49 +- references/std-hashmap.md | 36 +- references/std-http.md | 86 +-- references/std-io.md | 23 +- references/std-json.md | 30 +- references/std-linked-list.md | 40 +- references/std-log.md | 42 +- references/std-math.md | 25 +- references/std-mem.md | 42 +- references/std-meta.md | 39 +- references/std-multi-array-list.md | 20 +- references/std-net.md | 768 ++++++++++++--------------- references/std-os.md | 175 ++---- references/std-priority-dequeue.md | 65 ++- references/std-priority-queue.md | 69 +-- references/std-process.md | 10 + references/std-random.md | 51 +- references/std-segmented-list.md | 16 +- references/std-simd.md | 22 +- references/std-sort.md | 22 +- references/std-static-string-map.md | 8 +- references/std-tar.md | 110 ++-- references/std-testing.md | 45 +- references/std-thread.md | 10 +- references/std-time.md | 18 +- references/std-treap.md | 11 +- references/std-tz.md | 47 +- references/std-unicode.md | 52 +- references/std-uri.md | 115 ++-- references/std-zig.md | 38 +- references/std-zip.md | 72 +-- references/std-zon.md | 62 ++- references/style-guide.md | 16 +- references/zig-0.16-release-notes.md | 32 +- 58 files changed, 1691 insertions(+), 1641 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bafbea --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +scripts/ +audit-results/ \ No newline at end of file diff --git a/SKILL.md b/SKILL.md index 6312fe4..142deec 100644 --- a/SKILL.md +++ b/SKILL.md @@ -11,7 +11,10 @@ Always load **[Zig 0.16 Release Notes Migration Reference](references/zig-0.16-r ## Critical: I/O Is an Interface -Starting in Zig 0.16.0, input/output APIs require a `std.Io` instance. Treat anything that can block, touch the OS, use entropy/time/network/process/file APIs, or introduce nondeterminism as owned by `std.Io`. +Zig 0.16.0 routes many new blocking, OS-facing, and nondeterministic APIs +through a `std.Io` instance. For new APIs, treat entropy, time, networking, +process, and file operations as `std.Io` responsibilities. Existing stdlib APIs +are not uniformly parameterized this way, so check each installed signature. Preferred app entry: @@ -158,11 +161,11 @@ defer client.deinit(); ### Time -0.16 routes time through `std.Io` types: - -- `std.time.Instant` -> `std.Io.Timestamp` -- `std.time.Timer` -> `std.Io.Timestamp` -- `std.time.timestamp` -> `std.Io.Timestamp.now` +0.16 routes runtime-dependent time through `std.Io`. Choose an explicit +`std.Io.Clock` and pass `io`; for example, use +`std.Io.Timestamp.now(io, .real)` for wall time and `.boot` or `.awake` for +elapsed-time measurements. `std.Io.Timestamp` is not a one-for-one timer +replacement: preserve the old callsite's clock semantics during migration. Applications may centralize timestamp reads behind a shared helper so clock selection and timestamp semantics remain consistent. @@ -190,7 +193,9 @@ Migration map: - `std.Thread.RwLock` -> `std.Io.RwLock` - `std.Thread.ResetEvent` -> `std.Io.Event` - `std.Thread.WaitGroup` -> `std.Io.Group` -- `std.Thread.Futex` -> `std.Io.Futex` +- `std.Thread.Futex` operations -> `io.futexWait`, `io.futexWaitTimeout`, + `io.futexWaitUncancelable`, and `io.futexWake` on a suitably aligned + four-byte value Cancelable lock: @@ -212,12 +217,14 @@ Do not replace these with custom spin loops. Use std primitives unless a measure ### Removed or Deprecated -- `@Type` removed. Use `@Int`, `@Struct`, `@Union`, `@Enum`, `@Pointer`, `@Fn`, `@Tuple`, `@Opaque`, or `@EnumLiteral`. +- `@Type` removed. Use `@Int`, `@Struct`, `@Union`, `@Enum`, `@Pointer`, + `@Fn`, `@Tuple`, or `@EnumLiteral`. Declare opaque types with `opaque {}`. - `@cImport` is deprecated long-term. Prefer `b.addTranslateC(...)` and import `translate_c.createModule()`. - `async`/`await` keywords remain removed; use `std.Io` task APIs, an application scheduler, or explicit threads. - `usingnamespace` is removed; explicitly re-export names. - `@fence` is removed; use stronger atomic orderings or RMW operations. -- `@intFromFloat` is deprecated; use `@trunc` when truncating float to integer. +- `@intFromFloat` converts a finite, in-range float to an integer. `@trunc` + truncates a floating value while retaining a floating result type. ### Packed and Extern Layout @@ -253,7 +260,8 @@ try list.append(gpa, 42); - Priority queues use `push`/`pop` terminology and `.empty` initialization. - `std.SegmentedList` removed. - `std.heap.ThreadSafe` removed. -- `std.heap.ArenaAllocator` is thread-safe and lock-free. +- `std.heap.ArenaAllocator`'s allocator interface is thread-safe when its child + allocator is thread-safe. Do not assume a universal lock-free guarantee. ## Critical: Build System @@ -362,6 +370,6 @@ Load these selectively: - **[Zig Patterns](references/patterns.md)** - Idiomatic patterns. Treat 0.16 release-note reference as authoritative when examples conflict. - **[Code Review](references/code-review.md)** - Review checklist. Always apply the 0.16 release-note checks first. -General references remain available for specific modules: `std.mem`, `std.fmt`, `std.json`, `std.zon`, `std.crypto`, `std.http`, `std.net`, `std.compress`, `std.tar`, `std.zip`, `std.testing`, `std.debug`, `std.log`, `std.atomic`, `std.simd`, and other files under `references/`. +General references remain available for specific modules: `std.mem`, `std.fmt`, `std.json`, `std.zon`, `std.crypto`, `std.http`, `std.Io.net`, `std.compress`, `std.tar`, `std.zip`, `std.testing`, `std.debug`, `std.log`, `std.atomic`, `std.simd`, and other files under `references/`. If any reference still contains a historical 0.14/0.15 example, the Zig 0.16 release-notes reference and the top-level rules in this file win. diff --git a/references/builtins.md b/references/builtins.md index 5689bc9..45eddd1 100644 --- a/references/builtins.md +++ b/references/builtins.md @@ -1,6 +1,6 @@ -# Zig Built-in Functions Reference +# Selected Zig Built-in Functions Reference -Built-in functions are compiler intrinsics prefixed with `@`. Parameters marked `comptime` must be compile-time known. +Built-in functions are compiler intrinsics prefixed with `@`. Parameters marked `comptime` must be compile-time known. This is a practical reference to commonly used built-ins, not an exhaustive inventory; consult the language reference when completeness matters. ## Table of Contents - [Type Conversions](#type-conversions) @@ -199,10 +199,10 @@ const x = @abs(@as(i32, -5)); // 5 ### @min / @max ```zig -@min(a: T, b: T) T -@max(a: T, b: T) T +@min(a: T, b: T, ...) T +@max(a: T, b: T, ...) T ``` -Return minimum/maximum of two values. +Return the minimum/maximum of two or more values. ```zig const m = @max(3, 7); // 7 ``` @@ -546,13 +546,13 @@ Get type of a struct field. ### @fieldParentPtr ```zig -@fieldParentPtr(field_ptr: anytype, comptime field_name: []const u8) anytype +@fieldParentPtr(comptime field_name: []const u8, field_ptr: anytype) anytype ``` Get pointer to containing struct from field pointer (for intrusive data structures). ```zig const Node = struct { data: u32, hook: Hook }; fn getNode(hook: *Hook) *Node { - return @fieldParentPtr(hook, "hook"); + return @fieldParentPtr("hook", hook); } ``` @@ -629,7 +629,7 @@ fn method(self: *Self) void { ... } ```zig @src() std.builtin.SourceLocation ``` -Get current source location (file, line, column, fn name). +Get current source location (module, file, line, column, and function name). ### @inComptime ```zig @@ -771,7 +771,7 @@ if (unlikely_condition) { // rarely executed } ``` -Hints: `.none`, `.likely`, `.unlikely`, `.cold` +Hints: `.none`, `.likely`, `.unlikely`, `.cold`, `.unpredictable` ### @breakpoint ```zig @@ -836,7 +836,7 @@ Call function with modifier. ```zig const result = @call(.always_inline, my_fn, .{ arg1, arg2 }); ``` -Modifiers: `.auto`, `.never_inline`, `.always_inline`, `.always_tail`, `.never_tail`, `.compile_time` +Modifiers: `.auto`, `.never_inline`, `.always_inline`, `.always_tail`, `.never_tail`, `.compile_time`, `.no_suspend`. The `.no_suspend` modifier asserts that the call will not suspend. ### @prefetch ```zig diff --git a/references/c-interop.md b/references/c-interop.md index 875c93d..76e0246 100644 --- a/references/c-interop.md +++ b/references/c-interop.md @@ -26,7 +26,8 @@ Minimal C-compatible library: ```zig const std = @import("std"); -// Global state (opaque to C consumers) +// Global state (opaque to C consumers). This minimal example is not +// thread-safe; init must be called exactly once before get/set/deinit. var context: ?*Context = null; const Context = struct { @@ -36,6 +37,7 @@ const Context = struct { /// Initialize the library. Returns 0 on success, -1 on failure. export fn mylib_init() c_int { + if (context != null) return -1; const gpa = std.heap.c_allocator; context = gpa.create(Context) catch return -1; context.?.* = .{ .allocator = gpa, .value = 0 }; @@ -76,12 +78,10 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/lib.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); - // Link libc if using std.heap.c_allocator - lib.linkLibC(); - b.installArtifact(lib); // Install header alongside library @@ -284,10 +284,9 @@ const lib = b.addLibrary(.{ .root_source_file = b.path("src/lib.zig"), .target = target, .optimize = optimize, + .link_libc = true, // Required by c_allocator or direct libc calls. }), }); - -lib.linkLibC(); // If using c_allocator or libc functions b.installArtifact(lib); ``` @@ -301,11 +300,11 @@ const lib = b.addLibrary(.{ .root_source_file = b.path("src/lib.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), .version = .{ .major = 1, .minor = 0, .patch = 0 }, }); -lib.linkLibC(); b.installArtifact(lib); ``` @@ -406,7 +405,9 @@ const std = @import("std"); pub const Context = struct { allocator: std.mem.Allocator, - error_msg: ?[]const u8 = null, + // Storage is owned by this context and remains valid until the next + // operation that changes the error or until mylib_destroy. + error_msg: ?[:0]const u8 = null, callback: ?Callback = null, const Callback = struct { @@ -417,7 +418,9 @@ pub const Context = struct { export fn mylib_create() ?*Context { const allocator = std.heap.c_allocator; - return allocator.create(Context) catch null; + const ctx = allocator.create(Context) catch return null; + ctx.* = .{ .allocator = allocator }; + return ctx; } export fn mylib_destroy(ctx: ?*Context) void { @@ -663,7 +666,7 @@ Name: MyLib Functions: - Name: mylib_create SwiftName: "MyLibContext.create()" - NullabilityOfRet: N # Non-null (returns Optional in Swift) + NullabilityOfRet: O # Nullable C pointer; imported as Optional in Swift - Name: mylib_destroy SwiftName: "MyLibContext.destroy(self:)" - Name: mylib_get_error @@ -802,43 +805,29 @@ export fn get_greeting() [*:0]const u8 { return greeting.ptr; } -// Allocate string for caller to free +// Allocate a sentinel-terminated string. The caller must pass the same +// payload length to free_string; the sentinel is stored at index len. export fn alloc_string(len: usize) ?[*:0]u8 { const allocator = std.heap.c_allocator; const buf = allocator.allocSentinel(u8, len, 0) catch return null; return buf.ptr; } -export fn free_string(s: ?[*:0]u8) void { +export fn free_string(s: ?[*:0]u8, len: usize) void { if (s) |ptr| { const allocator = std.heap.c_allocator; - // Need to know length to free - typically tracked separately - // or use c_allocator which can query allocation size - _ = allocator; - _ = ptr; + allocator.free(ptr[0..len :0]); } } ``` ### Thread Safety -For thread-safe libraries, use atomics or mutexes: +For simple exported counters, atomics avoid the need to expose an I/O context across the C ABI. More complex synchronization in Zig 0.16 uses `std.Io.Mutex`, whose lock operations take an `std.Io` value; design that runtime context into the library rather than using the removed `std.Thread.Mutex` API. ```zig const std = @import("std"); -var global_mutex: std.Thread.Mutex = .{}; -var shared_value: c_int = 0; - -export fn thread_safe_increment() c_int { - global_mutex.lock(); - defer global_mutex.unlock(); - - shared_value += 1; - return shared_value; -} - -// Or use atomics for simple cases var atomic_counter: std.atomic.Value(c_int) = .init(0); export fn atomic_increment() c_int { diff --git a/references/code-review.md b/references/code-review.md index ed887c5..c9d9382 100644 --- a/references/code-review.md +++ b/references/code-review.md @@ -32,7 +32,7 @@ Systematic code review checklist organized by detection confidence level. Work t |--------|------|--------| | `@intCast(val)` without bounds check | Runtime panic | No `std.math.cast` or prior validation | | `.?` unwrap | Runtime panic | Not guarded by `if` or `orelse` | -| `catch unreachable` on alloc/create | Runtime panic | Allocation can fail | +| Unjustified `catch unreachable` on alloc/create | Runtime panic | Allocation can fail and no capacity bound proves otherwise | | `return &local_var` | Dangling pointer | Returns address of stack variable | | `&packed_struct.field` | Undefined behavior | Pointer to packed struct field | | pointer field in `packed struct`/`packed union` | Forbidden in 0.16 | Compile error / invalid layout | Store integer representation only if ABI requires it | @@ -395,7 +395,7 @@ fn getName(user: ?*User) []const u8 { #### 1.6.3 Catch Unreachable on Allocation -| Detect | `catch unreachable` after `alloc`/`create` calls | +| Detect | Unjustified `catch unreachable` after `alloc`/`create` calls | |--------|--------------------------------------------------| | Risk | Runtime panic on OOM | @@ -413,7 +413,7 @@ fn createBuffer(allocator: Allocator) !*Buffer { } ``` -**Verify:** Search for `catch unreachable`, check if operation can fail. +**Verify:** Search for `catch unreachable` and check whether failure is genuinely impossible under a documented invariant. It can be appropriate after a successful reservation or with a fixed, proven capacity bound; ordinary allocation failure should be propagated. #### 1.6.4 Returning Stack Pointer @@ -536,7 +536,7 @@ fn parseColor(byte: u8) Color { **Right:** ```zig fn parseColor(byte: u8) ?Color { - return std.meta.intToEnum(Color, byte) catch null; + return std.enums.fromInt(Color, byte); } ``` @@ -570,10 +570,10 @@ pub fn main() !void { **Verify:** Search for `DebugAllocator`, check for `defer.*deinit()`. -#### 1.7.2 @ptrCast Size Mismatch +#### 1.7.2 Unsafe @ptrCast Access -| Detect | `@ptrCast` between types of different sizes | -|--------|---------------------------------------------| +| Detect | `@ptrCast` whose result may be under-aligned or address more memory than the source allocation provides | +|--------|------------------------------------------------------------------------------------------------| | Risk | Memory corruption | **Wrong:** @@ -585,12 +585,14 @@ fn dangerous(ptr: *u32) *u64 { **Right:** ```zig -fn reinterpret(ptr: *u32) *[4]u8 { - return @ptrCast(ptr); // Same size +fn firstBytes(ptr: *u32) *align(@alignOf(u32)) [4]u8 { + // The u32 object provides four accessible bytes and the result preserves + // the source pointer's alignment contract. + return @ptrCast(ptr); } ``` -**Verify:** Check `@ptrCast` source and target sizes match. +**Verify:** Check the destination alignment, the number of bytes callers may access through the result, aliasing/lifetime constraints, and whether a byte copy would express the intent more safely. Equal pointee sizes alone do not make a cast valid. #### 1.7.3 Packed Struct Field Pointer @@ -1311,13 +1313,13 @@ fn formatVersion(allocator: Allocator, major: u32, minor: u32) ![]u8 { **Right (caller provides buffer):** ```zig -fn formatVersion(buf: []u8, major: u32, minor: u32) []u8 { - return std.fmt.bufPrint(buf, "{d}.{d}", .{ major, minor }) catch unreachable; +fn formatVersion(buf: []u8, major: u32, minor: u32) ![]u8 { + return std.fmt.bufPrint(buf, "{d}.{d}", .{ major, minor }); } // Call site — buffer outlives the returned slice var buf: [32]u8 = undefined; -const version = formatVersion(&buf, 1, 2); +const version = try formatVersion(&buf, 1, 2); ``` ### 3.11 Comptime Optimization diff --git a/references/comptime.md b/references/comptime.md index 8256866..a47893d 100644 --- a/references/comptime.md +++ b/references/comptime.md @@ -61,7 +61,7 @@ comptime { ### Container-Level Comptime -Top-level declarations are implicitly comptime. +Top-level constants whose values must be compile-time known are evaluated on demand at compile time. This does not make every top-level declaration a `comptime` block or imply eager evaluation. ```zig // These are computed at compile time automatically @@ -187,7 +187,7 @@ fn sumComptime(comptime values: []const i32) i32 { ### inline for -Loop unrolling with code generation. Body is duplicated per iteration. Can reference runtime values. Cannot use `break` to return values. +Loop unrolling with code generation. Body is duplicated per iteration and may reference runtime values. Like other loop expressions, an `inline for` can use `break` with a value when its control flow is valid. ```zig fn printFields(value: anytype) void { @@ -219,7 +219,7 @@ fn eqlAny(comptime T: type, a: T, b: T) bool { | Need | Use | Reason | |------|-----|--------| -| Return value from loop | `comptime for` | Only comptime allows `break` with value | +| Evaluate the entire loop and its result at compile time | `comptime for` | All values and control flow must be comptime-known | | Access runtime values in body | `inline for` | Comptime can't see runtime | | Type-level computation only | `comptime for` | Clearer intent, no code gen | | Generate code per iteration | `inline for` | Each iteration = separate code | @@ -250,6 +250,8 @@ const builtin = @import("builtin"); const native_endian = builtin.cpu.arch.endian(); pub fn readIntBig(comptime T: type, bytes: []const u8) T { + comptime std.debug.assert(@typeInfo(T) == .int); + std.debug.assert(bytes.len >= @sizeOf(T)); const value: T = @bitCast(bytes[0..@sizeOf(T)].*); if (comptime native_endian == .big) { return value; @@ -261,15 +263,15 @@ pub fn readIntBig(comptime T: type, bytes: []const u8) T { ### Propagating Across Functions -Use `inline fn` to propagate comptime conditions to call sites: +Comptime parameters specialize the function and make conditions that depend on them compile-time known. `inline fn` additionally requests call-site inlining; it is not required merely to eliminate a comptime-known branch: ```zig -// WITHOUT inline: branch exists at runtime +// The enabled branch is specialized at compile time. fn maybeLog(comptime enabled: bool, msg: []const u8) void { if (enabled) std.debug.print("{s}\n", .{msg}); } -// WITH inline: branch eliminated at each call site +// inline additionally requests call-site inlining. inline fn maybeLogInline(comptime enabled: bool, msg: []const u8) void { if (comptime enabled) std.debug.print("{s}\n", .{msg}); } @@ -414,11 +416,15 @@ fn getTypeName(value: anytype) []const u8 { fn typeFromName(name: []const u8) type { ... } ``` -### No I/O at Comptime +### Comptime I/O Boundary ```zig -// NOT POSSIBLE -const config = comptime std.fs.cwd().readFile("config.json"); +// Ordinary runtime filesystem I/O is not available at comptime. +// This is intentionally invalid; Zig 0.16 has no such comptime std.Io call: +// const config = comptime std.Io.Dir.cwd().readFileAlloc(...); + +// Compiler-supported static input is available: +const embedded = @embedFile("config.json"); // Alternatives: const config = @embedFile("config.json"); // Static embedding @@ -448,7 +454,7 @@ fn addMethod(comptime T: type, comptime name: []const u8, impl: anytype) type { | Type reflection | Yes | `@typeInfo`, `@TypeOf` | | Generate types | Yes | Return struct from function | | Add methods to types | No | Define in type definition | -| Read files | No | `@embedFile` or build.zig | +| Read files | Static inputs only | `@embedFile`; use build.zig for ordinary I/O | | Syscalls | No | build.zig runs as program | | Parse strings to code | No | Parse to data structures | | Host detection | No | Build system queries | diff --git a/references/language.md b/references/language.md index 8e7da21..9fb1d97 100644 --- a/references/language.md +++ b/references/language.md @@ -31,7 +31,7 @@ Key 0.16 language changes: ### Primitive Types ```zig -// Integers (signed and unsigned, any bit width 1-65535) +// Integers (signed and unsigned, including arbitrary-width integer types) i8, i16, i32, i64, i128, isize // signed u8, u16, u32, u64, u128, usize // unsigned i7, u24, i53 // arbitrary widths @@ -75,6 +75,7 @@ const z: u32 = @bitCast(float_val); // reinterpret bits ```zig // Fixed-size arrays const arr: [5]u8 = .{ 1, 2, 3, 4, 5 }; +var mutable_arr: [5]u8 = .{ 1, 2, 3, 4, 5 }; const arr2 = [_]u8{ 1, 2, 3 }; // infer length const zeros = [_]u8{0} ** 100; // repeat pattern @@ -89,7 +90,7 @@ const len = arr.len; // Iteration for (arr) |elem| { ... } for (arr, 0..) |elem, i| { ... } // with index -for (&arr) |*elem| { elem.* = 0; } // mutable +for (&mutable_arr) |*elem| { elem.* = 0; } // mutable ``` ### Tuples @@ -220,7 +221,10 @@ for (a, b, c) |x, y, z| { ... } // Mutable iteration for (&items) |*item| { item.* = new_value; } -// Range (comptime only for runtime, but works in comptime blocks) +// Runtime bounds work with an ordinary for loop. +for (start..end) |i| { ... } + +// Use inline for when the range is comptime-known and should be unrolled. inline for (0..10) |i| { ... } ``` @@ -257,7 +261,7 @@ fn example() void { } // Only runs on error return -fn example() !void { +fn example() !*Resource { const ptr = try allocate(); errdefer free(ptr); // runs only if function returns error try doSomething(ptr); @@ -587,8 +591,12 @@ const ptr: [*]u8 = buffer.ptr; const next = ptr + 1; const offset = ptr + n; -// Single-item pointers do NOT support arithmetic -// Use slicing instead: +// A single-item pointer can only establish a one-element slice by itself. +const one_ptr: *u8 = ...; +const one_item = one_ptr[0..1]; + +// Slicing an arbitrary length requires a many-item pointer plus an external +// guarantee that at least n elements are accessible. const slice = ptr[0..n]; ``` @@ -650,15 +658,9 @@ fn isInteger(comptime T: type) bool { return @typeInfo(T) == .int; } -fn fieldNames(comptime T: type) []const []const u8 { - const info = @typeInfo(T); - if (info != .@"struct") @compileError("expected struct"); - - var names: [info.@"struct".fields.len][]const u8 = undefined; - for (info.@"struct".fields, 0..) |field, i| { - names[i] = field.name; - } - return &names; +fn fieldNames(comptime T: type) *const [std.meta.fields(T).len][:0]const u8 { + // std.meta.fieldNames returns comptime-backed fixed storage. + return std.meta.fieldNames(T); } ``` diff --git a/references/patterns.md b/references/patterns.md index 4557b0c..d278892 100644 --- a/references/patterns.md +++ b/references/patterns.md @@ -64,7 +64,8 @@ Comprehensive patterns for writing idiomatic Zig code. Zig 0.16.0 changes I/O ow #### Allocator Setup ```zig // Debug allocator (development - detects leaks, use-after-free) -// Note: GeneralPurposeAllocator is now an alias for DebugAllocator +// DebugAllocator is the development allocator used here. Do not rely on the +// old GeneralPurposeAllocator name being an alias. var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -501,21 +502,21 @@ comptime const x, const y = .{ 1, 2 }; **When to use:** Multiple return values, array element extraction, SIMD vector unpacking. #### Hashed Mappings Storage -Use multiple `AutoArrayHashMapUnmanaged` fields when storing complex interned data. +Use multiple `std.array_hash_map.Auto` fields when storing complex interned data. The older root-level `AutoArrayHashMapUnmanaged` name is deprecated in Zig 0.16. ```zig // From llvm/Builder.zig - demonstrating the pattern of parallel maps for interned data -string_map: std.AutoArrayHashMapUnmanaged(void, void), +string_map: std.array_hash_map.Auto(void, void), string_indices: std.ArrayListUnmanaged(u32), string_bytes: std.ArrayListUnmanaged(u8), -types: std.AutoArrayHashMapUnmanaged(String, Type), -type_map: std.AutoArrayHashMapUnmanaged(void, void), +types: std.array_hash_map.Auto(String, Type), +type_map: std.array_hash_map.Auto(void, void), type_items: std.ArrayListUnmanaged(Type.Item), type_extra: std.ArrayListUnmanaged(u32), -attributes: std.AutoArrayHashMapUnmanaged(Attribute.Storage, void), -attributes_map: std.AutoArrayHashMapUnmanaged(void, void), +attributes: std.array_hash_map.Auto(Attribute.Storage, void), +attributes_map: std.array_hash_map.Auto(void, void), attributes_indices: std.ArrayListUnmanaged(u32), ``` @@ -751,19 +752,19 @@ try stdout.print("{f}", .{version}); **When to use:** Any type that needs custom string representation. #### Custom Type JSON -Implement `jsonParse`, `jsonParseFromValue`, and `jsonStringify` for JSON support. +The example below implements token-source parsing with `jsonParse`. Complete custom JSON integration may also require `jsonParseFromValue` and `jsonStringify`, depending on the APIs callers use. ```zig pub fn ArrayHashMap(comptime T: type) type { return struct { - map: std.StringArrayHashMapUnmanaged(T) = .empty, + map: std.array_hash_map.String(T) = .empty, pub fn jsonParse( allocator: Allocator, source: anytype, options: ParseOptions, ) !@This() { - var map: std.StringArrayHashMapUnmanaged(T) = .empty; + var map: std.array_hash_map.String(T) = .empty; errdefer map.deinit(allocator); if (.object_begin != try source.next()) return error.UnexpectedToken; @@ -1096,11 +1097,9 @@ pub const Node = enum(u32) { return @enumFromInt(@intFromEnum(r.start) + i); } - /// Iterate over all nodes in range. - pub fn slice(r: Range) []const Node { - // Note: requires nodes stored contiguously - return @ptrCast(@as([*]const u32, @ptrFromInt(@intFromEnum(r.start)))[0..r.len]); - } + // A Range stores indices, not node values or an address. Use at() to + // iterate, or pass the owning node storage to an accessor that returns + // the corresponding data slice. }; }; @@ -1121,6 +1120,7 @@ When individual node deletion is needed, maintain a freelist stack: ```zig pub const NodePool = struct { + allocator: std.mem.Allocator, nodes: std.ArrayListUnmanaged(Node.Data), /// Head of freelist, or none if no free slots. free_head: OptionalNode = .none, @@ -1133,7 +1133,7 @@ pub const NodePool = struct { } // Allocate new slot const index: Node = @enumFromInt(self.nodes.items.len); - try self.nodes.append(undefined); + try self.nodes.append(self.allocator, undefined); return index; } @@ -1356,7 +1356,7 @@ pub fn internString(state: *State, gpa: Allocator, bytes: []const u8) !String { } ``` -**Real-world example from HashMap.grow:** +**Abbreviated pattern based on HashMap growth logic** (helper declarations such as `new_cap`, `old_capacity`, and `Self.allocate` are intentionally omitted): ```zig fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void { var map: Self = .{}; @@ -1388,7 +1388,7 @@ fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) All - String/symbol interning (hash table + byte array) - Any operation where failure after partial mutation leaves invalid state -**Key insight:** `ensureUnusedCapacity` is magic—it contains all the failure modes but changes nothing. Reservation failures are safe to retry; partial mutations are not. +**Key insight:** `ensureUnusedCapacity` concentrates the fallible reservation step. On error it leaves the container unchanged; on success it may allocate, rehash, change capacity, and invalidate pointers. Reservation failures are safe to retry; partial logical mutations are not. ### IV. Performance Patterns diff --git a/references/std-allocators.md b/references/std-allocators.md index 0e9755c..1c7af10 100644 --- a/references/std-allocators.md +++ b/references/std-allocators.md @@ -8,14 +8,12 @@ Zig has no default allocator. Functions that need heap memory accept an `Allocat |-----------|----------|-------------| | `std.testing.allocator` | Unit tests (leak detection) | No | | `std.heap.FixedBufferAllocator` | Stack-based, bounded size known | Optional | -| `std.heap.ArenaAllocator` | Batch free, CLI apps, request handlers | No | +| `std.heap.ArenaAllocator` | Batch free, CLI apps, request handlers | Allocator interface only, when child is thread-safe | | `std.heap.page_allocator` | Backing for other allocators | Yes | | `std.heap.c_allocator` | Linking libc, interop | Yes | -| `std.heap.raw_c_allocator` | Libc arena backing (no alignment overhead) | Yes | | `std.heap.DebugAllocator` | Debug builds, leak/corruption detection | Configurable | | `std.heap.smp_allocator` | ReleaseFast production multithreaded | Yes | | `std.heap.MemoryPool` | High-frequency same-type allocations | No | -| `std.heap.ThreadSafeAllocator` | Wrap non-thread-safe allocator | Yes | | `std.heap.StackFallbackAllocator` | Stack buffer with heap fallback | Depends | | `std.heap.wasm_allocator` | WebAssembly targets | Yes | @@ -156,7 +154,7 @@ slice = try allocator.realloc(slice, new_len); 7. **Many same-type objects?** Use `MemoryPool(T)` for fast create/destroy 8. **Debug build?** Use `DebugAllocator` for leak/corruption detection 9. **ReleaseFast production?** Use `std.heap.smp_allocator` -10. **Linking libc?** Use `c_allocator` or `raw_c_allocator` (as arena backing) +10. **Linking libc?** Use `c_allocator` ## Common Allocators @@ -187,7 +185,7 @@ allocator.free(data); fba.reset(); ``` -**Thread-safe variant** (allocate only - no resize/free): +**Thread-safe variant:** allocation is lock-free. Resize, remap, and free are supported, but only the most recent allocation can be expanded or release arena space; shrinking an older allocation succeeds without reclaiming its bytes. ```zig const ts_allocator = fba.threadSafeAllocator(); ``` @@ -231,9 +229,9 @@ while (running) { - `.retain_capacity` - Keep allocated pages for reuse (faster) - `.{ .retain_with_limit = N }` - Retain up to N bytes -**Query current usage:** +**Query retained capacity:** ```zig -const bytes_used = arena.queryCapacity(); // Excludes internal overhead +const retained_bytes = arena.queryCapacity(); // Excludes internal overhead ``` **State optimization** - store just the state to save memory: @@ -305,47 +303,34 @@ const allocator = std.heap.page_allocator; Fast allocator for many objects of the same type. Outperforms general-purpose allocators when allocating/freeing objects in rapid succession: ```zig -var pool = std.heap.MemoryPool(MyStruct).init(std.heap.page_allocator); -defer pool.deinit(); +const allocator = std.heap.page_allocator; +var pool: std.heap.MemoryPool(MyStruct) = .empty; +defer pool.deinit(allocator); // Allocate objects (very fast) -const obj1 = try pool.create(); -const obj2 = try pool.create(); +const obj1 = try pool.create(allocator); +const obj2 = try pool.create(allocator); // Free returns to pool for reuse (not to backing allocator) pool.destroy(obj1); // Reuses freed slot -const obj3 = try pool.create(); // likely same address as obj1 +const obj3 = try pool.create(allocator); // likely same address as obj1 // Reset all - batch destroy without individual frees -_ = pool.reset(.retain_capacity); +_ = pool.reset(allocator, .retain_capacity); ``` **Options:** ```zig // Pre-allocate slots -var pool = try std.heap.MemoryPool(T).initPreheated(allocator, 100); +var pool = try std.heap.MemoryPool(T).initCapacity(allocator, 100); // Custom alignment -var pool = std.heap.MemoryPoolAligned(T, .@"64").init(allocator); +var pool: std.heap.memory_pool.Aligned(T, .@"64") = .empty; // Non-growable (fixed capacity) -var pool = try std.heap.MemoryPoolExtra(T, .{ .growable = false }).initPreheated(allocator, 50); -``` - -### ThreadSafeAllocator - -Wraps any allocator with mutex for thread safety: - -```zig -var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -defer arena.deinit(); - -var ts = std.heap.ThreadSafeAllocator{ - .child_allocator = arena.allocator(), -}; -const allocator = ts.allocator(); // Safe to use from multiple threads +var pool = try std.heap.memory_pool.Extra(T, .{ .growable = false }).initCapacity(allocator, 50); ``` ### StackFallbackAllocator @@ -363,18 +348,6 @@ const small = try allocator.alloc(u8, 100); const large = try allocator.alloc(u8, 10000); ``` -### raw_c_allocator - -Direct malloc/free without alignment overhead. Use as `ArenaAllocator` backing when linking libc: - -```zig -// More efficient than c_allocator when wrapping with ArenaAllocator -var arena = std.heap.ArenaAllocator.init(std.heap.raw_c_allocator); -defer arena.deinit(); -``` - -Requires linking libc. Does not support custom alignment - asserts alignment <= `@alignOf(std.c.max_align_t)`. - ### Wasm Allocator Optimized for WebAssembly. Uses `@wasmMemoryGrow`: @@ -498,7 +471,7 @@ fn process(allocator: Allocator) void { Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html -- `std.heap.ArenaAllocator` is thread-safe and lock-free in Zig 0.16. +- `std.heap.ArenaAllocator.allocator()` is thread-safe when its child allocator is thread-safe. Concurrent lifecycle, reset, state, and capacity-query operations are outside that guarantee; do not treat “lock-free” as a universal public contract. - `std.heap.ThreadSafe` allocator was removed. - Allocators that perform blocking synchronization or file/entropy/time work should accept/store `std.Io` at initialization rather than constructing a local backend. - Keep allocator naming by memory contract (`gpa`, `arena`, `scratch`) and expose stored `io` through a small accessor only when callsites need it. diff --git a/references/std-array-hash-map.md b/references/std-array-hash-map.md index aa356b3..7f04737 100644 --- a/references/std-array-hash-map.md +++ b/references/std-array-hash-map.md @@ -7,11 +7,9 @@ Zig 0.16 removed the managed array hash map aliases: - `std.ArrayHashMap` removed. - `std.AutoArrayHashMap` removed. - `std.StringArrayHashMap` removed. -- `std.AutoArrayHashMapUnmanaged` -> `std.array_hash_map.Auto` -- `std.StringArrayHashMapUnmanaged` -> `std.array_hash_map.String` -- `std.ArrayHashMapUnmanaged` -> `std.array_hash_map.Custom` +- The unmanaged root aliases still exist but are deprecated; prefer `std.array_hash_map.Auto`, `.String`, and `.Custom`. -Old examples below may use removed names; translate them to the `std.array_hash_map` names before using them in Zig 0.16 code. +All examples below use the Zig 0.16 unmanaged API: initialize with `.empty`, pass an allocator to operations that may allocate, and pass it again to `deinit`/`clearAndFree`. A hash map that preserves insertion order and stores keys/values in contiguous arrays. Combines hash table lookup with array-like iteration. @@ -26,23 +24,22 @@ A hash map that preserves insertion order and stores keys/values in contiguous a | Type | Description | |------|-------------| -| `AutoArrayHashMap(K, V)` | Auto-hashing for common key types | -| `ArrayHashMap(K, V, Ctx, store_hash)` | Custom hash/equal context | -| `StringArrayHashMap(V)` | String keys | -| `ArrayHashMapUnmanaged(...)` | No stored allocator | +| `std.array_hash_map.Auto(K, V)` | Auto-hashing for common key types | +| `std.array_hash_map.Custom(K, V, Ctx, store_hash)` | Custom hash/equal context | +| `std.array_hash_map.String(V)` | String keys | ## Basic Usage ```zig const std = @import("std"); -var map = std.AutoArrayHashMap(u32, []const u8).init(allocator); -defer map.deinit(); +var map: std.array_hash_map.Auto(u32, []const u8) = .empty; +defer map.deinit(allocator); // Insert -try map.put(1, "one"); -try map.put(2, "two"); -try map.put(3, "three"); +try map.put(allocator, 1, "one"); +try map.put(allocator, 2, "two"); +try map.put(allocator, 3, "three"); // Lookup if (map.get(2)) |value| { @@ -58,9 +55,9 @@ if (map.contains(1)) { ## Insertion Order Preserved ```zig -try map.put(10, "ten"); -try map.put(5, "five"); -try map.put(15, "fifteen"); +try map.put(allocator, 10, "ten"); +try map.put(allocator, 5, "five"); +try map.put(allocator, 15, "fifteen"); // Iteration is in insertion order: 10, 5, 15 var it = map.iterator(); @@ -101,13 +98,13 @@ if (map.fetchSwapRemove(key)) |kv| { ```zig // Get existing or insert new -const result = try map.getOrPut(key); +const result = try map.getOrPut(allocator, key); if (!result.found_existing) { result.value_ptr.* = "new_value"; } // Get or put with default value -const result2 = try map.getOrPutValue(key, "default"); +const result2 = try map.getOrPutValue(allocator, key, "default"); ``` ## Index-Based Operations @@ -125,24 +122,24 @@ if (map.getIndex(key)) |idx| { ## Capacity Management ```zig -try map.ensureTotalCapacity(100); -try map.ensureUnusedCapacity(10); +try map.ensureTotalCapacity(allocator, 100); +try map.ensureUnusedCapacity(allocator, 10); const cap = map.capacity(); const len = map.count(); map.clearRetainingCapacity(); -map.clearAndFree(); +map.clearAndFree(allocator); ``` ## String Keys ```zig -var map = std.StringArrayHashMap(i32).init(allocator); -defer map.deinit(); +var map: std.array_hash_map.String(i32) = .empty; +defer map.deinit(allocator); -try map.put("apple", 1); -try map.put("banana", 2); +try map.put(allocator, "apple", 1); +try map.put(allocator, "banana", 2); // Keys are stored by reference, not copied // Make sure string lifetime exceeds map usage @@ -164,16 +161,16 @@ const CaseInsensitiveContext = struct { } }; -var map = std.ArrayHashMap( +var map: std.array_hash_map.Custom( []const u8, i32, CaseInsensitiveContext, true, // store_hash for better performance -).initContext(allocator, .{}); -defer map.deinit(); + ) = .empty; +defer map.deinit(allocator); -try map.put("Hello", 1); -_ = map.get("HELLO"); // finds it! +try map.putContext(allocator, "Hello", 1, .{}); +_ = map.getContext("HELLO", .{}); // finds it! ``` ## Complete Example: Word Counter @@ -185,13 +182,14 @@ pub fn main() !void { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); - var counts = std.StringArrayHashMap(u32).init(gpa.allocator()); - defer counts.deinit(); + const allocator = gpa.allocator(); + var counts: std.array_hash_map.String(u32) = .empty; + defer counts.deinit(allocator); const words = [_][]const u8{ "apple", "banana", "apple", "cherry", "banana", "apple" }; for (words) |word| { - const result = try counts.getOrPut(word); + const result = try counts.getOrPut(allocator, word); if (result.found_existing) { result.value_ptr.* += 1; } else { @@ -221,13 +219,13 @@ pub fn main() !void { | orderedRemove | N/A | O(n) | | Iteration order | Undefined | Insertion order | | Key/value arrays | No | Yes | -| Memory layout | Scattered | Contiguous | +| Sequential iteration storage | Not exposed as direct key/value arrays | Direct key/value arrays | ## Notes - Iteration order equals insertion order - `swapRemove` is O(1) but changes order - `orderedRemove` preserves order but is O(n) -- Use `store_hash=true` when `eql` is expensive +- `store_hash=true` trades memory for avoiding some repeated hash work; benchmark it with the actual key/context workload - Keys/values are stored in `MultiArrayList` (cache-friendly) -- Pointer stability only guaranteed with pre-allocated capacity +- Any modification invalidates iterators. Growth/rehash can invalidate key/value pointers; consult the API's pointer-locking rules rather than assuming a capacity reservation guarantees stability across all mutations. diff --git a/references/std-arraylist.md b/references/std-arraylist.md index ae3c974..4d86997 100644 --- a/references/std-arraylist.md +++ b/references/std-arraylist.md @@ -7,17 +7,19 @@ Dynamic array (vector) that grows as needed. ## Initialization ```zig -// CRITICAL: Use .empty, not .{} +// Use the supported explicit empty initializer. var list: std.ArrayList(u32) = .empty; defer list.deinit(allocator); // With pre-allocated capacity -var list = try std.ArrayList(u32).initCapacity(allocator, 100); +var reserved = try std.ArrayList(u32).initCapacity(allocator, 100); +defer reserved.deinit(allocator); // From existing slice (takes ownership) var list = std.ArrayList(u32).fromOwnedSlice(existing_slice); -// Fixed buffer (no allocator needed for operations) +// Fixed buffer: use bounded/no-allocator operations only. Passing an allocator +// to a method on an initBuffer list is illegal. var buffer: [8]i32 = undefined; var stack = std.ArrayList(i32).initBuffer(&buffer); ``` @@ -36,7 +38,8 @@ list.appendSliceAssumeCapacity(&[_]u32{1, 2, 3}); // Access items const items = list.items; // []T slice const first = list.items[0]; -const last = list.getLast(); // returns ?T +const last = list.getLast(); // returns T; asserts if empty +const maybe_last = list.getLastOrNull(); // returns ?T const popped = list.pop(); // returns ?T, removes last // Insert at index @@ -44,8 +47,8 @@ try list.insert(allocator, 2, value); try list.insertSlice(allocator, 2, slice); // Remove -const removed = list.orderedRemove(index); // O(n), preserves order -const removed = list.swapRemove(index); // O(1), doesn't preserve order +const removed_ordered = list.orderedRemove(index); // O(n), preserves order +const removed_swapped = list.swapRemove(index); // O(1), changes order ``` ## Capacity Management @@ -57,7 +60,8 @@ try list.ensureUnusedCapacity(allocator, 10); // Ensure total capacity is at least N try list.ensureTotalCapacity(allocator, 100); -// Shrink to fit +// Attempt to shrink capacity. If resizing runs out of memory, the list keeps +// excess capacity while still reducing its logical length. list.shrinkAndFree(allocator, list.items.len); // Clear @@ -74,6 +78,7 @@ defer allocator.free(owned); // Get null-terminated slice const z_str = try list.toOwnedSliceSentinel(allocator, 0); +defer allocator.free(z_str); ``` ## Iteration @@ -123,14 +128,18 @@ When inserting into multiple containers or when partial mutation would corrupt s ```zig // BAD - partial failure leaves invalid state -fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Allocator, value: u32) !void { +fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMapUnmanaged(u32, usize), gpa: Allocator, value: u32) !void { try list.append(gpa, value); // Can fail - try map.put(gpa, value, list.items.len); // If this fails, list has orphan entry! + try map.put(gpa, value, list.items.len - 1); // If this fails, list has orphan entry! } // GOOD - reserve first, then mutate -fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Allocator, value: u32) !void { - // Phase 1: Reserve (fallible, but no mutation) +fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMapUnmanaged(u32, usize), gpa: Allocator, value: u32) !void { + if (map.contains(value)) return error.DuplicateItem; + const index = list.items.len; + + // Phase 1: Reserve. Logical contents are unchanged, but capacity may grow + // and existing element pointers may be invalidated. try list.ensureUnusedCapacity(gpa, 1); try map.ensureUnusedCapacity(gpa, 1); @@ -138,12 +147,13 @@ fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Al // Phase 3: Mutate (infallible) list.appendAssumeCapacity(value); - map.getOrPutAssumeCapacity(value).value_ptr.* = list.items.len; + map.putAssumeCapacityNoClobber(value, index); } ``` **Key methods:** -- `ensureUnusedCapacity(gpa, n)` - Reserve space for n more items (can fail, doesn't mutate) +- `ensureUnusedCapacity(gpa, n)` - Reserve space for n more items. It can fail + and can reallocate, but it does not append or remove logical elements. - `appendAssumeCapacity(item)` - Append without allocation (cannot fail, asserts capacity) - `appendSliceAssumeCapacity(items)` - Append slice without allocation @@ -160,6 +170,8 @@ var arr = std.BoundedArray(u8, 64){}; // NEW var buffer: [64]u8 = undefined; var arr = std.ArrayList(u8).initBuffer(&buffer); -// Note: Operations will panic if capacity exceeded -try arr.appendBounded(value); // returns error.OutOfMemory if full +// Bounded operations report capacity exhaustion. +try arr.appendBounded(value); // error.OutOfMemory if full +// Assume-capacity operations assert; allocator-taking operations are illegal +// for an initBuffer list. ``` diff --git a/references/std-ascii.md b/references/std-ascii.md index dabcdda..ce49b31 100644 --- a/references/std-ascii.md +++ b/references/std-ascii.md @@ -1,6 +1,9 @@ # std.ascii -7-bit ASCII character classification and manipulation. For Unicode handling, use `std.unicode`. +7-bit ASCII character classification and manipulation. Use `std.unicode` for +UTF encoding, decoding, validation, and codepoint iteration; it is not a +drop-in replacement for Unicode-aware classification, case conversion, or case +folding. ## Character Classification @@ -16,9 +19,11 @@ ascii.isHex('F') // A-F, a-f, 0-9 ascii.isUpper('A') // A-Z ascii.isLower('a') // a-z ascii.isWhitespace(' ') // space, \t, \n, \r, \v, \f -ascii.isPrint('!') // printable (not control) +ascii.isPrint('!') // printable 7-bit ASCII and not control ascii.isControl('\n') // control characters (0x00-0x1F, 0x7F) ascii.isAscii(c) // c < 128 +ascii.isGraphical('!') // printable ASCII excluding space +ascii.isPunctuation('!') // ASCII punctuation ``` ## Case Conversion @@ -29,16 +34,17 @@ ascii.toUpper('a') // 'A' ascii.toLower('A') // 'a' // Strings - to buffer -var buf: [100]u8 = undefined; -const lower = ascii.lowerString(&buf, "HeLLo"); // "hello" -const upper = ascii.upperString(&buf, "HeLLo"); // "HELLO" +var lower_buf: [100]u8 = undefined; +var upper_buf: [100]u8 = undefined; +const lower = ascii.lowerString(&lower_buf, "HeLLo"); // "hello" +const upper = ascii.upperString(&upper_buf, "HeLLo"); // "HELLO" // Strings - allocating -const lower = try ascii.allocLowerString(allocator, "HeLLo"); -defer allocator.free(lower); // "hello" +const allocated_lower = try ascii.allocLowerString(allocator, "HeLLo"); +defer allocator.free(allocated_lower); // "hello" -const upper = try ascii.allocUpperString(allocator, "HeLLo"); -defer allocator.free(upper); // "HELLO" +const allocated_upper = try ascii.allocUpperString(allocator, "HeLLo"); +defer allocator.free(allocated_upper); // "HELLO" ``` ## Case-Insensitive Comparison @@ -52,7 +58,7 @@ ascii.startsWithIgnoreCase("Hello World", "hello") // true ascii.endsWithIgnoreCase("Hello World", "WORLD") // true // Search -ascii.indexOfIgnoreCase("Hello World", "world") // ?usize = 6 +ascii.findIgnoreCase("Hello World", "world") // ?usize = 6 // Lexicographical order ascii.orderIgnoreCase("abc", "ABC") // .eq @@ -130,6 +136,7 @@ fn isAsciiString(s: []const u8) bool { ```zig // Use ascii.lowerString to normalize keys var buf: [64]u8 = undefined; +if (user_input.len > buf.len) return error.InputTooLong; const normalized = ascii.lowerString(&buf, user_input); if (map.get(normalized)) |value| { // found @@ -140,5 +147,7 @@ if (map.get(normalized)) |value| { - All functions handle bytes > 127 gracefully (return `false` for classification) - Functions use `u8` not `u7` for convenience -- For Unicode text, use `std.unicode` instead +- ASCII case/comparison helpers are not Unicode-aware; `std.unicode` covers UTF + encoding/decoding rather than locale-aware casing - `lowerString`/`upperString` assert output buffer is large enough +- Normalize stored keys and lookup keys with the same convention diff --git a/references/std-atomic.md b/references/std-atomic.md index 60afa6e..9f66131 100644 --- a/references/std-atomic.md +++ b/references/std-atomic.md @@ -19,8 +19,9 @@ Lock-free atomics do not need `std.Io`. Blocking synchronization should use `std ```zig std.atomic.Value(T) // Atomic wrapper for T (integers, enums, floats, bools, pointers) +std.atomic.Mutex // Non-blocking/spinning atomic mutex std.atomic.spinLoopHint() // CPU hint for spin-wait loops -std.atomic.cache_line // CPU cache line size (comptime constant) +std.atomic.cache_line // Target-based cache-line estimate (comptime constant) ``` ## Atomic Value Wrapper @@ -179,16 +180,16 @@ fn spinWait(flag: *std.atomic.Value(bool)) void { } ``` -Architecture-specific behavior: +Representative architecture-specific behavior (the implementation contains additional target/feature cases): - **x86/x86_64**: `pause` instruction - **AArch64**: `isb` instruction -- **ARM**: `yield` instruction (v6k+) +- **ARM**: feature-dependent yield/spin hint - **RISC-V**: `pause` (Zihintpause extension) -- **Others**: No-op +- **Some unsupported targets**: No-op ## Cache Line Size -`cache_line` is the CPU cache line size, used to prevent false sharing: +`cache_line` is a target-based estimate of cache-line size, useful when reducing false sharing. It is not a runtime query of the current CPU: ```zig const cache_line = std.atomic.cache_line; // 64, 128, etc. @@ -205,7 +206,7 @@ const ThreadCounters = struct { }; ``` -Typical values by architecture: +Representative values selected by the stdlib's target table (not a complete hardware guarantee): - x86_64, AArch64: 128 bytes (big cores) - ARM, MIPS: 32 bytes - Most others: 64 bytes @@ -300,18 +301,20 @@ fn Stack(comptime T: type) type { ```zig var initialized = std.atomic.Value(bool).init(false); -var init_mutex: std.Thread.Mutex = .{}; +var init_mutex: std.Io.Mutex = .init; var global_resource: ?*Resource = null; -fn getResource() *Resource { +fn getResource(io: std.Io) *Resource { // Fast path: already initialized if (initialized.load(.acquire)) { return global_resource.?; } // Slow path: initialize with lock - init_mutex.lock(); - defer init_mutex.unlock(); + // This variant deliberately makes initialization uncancelable. Use + // `try init_mutex.lock(io)` in a cancelable function instead. + init_mutex.lockUncancelable(io); + defer init_mutex.unlock(io); if (!initialized.load(.acquire)) { global_resource = initializeResource(); @@ -412,5 +415,5 @@ const Barrier = struct { ## See Also -- **[std.Thread](std-thread.md)** - Higher-level synchronization (Mutex, RwLock, Condition, Semaphore) -- **[std.Thread.Futex](std-thread.md)** - OS-level blocking primitives +- **[std.Io synchronization](std-io.md)** - Blocking `Mutex`, `RwLock`, `Condition`, `Semaphore`, `Event`, and futex methods +- **[std.Thread](std-thread.md)** - Thread creation, joining, and thread-local facilities diff --git a/references/std-base64.md b/references/std-base64.md index 9c3073a..83242e2 100644 --- a/references/std-base64.md +++ b/references/std-base64.md @@ -10,7 +10,7 @@ For examples that write encoded output to files/stdout, use `std.Io.Writer` and | Codec | Use Case | |-------|----------| -| `standard` | Standard Base64 with `=` padding (email, MIME) | +| `standard` | Standard RFC 4648 Base64 with `=` padding; MIME framing and line wrapping are separate | | `standard_no_pad` | Standard Base64 without padding | | `url_safe` | URL-safe Base64 with `=` padding | | `url_safe_no_pad` | URL-safe Base64 without padding (JWT, URLs) | @@ -63,7 +63,7 @@ const encoded = "SGVs bG8s\nIFdv cmxk IQ=="; // with spaces and newlines const decoder = base64.standard.decoderWithIgnore(" \n"); var buf: [100]u8 = undefined; -const max_size = try decoder.calcSizeUpperBound(encoded.len); +const max_size = decoder.calcSizeUpperBound(encoded.len); const decoded_len = try decoder.decode(buf[0..max_size], encoded); const decoded = buf[0..decoded_len]; // "Hello, World!" @@ -71,6 +71,8 @@ const decoded = buf[0..decoded_len]; ## Streaming Encoding +This fragment assumes a caller-provided `io: std.Io` and `data: []const u8`: + ```zig var buf: [4096]u8 = undefined; var writer = std.Io.File.stdout().writer(io, &buf); @@ -110,6 +112,7 @@ base64.standard.Decoder.decode(dest, source) catch |err| switch (err) { ### Encode binary data for JSON/URLs ```zig fn encodeForUrl(data: []const u8, buf: []u8) []const u8 { + std.debug.assert(buf.len >= std.base64.url_safe_no_pad.Encoder.calcSize(data.len)); return std.base64.url_safe_no_pad.Encoder.encode(buf, data); } ``` @@ -126,11 +129,12 @@ fn decodeJwtPayload(payload: []const u8, buf: []u8) ![]u8 { ### Handle multi-line Base64 (PEM format) ```zig -fn decodePem(pem_data: []const u8, buf: []u8) ![]u8 { - // Skip header/footer, decode with newline ignoring +fn decodePemBody(base64_body: []const u8, buf: []u8) ![]u8 { + // The caller must extract and validate the PEM header/footer first. This + // helper only decodes the Base64 body while ignoring line breaks. const decoder = std.base64.standard.decoderWithIgnore("\n\r"); - const max = try decoder.calcSizeUpperBound(pem_data.len); - const len = try decoder.decode(buf[0..max], pem_data); + const max = decoder.calcSizeUpperBound(base64_body.len); + const len = try decoder.decode(buf[0..max], base64_body); return buf[0..len]; } ``` @@ -141,4 +145,4 @@ fn decodePem(pem_data: []const u8, buf: []u8) ![]u8 { - URL-safe uses `-` and `_` which are safe in URLs - Padding (`=`) makes length divisible by 4 - `calcSizeForSlice` gives exact size; `calcSizeUpperBound` gives max (ignores padding) -- All codecs use little-endian byte order internally +- Base64 operates on byte slices; integer endianness is not part of its public contract diff --git a/references/std-bit-set.md b/references/std-bit-set.md index e064e06..c0d6b7d 100644 --- a/references/std-bit-set.md +++ b/references/std-bit-set.md @@ -13,9 +13,9 @@ Densely stored sets of integers with efficient set operations (union, intersecti | Type | Size | Allocation | |------|------|------------| -| `IntegerBitSet(N)` | Compile-time, N <= 128 | None (single integer) | +| `IntegerBitSet(N)` | Compile-time, backed by one integer of N bits | None (single integer) | | `ArrayBitSet(usize, N)` | Compile-time, any N | None (array) | -| `StaticBitSet(N)` | Compile-time | Auto-selects Integer or Array | +| `StaticBitSet(N)` | Compile-time | Uses Integer when N <= `@bitSizeOf(usize)`, otherwise Array | | `DynamicBitSet` | Runtime | Allocator (managed) | | `DynamicBitSetUnmanaged` | Runtime | Allocator (unmanaged) | @@ -27,7 +27,7 @@ const std = @import("std"); // StaticBitSet auto-selects best implementation const Flags = std.StaticBitSet(64); -var flags = Flags.initEmpty(); +var flags: Flags = .empty; flags.set(5); flags.set(10); @@ -52,7 +52,7 @@ bits.set(100); // Resize dynamically try bits.resize(2000, false); // false = new bits are 0 -try bits.resize(2000, true); // true = new bits are 1 +try bits.resize(2500, true); // true = newly added bits are 1 // Clone var copy = try bits.clone(allocator); @@ -62,8 +62,8 @@ defer copy.deinit(); ## Set Operations ```zig -var a = Flags.initEmpty(); -var b = Flags.initEmpty(); +var a: Flags = .empty; +var b: Flags = .empty; a.set(1); a.set(2); b.set(2); b.set(3); @@ -99,7 +99,7 @@ if (a.supersetOf(b)) { ## Iteration ```zig -var flags = Flags.initEmpty(); +var flags: Flags = .empty; flags.set(1); flags.set(5); flags.set(10); // Iterate set bits (ascending order by default) @@ -135,7 +135,7 @@ if (flags.findLastSet()) |index| { // index of highest set bit } -// Find and toggle (atomic-like) +// Find the first set bit and remove it from this set (not an atomic operation) if (flags.toggleFirstSet()) |index| { // returns index and unsets the bit } @@ -171,7 +171,7 @@ const Permission = enum(u8) { admin = 4, }; -const Permissions = std.StaticBitSet(8); +const Permissions = std.StaticBitSet(@typeInfo(Permission).@"enum".fields.len); fn hasPermission(perms: Permissions, p: Permission) bool { return perms.isSet(@intFromEnum(p)); @@ -186,14 +186,14 @@ fn revoke(perms: *Permissions, p: Permission) void { } pub fn main() void { - var user_perms = Permissions.initEmpty(); + var user_perms: Permissions = .empty; grant(&user_perms, .read); grant(&user_perms, .write); - var admin_perms = Permissions.initFull(); + const admin_perms: Permissions = .full; // Check if user has all admin permissions - if (user_perms.subsetOf(admin_perms)) { + if (admin_perms.subsetOf(user_perms)) { // user can do everything admin can (not in this case) } @@ -206,6 +206,6 @@ pub fn main() void { - `StaticBitSet` is zero-allocation, copyable by value - `DynamicBitSet` requires allocation, call `deinit()` -- `initFull()` creates set with all bits set +- `.full` creates a static set with all bits set; dynamic sets use their initialization functions - Iteration order is index order, not insertion order - Use `std.enums.EnumSet` for enum-based bit flags diff --git a/references/std-buf-map.md b/references/std-buf-map.md index db02007..1540dc2 100644 --- a/references/std-buf-map.md +++ b/references/std-buf-map.md @@ -17,7 +17,8 @@ const std = @import("std"); var map = std.BufMap.init(allocator); defer map.deinit(); // frees all stored strings -// Put (copies both key and value) +// Put copies a new key and value. Replacing an existing key retains the stored +// key allocation and copies only the new value. try map.put("HOME", "/Users/alice"); try map.put("PATH", "/usr/bin"); @@ -26,10 +27,8 @@ if (map.get("HOME")) |home| { std.debug.print("home: {s}\n", .{home}); } -// Get pointer (invalidated on resize) -if (map.getPtr("PATH")) |path_ptr| { - path_ptr.* = try map.copy("/new/path"); // update in place -} +// Replace through the public ownership-aware operation. +try map.put("PATH", "/new/path"); // Remove (frees both key and value) map.remove("PATH"); @@ -44,8 +43,12 @@ const n = map.count(); // putMove takes ownership instead of copying const key = try allocator.dupe(u8, "MY_KEY"); const value = try allocator.dupe(u8, "my_value"); -try map.putMove(key, value); -// Don't free key/value - map owns them now +map.putMove(key, value) catch |err| { + allocator.free(key); + allocator.free(value); + return err; +}; +// On success, the map owns both buffers. ``` ## BufMap: Iteration @@ -169,8 +172,13 @@ pub fn main() !void { ## Notes -- All strings are copied on insert/put, freed on remove/deinit -- Use `putMove` to transfer ownership instead of copying -- `get()` returns borrowed slice - don't store long-term -- Iteration order is not insertion order (hash map) +- A new insertion copies both strings. Replacing an existing key retains its + stored key and replaces the owned value. +- `putMove` transfers ownership only on success; on error the caller retains it. +- A slice from `get()` is invalidated by replacing/removing its key or + deinitializing the map. +- A pointer from `getPtr()` is invalidated by resize, removal of that entry, or + deinitialization. Replacing the value updates the existing slot. +- Iteration order is arbitrary; do not rely on insertion order, and do not + modify a `BufMap` or `BufSet` while an iterator is live. - For non-owning string maps, use `std.StringHashMap` diff --git a/references/std-build.md b/references/std-build.md index 00e680d..f60bf69 100644 --- a/references/std-build.md +++ b/references/std-build.md @@ -457,7 +457,7 @@ const zlib = b.dependency("zlib", .{ exe.root_module.addImport("zlib", zlib.module("zlib")); // Get artifact from dependency -exe.linkLibrary(zlib.artifact("z")); +exe.root_module.linkLibrary(zlib.artifact("z")); // Get path from dependency const include_path = zlib.path("include"); @@ -495,8 +495,8 @@ const dawn_dep = switch (target.result.os.tag) { if (dawn_dep) |dep| { // Dependency is available, use normally - exe.addLibraryPath(dep.path("lib")); - exe.linkSystemLibrary("dawn"); + exe.root_module.addLibraryPath(dep.path("lib")); + exe.root_module.linkSystemLibrary("dawn", .{}); } ``` @@ -542,7 +542,7 @@ run_step.dependOn(&run_cmd.step); const cmd = b.addSystemCommand(&.{ "git", "describe", "--tags" }); // Capture output -const version = cmd.captureStdOut(); +const version = cmd.captureStdOut(.{}); // Use output as file const version_file = b.addInstallFile(version, "version.txt"); @@ -705,7 +705,7 @@ const config_h = b.addConfigHeader(.{ .VERSION_STRING = "1.0.0", }); -exe.addConfigHeader(config_h); +exe.root_module.addConfigHeader(config_h); ``` ### Code Generation with Zig Tool @@ -729,9 +729,8 @@ exe.root_module.addAnonymousImport("schema", .{ ## C/C++ Integration -> **Note:** Compile-level methods like `exe.addCSourceFiles()`, `exe.linkSystemLibrary()`, -> `exe.addIncludePath()`, `exe.linkLibC()` are **deprecated** (to be removed after 0.15.0). -> Use `exe.root_module.*` equivalents shown below. +> **Note:** Zig 0.16 configures compilation and linking on the artifact's `root_module`. +> Older Compile-level methods are no longer the API shown here. ### Adding C Sources ```zig @@ -762,11 +761,11 @@ exe.root_module.linkSystemLibrary("pthread", .{}); exe.root_module.linkSystemLibrary("ssl", .{}); // Static library file -exe.addObjectFile(b.path("lib/libfoo.a")); +exe.root_module.addObjectFile(b.path("lib/libfoo.a")); // Library search path -exe.addLibraryPath(b.path("lib")); -exe.addRPath(b.path("lib")); +exe.root_module.addLibraryPath(b.path("lib")); +exe.root_module.addRPath(b.path("lib")); // Link libc (set via createModule options or directly) exe.root_module.link_libc = true; @@ -835,7 +834,7 @@ const cwd_path: std.Build.LazyPath = .{ .cwd_relative = "/absolute/path" }; exe.root_module.root_source_file = b.path("src/main.zig"); // As include path -exe.addIncludePath(dep.path("include")); +exe.root_module.addIncludePath(dep.path("include")); // Install b.installFile(generated_file, "share/output.txt"); @@ -849,16 +848,20 @@ LazyPath is central to how data flows between build steps. Understanding its var ```zig const LazyPath = union(enum) { // Path relative to build root (most common) - src_path: struct { root: ?*Build, sub_path: []const u8 }, + src_path: struct { owner: *Build, sub_path: []const u8 }, // Output from a build step (e.g., compiled binary, generated file) - generated: struct { file: *GeneratedFile, sub_path: ?[]const u8 }, + generated: struct { + file: *const GeneratedFile, + up: usize = 0, + sub_path: []const u8 = "", + }, // Absolute or CWD-relative path (use sparingly) cwd_relative: []const u8, // Path inside a dependency package - dependency: struct { dep: *Dependency, sub_path: []const u8 }, + dependency: struct { dependency: *Dependency, sub_path: []const u8 }, }; ``` @@ -902,10 +905,12 @@ const base = b.path("src/modules/parser"); const parent = base.dirname(); // "src/modules" // Concatenate subpath -const file = base.join("lexer.zig"); // "src/modules/parser/lexer.zig" +const file = try base.join(b.allocator, "lexer.zig"); +// Convenience form with the build allocator (panics on OOM): +const same_file = base.path(b, "lexer.zig"); // Chain operations -const sibling = base.dirname().join("utils/helpers.zig"); // "src/modules/utils/helpers.zig" +const sibling = try base.dirname().join(b.allocator, "utils/helpers.zig"); ``` ## Build Allocation @@ -1123,13 +1128,5 @@ fmt_step.dependOn(&fmt.step); ``` ### Clean Step -```zig -const clean_step = b.step("clean", "Clean build artifacts"); -clean_step.dependOn(&b.addRemoveDirTree(b.path("zig-out")).step); - -// Note: zig-cache deletion may fail on Windows while build is running -if (@import("builtin").os.tag != .windows) { - clean_step.dependOn(&b.addRemoveDirTree(b.path(".zig-cache")).step); -} -``` +Zig 0.16 does not provide `Build.addRemoveDirTree`. Prefer keeping generated outputs in normal build/install locations and clean them outside the running build graph. If a project adds an explicit platform-specific removal command, treat it as an external command with its own portability and concurrent-build hazards; do not delete the active cache from a build step. diff --git a/references/std-c.md b/references/std-c.md index 587e12c..9cbd127 100644 --- a/references/std-c.md +++ b/references/std-c.md @@ -31,13 +31,13 @@ Use `std.c` when: Prefer higher-level alternatives when available: ```zig // High-level (recommended) -const file = try std.fs.cwd().openFile("data.txt", .{}); +const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{}); -// POSIX-level -const fd = try std.posix.open("data.txt", .{}, 0); +// Checked OS-level wrappers live under std.posix on supported targets. Their +// signatures and flag types are target-aware and report Zig errors. -// C-level (direct libc, lowest level) -const fd = std.c.open("data.txt", .{}, 0); +// C-level std.c bindings are raw libc calls: check the return code and errno. +const fd = std.c.open("data.txt", flags, mode); ``` ## Fundamental Types @@ -94,21 +94,11 @@ c.pid_t // Process ID (platform-specific) ```zig const c = std.c; -// Platform-specific stat structure +// Stat is selected for the compilation target. Its field names and layout are +// not a portable record; inspect `c.Stat` for the selected target before direct +// field access, or prefer a higher-level std.Io API. const stat: c.Stat = undefined; -// Fields (vary by platform): -stat.dev // Device ID -stat.ino // Inode number -stat.mode // File mode -stat.nlink // Number of hard links -stat.uid // Owner UID -stat.gid // Owner GID -stat.size // File size in bytes -stat.atim // Last access time (timespec) -stat.mtim // Last modification time -stat.ctim // Last status change time -stat.blksize // Preferred block size -stat.blocks // Number of 512-byte blocks +_ = stat; ``` ### I/O Vectors @@ -273,10 +263,10 @@ c.sockaddr // Generic socket address .family // Address family (sa_family_t) .data // Address data -c.sockaddr_in // IPv4 address (from std.posix) -c.sockaddr_in6 // IPv6 address -c.sockaddr_un // Unix domain socket -c.sockaddr_storage // Large enough for any address +c.sockaddr.in // IPv4 address, when exported for the selected target +c.sockaddr.in6 // IPv6 address, when exported for the selected target +c.sockaddr.un // Unix domain socket, on applicable targets +c.sockaddr.storage // Generic storage, on applicable targets c.socklen_t // Socket address length type c.sa_family_t // Address family type @@ -307,10 +297,10 @@ c.SOCK // Socket types .CLOEXEC // Set close-on-exec .NONBLOCK // Non-blocking -c.SOL // Socket level for options - .SOCKET // Socket-level options - .IP, .IPV6 // IP-level options - .TCP, .UDP // Protocol-level options +c.SOL // Target-specific socket option levels + .SOCKET // Widely available socket-level option value + // Other members vary by target. Protocol numbers such as IP/TCP/UDP are + // not a portable generic `SOL` member set. c.SO // Socket options (SOL_SOCKET level) .REUSEADDR, .REUSEPORT @@ -455,8 +445,7 @@ c.MAP // mmap flags .SHARED // Share changes .PRIVATE // Private copy-on-write .FIXED // Use exact address - .ANONYMOUS // No file backing (Linux/BSD) - .ANON // Alias for ANONYMOUS + .ANONYMOUS // No file backing on targets that export this spelling .NORESERVE // Don't reserve swap .STACK // Stack mapping // Platform-specific flags @@ -714,6 +703,8 @@ c.port_event // Port event structure ## Libc Function Bindings +`std.c` declarations are selected by target OS, architecture, libc, and sometimes libc version. The names below are an orientation guide, not a universally callable inventory: verify that each declaration is non-`void` for the selected target and link libc when required. + ### File Operations ```zig c.close // Close file descriptor @@ -792,49 +783,47 @@ c.getcontext // Get current context (some platforms) ### darwin (macOS/iOS) ```zig -const darwin = std.c.darwin; // Internal, re-exported via std.c +const c = std.c; // Darwin declarations below are public direct re-exports. // Mach types and functions -darwin.mach_port_t -darwin.mach_task_self() -darwin.mach_msg() -darwin.mach_host_self() -darwin.mach_timebase_info() -darwin.mach_absolute_time() +c.mach_port_t +c.mach_task_self() +c.mach_msg() +c.mach_host_self() +c.mach_timebase_info() +c.mach_absolute_time() // Exception handling -darwin.EXC, darwin.EXCEPTION -darwin.task_set_exception_ports() -darwin.task_get_exception_ports() +c.EXC, c.EXCEPTION +c.task_set_exception_ports() +c.task_get_exception_ports() // Thread state -darwin.thread_state -darwin.thread_get_state() -darwin.thread_set_state() +c.thread_state +c.thread_get_state() +c.thread_set_state() // VM operations -darwin.mach_vm_read() -darwin.mach_vm_write() -darwin.mach_vm_protect() -darwin.mach_vm_region() +c.mach_vm_read() +c.mach_vm_write() +c.mach_vm_protect() +c.mach_vm_region() -// Dispatch/GCD semaphores -darwin.dispatch_semaphore_create() -darwin.dispatch_semaphore_wait() -darwin.dispatch_semaphore_signal() +// Public Dispatch/GCD aliases (only names exported by this namespace) +c.dispatch // Unfair locks -darwin.os_unfair_lock -darwin.os_unfair_lock_lock() -darwin.os_unfair_lock_unlock() +c.os_unfair_lock +c.os_unfair_lock_lock() +c.os_unfair_lock_unlock() // Process spawning -darwin.posix_spawn() -darwin.posix_spawn_file_actions_* +c.posix_spawn() +c.posix_spawn_file_actions_* // File copy -darwin.fcopyfile() -darwin.COPYFILE +c.fcopyfile() +c.COPYFILE ``` ### freebsd @@ -944,7 +933,7 @@ extern "c" fn c_function(fd: std.c.fd_t, buf: [*]u8, len: std.c.size_t) std.c.ss pub fn wrapper(fd: std.posix.fd_t, buf: []u8) !usize { const result = c_function(fd, buf.ptr, buf.len); if (result < 0) { - const err = std.posix.errno(std.c._errno().*); + const err: std.c.E = @enumFromInt(std.c._errno().*); return std.posix.unexpectedErrno(err); } return @intCast(result); @@ -963,7 +952,7 @@ fn platformSpecificCall() void { }, .macos, .ios => { // Darwin uses different types - const port = std.c.darwin.mach_port_t; + const port = std.c.mach_port_t; }, .windows => { // Windows uses HANDLE diff --git a/references/std-compress.md b/references/std-compress.md index 4ee1f34..23c826e 100644 --- a/references/std-compress.md +++ b/references/std-compress.md @@ -4,7 +4,7 @@ Compression and decompression algorithms. Zig 0.16 adds Deflate compression, sim Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html -Old examples below may use removed `std.io.GenericReader` or `fixedBufferStream` patterns. Translate them to `std.Io.Reader` / `std.Io.Writer` before using them in Zig 0.16 code. +The examples below use Zig 0.16's concrete `std.Io.Reader` / `std.Io.Writer` interfaces rather than removed generic-reader or fixed-buffer-stream patterns. ## Table of Contents - [Module Structure](#module-structure) @@ -80,13 +80,15 @@ var output: std.Io.Writer.Allocating = .init(allocator); defer output.deinit(); var buffer: [flate.max_window_len]u8 = undefined; -var compress: flate.Compress = .init(&output.writer, &buffer, .{ - .level = .default, - .container = .gzip, -}); +var compress: flate.Compress = try .init( + &output.writer, + &buffer, + .gzip, + .default, +); try compress.writer.writeAll(data); -try compress.end(); +try compress.finish(); const compressed = output.written(); ``` @@ -94,7 +96,10 @@ const compressed = output.written(); ### Compression Levels ```zig -const Level = enum { +const Options = struct { + // Options are parameter sets rather than an enum. Levels 1 through 9 are + // available; these public constants select common presets: + level_1: Options, level_4, // Fastest level_5, level_6, // Default @@ -102,7 +107,7 @@ const Level = enum { level_8, level_9, // Best compression - fast, // Alias for level_4 + fastest, // Alias for level_1 default, // Alias for level_6 best, // Alias for level_9 }; @@ -110,12 +115,7 @@ const Level = enum { ### Huffman-Only Compression -For faster compression without LZ77 match searching: - -```zig -const HuffmanEncoder = flate.HuffmanEncoder; -// Used internally for Huffman-only encoding (bigger output, faster compression) -``` +`flate.Compress.Huffman` exposes public initialization and a writer that skips LZ77 match searching. However, in the installed Zig 0.16 source its end-of-stream `finish` method is private. External code therefore cannot complete the container lifecycle through the public API alone. Treat it as an implementation-facing type in this release rather than a standalone archive-writing recipe; use `flate.Compress` when a complete public compression lifecycle is required. ## Zstandard @@ -171,38 +171,38 @@ LZMA decompression with streaming reader interface. ```zig const lzma = std.compress.lzma; -var decompress = try lzma.decompress(allocator, reader); +var decoder_buffer = try allocator.alloc(u8, 4096); +errdefer allocator.free(decoder_buffer); +var decompress: lzma.Decompress = try .initOptions( + &reader, + allocator, + decoder_buffer, + .{}, + 128 * 1024 * 1024, +); defer decompress.deinit(); -var buf: [4096]u8 = undefined; -while (true) { - const n = try decompress.read(&buf); - if (n == 0) break; - // Process buf[0..n] -} +_ = try decompress.reader.streamRemaining(&output.writer); ``` ### With Options ```zig -var decompress = try lzma.decompressWithOptions(allocator, reader, .{ - .memlimit = 128 * 1024 * 1024, // 128 MB memory limit -}); +var decompress: lzma.Decompress = try .initOptions( + &reader, + allocator, + decoder_buffer, + .{ .allow_incomplete = false }, + 128 * 1024 * 1024, +); ``` ### Decompress Type ```zig -pub fn Decompress(comptime ReaderType: type) type { - return struct { - pub const Reader = std.io.GenericReader(*Self, Error, read); - - pub fn init(allocator: Allocator, source: ReaderType, params: Params, memlimit: ?usize) !Self; - pub fn deinit(self: *Self) void; - pub fn reader(self: *Self) Reader; - pub fn read(self: *Self, output: []u8) Error!usize; - }; -} +// Decompress embeds `reader: std.Io.Reader` and owns the caller-supplied +// buffer after init. `deinit` frees that buffer unless `takeBuffer` first +// reclaims it. `initParams` and `initOptions` are the construction paths. ``` ## LZMA2 @@ -214,11 +214,12 @@ LZMA2 decompression (improved LZMA with better streaming support). ```zig const lzma2 = std.compress.lzma2; -var output = std.ArrayList(u8).empty; -defer output.deinit(allocator); - -var stream = std.io.fixedBufferStream(compressed_data); -try lzma2.decompress(allocator, stream.reader(), output.writer(allocator)); +var input: std.Io.Reader = .fixed(compressed_data); +var output: std.Io.Writer.Allocating = .init(allocator); +defer output.deinit(); +var decode = try lzma2.Decode.init(allocator); +defer decode.deinit(allocator); +_ = try decode.decompress(&input, &output); ``` ## XZ @@ -230,20 +231,17 @@ XZ format decompression (LZMA2 in a container with checksums). ```zig const xz = std.compress.xz; -var decompress = try xz.decompress(allocator, reader); +var decoder_buffer = try allocator.alloc(u8, 4096); +errdefer allocator.free(decoder_buffer); +var decompress: xz.Decompress = try .init(&reader, allocator, decoder_buffer); defer decompress.deinit(); -var buf: [4096]u8 = undefined; -while (true) { - const n = try decompress.read(&buf); - if (n == 0) break; - // Process buf[0..n] -} +_ = try decompress.reader.streamRemaining(&output.writer); ``` ### Check Types -XZ supports multiple integrity check types: +XZ parses these integrity-check identifiers, but Zig 0.16 does not implement full XZ block-check verification. Do not treat successful decoding as verification of CRC32, CRC64, or SHA-256 block checks: ```zig pub const Check = enum(u4) { @@ -312,23 +310,24 @@ fn decompressZstd(allocator: Allocator, compressed: []const u8) ![]u8 { ```zig fn decompressToFile( + io: std.Io, input_path: []const u8, output_path: []const u8, container: std.compress.flate.Container, ) !void { const flate = std.compress.flate; - const input_file = try std.fs.cwd().openFile(input_path, .{}); - defer input_file.close(); + const input_file = try std.Io.Dir.cwd().openFile(io, input_path, .{}); + defer input_file.close(io); - const output_file = try std.fs.cwd().createFile(output_path, .{}); - defer output_file.close(); + const output_file = try std.Io.Dir.cwd().createFile(io, output_path, .{}); + defer output_file.close(io); var input_buf: [4096]u8 = undefined; - var input_reader = input_file.reader(&input_buf); + var input_reader = input_file.reader(io, &input_buf); var output_buf: [4096]u8 = undefined; - var output_writer = output_file.writer(&output_buf); + var output_writer = output_file.writer(io, &output_buf); var decompress: flate.Decompress = .init(&input_reader.interface, container, &.{}); _ = try decompress.reader.streamRemaining(&output_writer.interface); @@ -437,7 +436,7 @@ pub const Error = error{ **DEFLATE (flate)**: - Decompression: gzip, zlib, raw deflate -- Compression: gzip, zlib, raw deflate (levels 4-9) +- Compression: gzip, zlib, raw deflate (levels 1-9) - Streaming with history buffer **Zstandard (zstd)**: @@ -453,5 +452,5 @@ pub const Error = error{ **XZ**: - Decompression only -- CRC32/CRC64/SHA256 integrity checks +- CRC32/CRC64/SHA256 check IDs are parsed; full block-check verification is incomplete - Multiple block support diff --git a/references/std-crypto.md b/references/std-crypto.md index c952745..e7fc2d1 100644 --- a/references/std-crypto.md +++ b/references/std-crypto.md @@ -114,9 +114,11 @@ Blake3.hash("data", &digest, .{}); var keyed: [Blake3.digest_length]u8 = undefined; Blake3.hash("data", &keyed, .{ .key = key }); -// Key derivation +// Key derivation uses the dedicated KDF mode, not keyed-hash options. var derived: [32]u8 = undefined; -Blake3.hash("material", &derived, .{ .context = "my app v1 key derivation" }); +var kdf = Blake3.initKdf("my app v1 key derivation", .{}); +kdf.update("material"); +kdf.final(&derived); ``` ### Blake2 @@ -149,7 +151,8 @@ Aes256Gcm.encrypt(&ciphertext, &tag, plaintext, associated_data, nonce, key); // Decryption var decrypted: [ciphertext.len]u8 = undefined; try Aes256Gcm.decrypt(&decrypted, &ciphertext, tag, associated_data, nonce, key); -// Returns error.AuthenticationFailed if tag doesn't verify +// Returns error.AuthenticationFailed if the tag doesn't verify. Treat the +// contents of `decrypted` as invalid and discard them on any failure. ``` Key constants: @@ -231,13 +234,13 @@ const hash = SipHash.hash(key, data); const Ed25519 = std.crypto.sign.Ed25519; // Generate key pair -const kp = Ed25519.KeyPair.generate(); +const kp = Ed25519.KeyPair.generate(io); // Sign message const sig = kp.sign(message, null); // Verify signature -try kp.public_key.verify(sig, message); +try sig.verify(message, kp.public_key); // Returns error.SignatureVerificationFailed on failure // Incremental signing (large messages) @@ -258,7 +261,7 @@ Key lengths: const EcdsaP256Sha256 = std.crypto.sign.ecdsa.EcdsaP256Sha256; // Generate key pair -const kp = EcdsaP256Sha256.KeyPair.generate(); +const kp = EcdsaP256Sha256.KeyPair.generate(io); // Sign const sig = try kp.sign(message, null); @@ -277,8 +280,8 @@ Available: `EcdsaP256Sha256`, `EcdsaP256Sha3_256`, `EcdsaP384Sha384`, `EcdsaP384 const X25519 = std.crypto.dh.X25519; // Generate key pairs for Alice and Bob -const alice = X25519.KeyPair.generate(); -const bob = X25519.KeyPair.generate(); +const alice = X25519.KeyPair.generate(io); +const bob = X25519.KeyPair.generate(io); // Compute shared secret const alice_shared = try X25519.scalarmult(alice.secret_key, bob.public_key); @@ -293,13 +296,13 @@ std.crypto.hash.sha2.Sha256.hash(&alice_shared, &key, .{}); ### ML-KEM (Post-Quantum) ```zig -const MlKem768 = std.crypto.kem.ml_kem.MlKem768; +const MLKem768 = std.crypto.kem.ml_kem.MLKem768; // Key generation -const kp = MlKem768.KeyPair.generate(); +const kp = MLKem768.KeyPair.generate(io); // Encapsulation (sender) -const encaps = kp.public_key.encaps(null); +const encaps = kp.public_key.encaps(io); const shared_secret = encaps.shared_secret; const ciphertext = encaps.ciphertext; @@ -308,7 +311,7 @@ const decaps_secret = try kp.secret_key.decaps(ciphertext); // shared_secret == decaps_secret ``` -Available: `MlKem512`, `MlKem768`, `MlKem1024` +Available: `MLKem512`, `MLKem768`, `MLKem1024` ## Key Derivation @@ -354,18 +357,23 @@ try argon2.kdf( .p = 4, // parallelism }, .argon2id, // mode: argon2i, argon2d, or argon2id + io, ); // Use preset parameters -try argon2.kdf(allocator, &hash, password, salt, argon2.Params.interactive_2id, .argon2id); +try argon2.kdf(allocator, &hash, password, salt, argon2.Params.interactive_2id, .argon2id, io); // PHC string format (for storage) var buf: [128]u8 = undefined; -const encoded = try argon2.strHash(password, salt, .interactive_2id, .argon2id, &buf); +const encoded = try argon2.strHash(password, .{ + .allocator = allocator, + .params = .interactive_2id, + .mode = .argon2id, +}, &buf, io); // Returns: "$argon2id$v=19$m=65536,t=3,p=4$..." // Verify PHC-encoded hash -try argon2.strVerify(encoded, password, null); +try argon2.strVerify(encoded, password, .{ .allocator = allocator }, io); ``` Parameter presets: @@ -400,11 +408,14 @@ try scrypt.kdf(allocator, &hash, password, salt, scrypt.Params.interactive); const bcrypt = std.crypto.pwhash.bcrypt; // Hash password -var hash: [bcrypt.hash_length]u8 = undefined; -try bcrypt.strHash(password, .{ .rounds = 10 }, &hash); +var hash: [128]u8 = undefined; +const hash_str = try bcrypt.strHash(password, .{ + .params = .owasp, + .encoding = .phc, +}, &hash, io); // Verify -try bcrypt.strVerify(hash_str, password); +try bcrypt.strVerify(hash_str, password, .{ .silently_truncate_password = false }); ``` ### PBKDF2 @@ -414,21 +425,21 @@ const pbkdf2 = std.crypto.pwhash.pbkdf2; const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256; var key: [32]u8 = undefined; -pbkdf2(HmacSha256, &key, password, salt, 100000); // 100k iterations +try pbkdf2(&key, password, salt, 100000, HmacSha256); // 100k iterations ``` ## Secure Random -Thread-local cryptographically secure PRNG: +Use the caller-provided `std.Io` entropy interface. `io.random` is the process CSPRNG and may use a less-secure seeding fallback on platforms where fresh system entropy is unavailable; use `io.randomSecure` when that fallback is unacceptable. ```zig -const random = std.crypto.random; - // Random bytes var key: [32]u8 = undefined; -random.bytes(&key); +io.random(&key); -// Random integers +// Adapt std.Io to the general std.Random convenience API. +var source: std.Random.IoSource = .{ .io = io }; +const random = source.interface(); const n = random.int(u64); const bounded = random.intRangeLessThan(u32, 0, 100); // [0, 100) @@ -535,10 +546,10 @@ Aes256Gcm.encrypt(&ct, &tag, pt, ad, nonce, key); ```zig // For symmetric keys var key: [32]u8 = undefined; -std.crypto.random.bytes(&key); +io.random(&key); // For asymmetric keys -const kp = std.crypto.sign.Ed25519.KeyPair.generate(); +const kp = std.crypto.sign.Ed25519.KeyPair.generate(io); ``` ### Nonce Management @@ -553,7 +564,7 @@ counter += 1; // Option 2: Random (safe with XChaCha's 24-byte nonce) const XChaCha = std.crypto.aead.chacha_poly.XChaCha20Poly1305; var nonce: [XChaCha.nonce_length]u8 = undefined; -std.crypto.random.bytes(&nonce); +io.random(&nonce); ``` ### Secure Password Storage @@ -563,11 +574,15 @@ const argon2 = std.crypto.pwhash.argon2; // Registration: hash and store var buf: [128]u8 = undefined; -const hash_str = try argon2.strHash(password, null, .interactive_2id, .argon2id, &buf); +const hash_str = try argon2.strHash(password, .{ + .allocator = allocator, + .params = .interactive_2id, + .mode = .argon2id, +}, &buf, io); // Store hash_str in database // Login: verify -argon2.strVerify(stored_hash, password, null) catch |err| { +argon2.strVerify(stored_hash, password, .{ .allocator = allocator }, io) catch |err| { if (err == error.PasswordVerificationFailed) { // Invalid password } diff --git a/references/std-debug.md b/references/std-debug.md index 1eaa2a6..4560e22 100644 --- a/references/std-debug.md +++ b/references/std-debug.md @@ -4,7 +4,7 @@ Debugging utilities: panic handling, assertions, stack traces, hex dumps, and va Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html -Zig 0.16 reworked debug information and expanded target support for segfault handling/unwinding. When debug output writes to stderr/stdout directly, use `std.Io.File.stderr().writer(io, &buf)` / `std.Io.File.stdout().writer(io, &buf)` rather than old `std.io` or `std.fs.File` APIs. +Zig 0.16 reworked debug information and expanded target support for segfault handling/unwinding. For application-owned output, create a writer from the caller's `std.Io` (for example `std.Io.File.stderr().writer(io, &buf)`). Low-level `std.debug.print` and stack-dump helpers intentionally use the configured debug I/O path instead. ## Quick Reference @@ -33,7 +33,7 @@ std.debug.print("loading...", .{}); ## Format Specifiers -Format string syntax: `{[arg]:[fill][alignment][width][.precision][specifier]}` +Format string syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}`. Named arguments use square brackets, for example `{[name]s}`. ### Type Specifiers @@ -99,11 +99,11 @@ std.debug.print("{x:.4}\n", .{@as(f32, 1.0)}); // "0x1.0000p0" std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a" // Named (with struct) -std.debug.print("{name}: {value}\n", .{ .name = "x", .value = 42 }); +std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 }); // Runtime width/precision -std.debug.print("{d:[width]}\n", .{ .width = 5, 42 }); -std.debug.print("{d:.[precision]}\n", .{ .precision = 2, 3.14159 }); +std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = 5 }); +std.debug.print("{[value]d:.[precision]}\n", .{ .value = 3.14159, .precision = 2 }); ``` ### Escape Braces @@ -177,47 +177,32 @@ Panic prints message + stack trace to stderr, then aborts. ```zig // Print current stack trace to stderr -std.debug.dumpCurrentStackTrace(null); +std.debug.dumpCurrentStackTrace(.{}); // Skip frames until this address -std.debug.dumpCurrentStackTrace(@returnAddress()); +std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); ``` ### Dump to Writer ```zig var buf: [4096]u8 = undefined; -var stderr = std.Io.File.stderr().writer(io, &buf); -try std.debug.dumpCurrentStackTraceToWriter(null, &stderr.interface); +var locked = try io.lockStderr(&buf, null); +defer io.unlockStderr(); +try std.debug.writeCurrentStackTrace(.{}, locked.terminal()); ``` ### Capture Stack Trace ```zig var addrs: [32]usize = undefined; -var trace: std.builtin.StackTrace = .{ - .instruction_addresses = &addrs, - .index = 0, -}; -std.debug.captureStackTrace(@returnAddress(), &trace); +const trace = std.debug.captureCurrentStackTrace(.{}, &addrs); // Later: print captured trace -std.debug.dumpStackTrace(trace); +std.debug.dumpStackTrace(&trace); ``` -### StackIterator - -Walk the stack manually: - -```zig -var it = std.debug.StackIterator.init(@returnAddress(), null); -defer it.deinit(); - -while (it.next()) |return_address| { - const addr = return_address -| 1; - std.debug.print("0x{x}\n", .{addr}); -} -``` +`StackUnwindOptions` can also carry a target-specific `cpu_context.Native` pointer and opt into unsafe fallback unwinding. The stack iterator itself is an implementation detail; use `captureCurrentStackTrace`, `writeCurrentStackTrace`, and `dumpCurrentStackTrace` as the public interface. ## Hex Dump @@ -229,13 +214,12 @@ std.debug.dumpHex(data); // Output: // 7fff5fbff8a0 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 00 01 02 Hello, World!... -// Dump to writer -var buf: [256]u8 = undefined; -var aw: std.io.Writer.Allocating = .init(allocator); -defer aw.deinit(); -try std.debug.dumpHexFallible(&aw.writer, .no_color, data); +// Fallible dump to an existing terminal abstraction +try std.debug.dumpHexFallible(terminal, data); ``` +`dumpHexFallible` accepts `std.Io.Terminal`, not a bare writer, because it uses terminal color/mode operations. Use `.no_color` when constructing a terminal for a non-terminal sink. + Output format: - Address (lowercase hex) - 16 bytes per line (uppercase hex) @@ -282,7 +266,7 @@ if (MyTrace.enabled) { std.debug.print("trace: {}", .{trace}); ``` -In release builds (`enabled = false`), all trace operations are no-ops with zero size. +The predefined `std.debug.Trace` is enabled only in Debug mode. A custom `ConfigurableTrace` follows its explicit `is_enabled` argument; when disabled, its operations are no-ops and its storage is zero-sized. ## SafetyLock @@ -304,8 +288,8 @@ fn checkNotLocked() void { } ``` -- In Debug/ReleaseSafe: actively tracks lock state -- In ReleaseFast/ReleaseSmall: all methods are no-ops +- `SafetyLock` follows runtime-safety mode: active in Debug and ReleaseSafe, and a no-op in ReleaseFast and ReleaseSmall. +- `Trace` has the separate Debug-only default described above. ## Source Location @@ -327,7 +311,7 @@ const unknown = SourceLocation.invalid; ```zig const Symbol = std.debug.Symbol; -// Symbol with resolved source location +// Resolved fields are optional because debug information may be incomplete. const sym: Symbol = .{ .name = "myFunction", .compile_unit_name = "main.zig", @@ -335,7 +319,7 @@ const sym: Symbol = .{ }; // Unknown symbol -const unknown: Symbol = .{}; // name = "???", compile_unit_name = "???" +const unknown: Symbol = .unknown; // all three fields are null ``` ## Segfault Handling @@ -356,23 +340,9 @@ const enabled = std.debug.default_enable_segfault_handler; **Note:** `maybeEnableSegfaultHandler()` is called automatically by the runtime if `std.options.enable_segfault_handler` is true. -## Thread Context +## CPU Context Unwinding -Platform-specific CPU register state for stack unwinding: - -```zig -const ThreadContext = std.debug.ThreadContext; - -var ctx: ThreadContext = undefined; -if (std.debug.getContext(&ctx)) { - // ctx now contains register state - std.debug.dumpStackTraceFromBase(&ctx, stderr); -} - -// Copy context (handles internal pointers) -var ctx_copy: ThreadContext = undefined; -std.debug.copyContext(&original_ctx, &ctx_copy); -``` +Signal handlers that already receive a native CPU context can pass it through `std.debug.StackUnwindOptions.context` to `captureCurrentStackTrace`, `writeCurrentStackTrace`, or `dumpCurrentStackTrace`. There are no public `std.debug.ThreadContext`, `getContext`, `copyContext`, or `dumpStackTraceFromBase` helpers in Zig 0.16; native context capture is target- and signal-handler-specific under `std.debug.cpu_context`. ## Valgrind Detection @@ -389,28 +359,22 @@ if (std.debug.inValgrind()) { // Get debug info for current executable const info = try std.debug.getSelfDebugInfo(); -// Get symbol at address -const symbol = try info.getSymbolAtAddress(allocator, address); -defer if (symbol.source_location) |sl| allocator.free(sl.file_name); - -std.debug.print("{s}:{d}: {s}\n", .{ - symbol.source_location.?.file_name, - symbol.source_location.?.line, - symbol.name, -}); +// Public methods are target-specific through SelfInfo. A broadly available +// operation is resolving the owning module name; returned storage is owned by +// SelfInfo rather than by the caller. +const module_name = try info.getModuleName(io, address); +std.debug.print("module: {s}\n", .{module_name}); ``` ## Constants ```zig // Whether runtime safety checks are enabled -std.debug.runtime_safety // true in Debug/ReleaseSafe +std.debug.runtime_safety // deprecated; reflects stdlib mode, not necessarily the caller's module // Whether platform can produce stack traces std.debug.sys_can_stack_trace // false on WASM, MIPS, etc. -// Whether platform has ucontext_t -std.debug.have_ucontext ``` ## Submodules @@ -420,8 +384,8 @@ std.debug.have_ucontext | `std.debug.Dwarf` | DWARF debug info parser | | `std.debug.Pdb` | Windows PDB debug info parser | | `std.debug.SelfInfo` | Debug info for current executable | -| `std.debug.MemoryAccessor` | Safe memory access for unwinding | | `std.debug.Coverage` | Code coverage support | +| `std.debug.cpu_context` | Target-specific native CPU context definitions | ## FullPanic @@ -447,24 +411,22 @@ fn myPanicFn(msg: []const u8, ret_addr: ?usize) noreturn { For multi-line debug output without interleaving: ```zig -// Lock stderr and clear any progress indicators -std.debug.lockStdErr(); -defer std.debug.unlockStdErr(); +// Lock stderr and clear any progress indicators. The returned object exposes +// a file writer and terminal; unlock performs the final flush automatically. +var lock_buf: [256]u8 = undefined; +const locked = std.debug.lockStderr(&lock_buf); +defer std.debug.unlockStderr(); // Safe to write multiple lines -var buf: [256]u8 = undefined; -var stderr = std.Io.File.stderr().writer(io, &buf); -try stderr.interface.writeAll("Line 1\n"); -try stderr.interface.writeAll("Line 2\n"); -try stderr.interface.flush(); +try locked.file_writer.interface.writeAll("Line 1\n"); +try locked.file_writer.interface.writeAll("Line 2\n"); ``` -Or with a writer: +The matching function is spelled `unlockStderr`: ```zig var buf: [256]u8 = undefined; -const writer = std.debug.lockStderrWriter(&buf); -defer std.debug.unlockStderrWriter(); - -try writer.print("Complex output: {}\n", .{value}); +const locked = std.debug.lockStderr(&buf); +defer std.debug.unlockStderr(); +try locked.file_writer.interface.print("Complex output: {}\n", .{value}); ``` diff --git a/references/std-enums.md b/references/std-enums.md index b46bf9d..874dc16 100644 --- a/references/std-enums.md +++ b/references/std-enums.md @@ -13,8 +13,8 @@ const Color = enum { red, green, blue, yellow }; const ColorSet = std.enums.EnumSet(Color); // Initialize -var colors = ColorSet.initEmpty(); -var all = ColorSet.initFull(); +var colors: ColorSet = .empty; +const all: ColorSet = .full; // Struct-style init var primary = ColorSet.init(.{ @@ -106,7 +106,7 @@ if (map.get(.red)) |value| { } // Get with default -const value = map.getOrDefault(.blue, 0); +const value = map.get(.blue) orelse 0; // Get pointer if (map.getPtr(.red)) |ptr| { @@ -132,10 +132,10 @@ while (it.next()) |entry| { std.debug.print("{}: {}\n", .{ entry.key, entry.value.* }); } -// Iterate keys only -var key_it = map.keyIterator(); -while (key_it.next()) |key| { - std.debug.print("{}\n", .{key}); +// Iterate keys only by ignoring each entry's value pointer +var key_it = map.iterator(); +while (key_it.next()) |entry| { + std.debug.print("{}\n", .{entry.key}); } ``` @@ -171,8 +171,11 @@ for (std.enums.values(Color)) |color| { std.debug.print("{}: {}\n", .{ color, rgb.get(color) }); } -// Direct slice access -const slice = rgb.values; // [3]u32 +// Public sequential access uses the iterator. +var rgb_it = rgb.iterator(); +while (rgb_it.next()) |entry| { + std.debug.print("{}: {}\n", .{ entry.key, entry.value.* }); +} ``` ## EnumIndexer @@ -191,7 +194,7 @@ const count = Indexer.count; // 3 ```zig // Get all values as slice -const colors = std.enums.values(Color); // [3]Color +const colors = std.enums.values(Color); // []const Color // Safe tag name (works with non-exhaustive) const name = std.enums.tagName(Color, .red); // "red" or null @@ -244,7 +247,7 @@ fn canAccess(user: User, required: Permissions) bool { pub fn main() void { const admin = User{ .name = "admin", - .perms = Permissions.initFull(), + .perms = .full, }; const reader = User{ @@ -293,8 +296,8 @@ pub fn main() void { ## Notes - `EnumSet`: Bit-backed, use for presence tracking -- `EnumMap`: Sparse, only stores present values +- `EnumMap`: Fixed-size dense value storage plus a presence bitset - `EnumArray`: Dense, all values always present - All are fixed-size, zero-allocation, copyable by value - Use `std.StaticBitSet` for non-enum integer sets -- Works with non-exhaustive enums (explicit fields only) +- Exhaustive sparse enums are remapped densely. For non-exhaustive enums, index-based containers span the representable tag range rather than only explicit fields, which can make them impractically large; check each container's `EnumIndexer` behavior before use. diff --git a/references/std-fmt.md b/references/std-fmt.md index 7b351bc..29f8742 100644 --- a/references/std-fmt.md +++ b/references/std-fmt.md @@ -9,7 +9,7 @@ Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/releas - `std.fmt.format` is replaced by `std.Io.Writer.print`. - `std.fmt.Formatter` is renamed to `std.fmt.Alt`. - `std.fmt.FormatOptions` is renamed to `std.fmt.Options`. -- `std.fmt.bufPrintZ` is renamed to `std.fmt.bufPrintSentinel`. +- `std.fmt.bufPrintZ` remains as a deprecated compatibility wrapper; use `std.fmt.bufPrintSentinel`. - The `{D}` duration specifier was removed; format `std.Io.Duration` with `{f}`. ```zig @@ -38,13 +38,13 @@ pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { ## Format String Syntax -Full syntax: `{[arg]:[fill][alignment][width][.precision][specifier]}` +Full syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}` ### Components | Component | Description | Example | |-----------|-------------|---------| -| `arg` | Argument index or name | `{0}`, `{name}` | +| `argument` | Argument index or bracketed name | `{0}`, `{[name]s}` | | `fill` | Padding character | `{:0>5}` uses `0` | | `alignment` | `<` left, `^` center, `>` right | `{:<10}` | | `width` | Minimum field width | `{:10}` | @@ -63,14 +63,14 @@ std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a" ### Named Arguments ```zig -std.debug.print("{name}: {value}\n", .{ .name = "x", .value = 42 }); +std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 }); ``` ### Runtime Width/Precision ```zig -std.debug.print("{d:[width]}\n", .{ .width = @as(usize, 8), 42 }); -std.debug.print("{d:.[prec]}\n", .{ .prec = @as(usize, 2), 3.14159 }); +std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = @as(usize, 8) }); +std.debug.print("{[value]d:.[prec]}\n", .{ .value = 3.14159, .prec = @as(usize, 2) }); ``` ### Escape Braces @@ -81,7 +81,9 @@ std.debug.print("{{literal}}\n", .{}); // "{literal}" ## Format Specifiers -### Type Specifiers +### Selected Type Specifiers + +This table lists common specifiers; it is not a complete substitute for `std.Io.Writer`'s type-specific formatting rules. | Specifier | Types | Output | |-----------|-------|--------| @@ -171,7 +173,8 @@ const e = try std.fmt.parseInt(i32, "0b101", 0); // 5 (binary) const f = try std.fmt.parseInt(i32, "0o17", 0); // 15 (octal) const g = try std.fmt.parseInt(i32, "42", 0); // 42 (decimal) -// Underscores allowed between digits +// Underscores are ignored within the digit sequence. Leading/trailing +// underscores and underscores immediately after a base prefix are invalid. const h = try std.fmt.parseInt(u32, "1_000_000", 10); // 1000000 const i = try std.fmt.parseInt(u32, "0xff_ff", 0); // 65535 ``` @@ -306,19 +309,19 @@ const result = try std.fmt.bufPrint(&buf, "Hello {s}!", .{"world"}); **Errors:** - `error.NoSpaceLeft` - Buffer too small -### bufPrintZ +### bufPrintSentinel Format into buffer with null terminator. ```zig var buf: [256]u8 = undefined; -const result = try std.fmt.bufPrintZ(&buf, "Hello {s}!", .{"world"}); +const result = try std.fmt.bufPrintSentinel(&buf, "Hello {s}!", .{"world"}, 0); // result is [:0]u8 = "Hello world!" (null-terminated) ``` ### count -Count characters needed for format (without allocating). +Count output bytes needed for the format (without allocating). ```zig const len = std.fmt.count("Value: {d}, Name: {s}", .{ 42, "test" }); diff --git a/references/std-fs.md b/references/std-fs.md index 88745a0..87931e0 100644 --- a/references/std-fs.md +++ b/references/std-fs.md @@ -4,6 +4,10 @@ Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.h Zig 0.16 migrates file-system operations to `std.Io`. Use `std.Io.Dir`, `std.Io.File`, and an explicit `std.Io` parameter. `std.fs` is now mostly path helpers and deprecated compatibility names. +The snippets below are focused fragments. They assume `const std = +@import("std")`, a caller-supplied `io: std.Io`, a suitable allocator, and any +named application values such as `max_size`, `from`, and `to`. + ## Core Types ```zig @@ -41,7 +45,10 @@ const file = try std.Io.Dir.cwd().createFile(io, "out.txt", .{}); defer file.close(io); ``` -Common options remain conceptually similar: truncate, exclusive create, read access, mode/permissions, and locking where supported. +With default options, `createFile` truncates an existing regular file. Use the +exclusive-create option when overwriting must fail. Other options cover read +access, `permissions`, and locking where supported. File access mode is an +`openFile` option, not the name of the creation-permissions field. ## Reading Files @@ -68,6 +75,9 @@ defer allocator.free(bytes); ``` The limit uses `std.Io.Limit`. Hitting the limit returns `error.StreamTooLong`. +`readFileAlloc` creates a file reader internally and reads until the supplied +limit. Use an explicit `File.Reader` when streaming, reusing buffers, or +controlling incremental consumption. ### Read To End From Existing File @@ -130,14 +140,16 @@ if (try stdin_reader.interface.takeDelimiter('\n')) |line| { ## Directories ```zig -var dir = try std.Io.Dir.cwd().openDir(io, "assets", .{}); +var dir = try std.Io.Dir.cwd().openDir(io, "assets", .{ .iterate = true }); defer dir.close(io); try std.Io.Dir.cwd().createDir(io, "new-dir", .default_dir); try std.Io.Dir.cwd().createDirPath(io, "path/to/nested"); ``` -Use `openDir` options for iteration/access/no-follow behavior as needed. +Both walking APIs require a directory opened with `.iterate = true`; iterating +without that capability is illegal behavior. Use other `openDir` options for +access/no-follow behavior as needed. ## Walking @@ -190,7 +202,10 @@ try file.setTimestamps(io, .{ `std.Io.Dir.path` / `std.fs.path` functions handle Windows paths more consistently in 0.16. -`relative`, `relativeWindows`, and `relativePosix` are pure: pass the current directory and optional environment map instead of letting the function query the OS. +The relative-path helpers are pure and receive the current directory rather +than querying the OS. `relative` and `relativeWindows` also accept an optional +environment map for Windows per-drive current-directory resolution; +`relativePosix` has a distinct signature without that map. ```zig const cwd_path = try std.process.currentPathAlloc(io, allocator); @@ -202,7 +217,10 @@ defer allocator.free(rel); ## Atomic Files -Use `std.Io.File.Atomic` or directory atomic helpers instead of hand-rolled random temporary names. The 0.16 implementation is routed through `std.Io`, including entropy. +Obtain a `std.Io.File.Atomic` value through `Dir.createFileAtomic`, then use its +public cleanup/materialization lifecycle. Do not construct `File.Atomic` +directly or hand-roll random temporary names; the helper routes entropy and +filesystem work through `std.Io`. ## Absolute Operations diff --git a/references/std-hash.md b/references/std-hash.md index 49b5f08..3c276ce 100644 --- a/references/std-hash.md +++ b/references/std-hash.md @@ -68,7 +68,7 @@ const h7 = hash.CityHash64.hash("hello world"); ### Streaming/Incremental Hashing -All hashers support incremental updates for large or streaming data: +Hashers that expose `init`/`update`/`final` support incremental input. Some checksum types have a simpler state shape, and some algorithms expose only one-shot helpers; check the selected type rather than assuming a uniform interface. ```zig const std = @import("std"); @@ -101,7 +101,7 @@ const h3 = fnv.final(); ## Auto-Hashing (Generic Types) -`std.hash.autoHash` automatically hashes any Zig type: +`std.hash.autoHash` hashes eligible Zig value types. It intentionally rejects ambiguous categories such as slices unless the caller selects an explicit pointer strategy, and some type categories are unsupported. ```zig const std = @import("std"); @@ -117,7 +117,7 @@ fn hashPoint(p: Point) u64 { return hasher.final(); } -// Works with any hashable type +// Works with types accepted by autoHash; slices/pointers require deliberate handling. fn hashAny(value: anytype) u64 { var hasher = std.hash.Wyhash.init(0); std.hash.autoHash(&hasher, value); @@ -141,7 +141,7 @@ const Strategy = std.hash.Strategy; var hasher = std.hash.Wyhash.init(0); const data: []const u8 = "hello"; -// Shallow: hash pointer address only (default for autoHash) +// Shallow slice hashing includes its pointer and length, not its contents. std.hash.autoHashStrat(&hasher, data, .Shallow); // Deep: follow pointer, hash contents (one level) @@ -153,7 +153,7 @@ std.hash.autoHashStrat(&hasher, data, .DeepRecursive); | Strategy | Behavior | |----------|----------| -| `.Shallow` | Hash pointer address, not contents | +| `.Shallow` | Hash a one-item pointer's address; for a slice, hash pointer and length, not elements | | `.Deep` | Follow pointer one level, hash contents | | `.DeepRecursive` | Follow all pointers, hash all contents | @@ -240,14 +240,14 @@ const Adler32 = std.hash.Adler32; const checksum = Adler32.hash("data"); // Streaming -var adler = Adler32.init(); +var adler: Adler32 = .{}; adler.update("data"); -const result = adler.final(); +const result = adler.adler; ``` ## Using with HashMap -HashMap uses `std.hash.autoHash` by default: +`AutoHashMap` uses the stdlib's automatic hashing context. `StringHashMap` uses a string-specific context, while custom `HashMap` users provide their own context: ```zig const std = @import("std"); @@ -409,8 +409,13 @@ const std = @import("std"); // Requires 128-bit key const key: [16]u8 = .{0} ** 16; -const h64 = std.hash.SipHash64(2, 4).hash(&key, "data"); -const h128 = std.hash.SipHash128(2, 4).hash(&key, "data"); +var sip64 = std.hash.SipHash64(2, 4).init(&key); +sip64.update("data"); +const h64 = sip64.finalInt(); + +var sip128 = std.hash.SipHash128(2, 4).init(&key); +sip128.update("data"); +const h128 = sip128.finalInt(); // Default parameters (2-4 rounds) const SipHash = std.hash.SipHash64(2, 4); @@ -440,17 +445,19 @@ fn hashPair(comptime T: type, a: T, b: T) u64 { ### File Checksum ```zig -fn checksumFile(path: []const u8) !u32 { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); +fn checksumFile(io: std.Io, path: []const u8) !u32 { + const file = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer file.close(io); - var crc = std.hash.Crc32.init(); - var buf: [4096]u8 = undefined; + var crc: std.hash.Crc32 = .init(); + var reader_buf: [4096]u8 = undefined; + var chunk: [4096]u8 = undefined; + var reader = file.reader(io, &reader_buf); while (true) { - const n = try file.read(&buf); + const n = try reader.interface.readSliceShort(&chunk); if (n == 0) break; - crc.update(buf[0..n]); + crc.update(chunk[0..n]); } return crc.final(); @@ -460,15 +467,13 @@ fn checksumFile(path: []const u8) !u32 { ### Bloom Filter Hash ```zig -fn bloomHashes(data: []const u8, k: usize) []u64 { - var hashes: [16]u64 = undefined; +fn bloomHashes(data: []const u8, hashes: []u64) void { const h1 = std.hash.Wyhash.hash(0, data); const h2 = std.hash.Wyhash.hash(h1, data); - for (0..k) |i| { - hashes[i] = h1 +% @as(u64, i) *% h2; + for (hashes, 0..) |*hash, i| { + hash.* = h1 +% @as(u64, i) *% h2; } - return hashes[0..k]; } ``` diff --git a/references/std-hashmap.md b/references/std-hashmap.md index e62952e..1b76a25 100644 --- a/references/std-hashmap.md +++ b/references/std-hashmap.md @@ -13,9 +13,10 @@ std.AutoHashMapUnmanaged(KeyType, ValueType) // no stored allocator std.StringHashMap(ValueType) std.StringHashMapUnmanaged(ValueType) -// ArrayHashMap - preserves insertion order, fast iteration -std.ArrayHashMap(K, V, Context, store_hash) -std.StringArrayHashMap(V) +// ArrayHashMap - Zig 0.16 unmanaged ordered maps +std.array_hash_map.Auto(K, V) +std.array_hash_map.String(V) +std.array_hash_map.Custom(K, V, Context, store_hash) ``` ## AutoHashMap Usage @@ -54,7 +55,7 @@ const n = map.count(); ## Unmanaged Variant ```zig -// No stored allocator - pass to each method +// No stored allocator - pass it to operations that allocate or release memory var map: std.AutoHashMapUnmanaged(u32, []const u8) = .empty; defer map.deinit(allocator); @@ -98,12 +99,16 @@ while (iter.next()) |entry| { } // Keys only -for (map.keys()) |key| { } +var keys = map.keyIterator(); +while (keys.next()) |key_ptr| { _ = key_ptr.*; } // Values only -for (map.values()) |value| { } +var values = map.valueIterator(); +while (values.next()) |value_ptr| { _ = value_ptr.*; } ``` +Any modification invalidates iterators. Growth or rehashing can invalidate returned key/value pointers; removal immediately invalidates the removed entry's pointers. + ## Capacity ```zig @@ -138,11 +143,11 @@ var map = std.HashMap(MyKey, Value, Context, 80).initContext(allocator, context) Preserves insertion order, supports indexed access: ```zig -var map = std.StringArrayHashMap(i32).init(allocator); -defer map.deinit(); +var map: std.array_hash_map.String(i32) = .empty; +defer map.deinit(allocator); -try map.put("b", 2); -try map.put("a", 1); +try map.put(allocator, "b", 2); +try map.put(allocator, "a", 1); // Iterate in insertion order: "b", "a" for (map.keys(), map.values()) |k, v| { } @@ -152,10 +157,10 @@ const key = map.keys()[0]; // "b" const val = map.values()[0]; // 2 // Swap remove (O(1) but changes order) -map.swapRemove("b"); +_ = map.swapRemove("b"); // Ordered remove (O(n) but preserves order) -map.orderedRemove("a"); +_ = map.orderedRemove("a"); ``` ## Common Patterns @@ -174,8 +179,15 @@ for (words) |word| { // Cache with owned keys var cache = std.StringHashMap(Data).init(allocator); +defer cache.deinit(); +defer { + var owned_keys = cache.keyIterator(); + while (owned_keys.next()) |key_ptr| allocator.free(key_ptr.*); +} // When inserting, dupe the key if needed: const key_copy = try allocator.dupe(u8, external_key); errdefer allocator.free(key_copy); try cache.put(key_copy, data); ``` + +On removal, free the duplicated key returned by `fetchRemove`. Before clearing or deinitializing the map, iterate and free every remaining owned key as above; the map does not own string storage automatically. diff --git a/references/std-http.md b/references/std-http.md index 73e6c4e..3d5134d 100644 --- a/references/std-http.md +++ b/references/std-http.md @@ -14,7 +14,7 @@ var client: std.http.Client = .{ defer client.deinit(); ``` -Older examples below may still show 0.15 client construction. Add `.io = io` and prefer `std.Io.net` patterns in new code. +The client borrows both `allocator` and `io`; keep them valid through `client.deinit()`. ## Table of Contents - [HTTP Client](#http-client) @@ -30,12 +30,13 @@ Older examples below may still show 0.15 client construction. Add `.io = io` and ```zig const std = @import("std"); -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); - var client: std.http.Client = .{ .allocator = allocator }; + const io = init.io; + var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); // Simple GET - response body discarded @@ -49,7 +50,7 @@ pub fn main() !void { ### Fetch with Response Body ```zig -var client: std.http.Client = .{ .allocator = allocator }; +var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); // Create writer to capture response @@ -84,7 +85,7 @@ const result = try client.fetch(.{ For more control over the request lifecycle: ```zig -var client: std.http.Client = .{ .allocator = allocator }; +var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); const uri = try std.Uri.parse("https://api.example.com/resource"); @@ -135,7 +136,13 @@ defer req.deinit(); const body = "request body content"; try req.sendBodyComplete(@constCast(body)); -// Or for streaming: +``` + +Streaming is a separate request lifecycle; do not call both body-send forms on one request: + +```zig +var req = try client.request(.POST, uri, .{}); +defer req.deinit(); req.transfer_encoding = .{ .content_length = body.len }; var body_writer_buf: [1024]u8 = undefined; var bw = try req.sendBody(&body_writer_buf); @@ -212,7 +219,7 @@ std.debug.print("Final URL: {s}\n", .{req.uri.path.raw}); Connections are automatically pooled and reused: ```zig -var client: std.http.Client = .{ .allocator = allocator }; +var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); // Configure pool size (default 32) @@ -226,20 +233,25 @@ client.write_buffer_size = 2048; // default 1024 for (0..10) |_| { var req = try client.request(.GET, uri, .{ .keep_alive = true }); defer req.deinit(); - // ... same connection reused + try req.sendBodiless(); + var response = try req.receiveHead(&.{}); + var discard_buf: [1024]u8 = undefined; + _ = try response.reader(&discard_buf).discardRemaining(); + // req.deinit() returns an eligible keep-alive connection to the pool. } ``` ### Proxy Configuration ```zig -var client: std.http.Client = .{ .allocator = allocator }; +var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); // Load from environment (HTTP_PROXY, HTTPS_PROXY, etc.) var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); -try client.initDefaultProxies(arena.allocator()); +// `environ_map` and the arena-backed proxy strings must outlive the client. +try client.initDefaultProxies(arena.allocator(), &environ_map); // Or configure manually: var proxy: std.http.Proxy = .{ @@ -255,15 +267,15 @@ client.http_proxy = &proxy; ### TLS Configuration ```zig -var client: std.http.Client = .{ .allocator = allocator }; +var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); // TLS is enabled by default for https:// // Configure TLS buffer size (affects memory usage) client.tls_buffer_size = std.crypto.tls.Client.min_buffer_len; -// Force certificate rescan on next HTTPS request -client.next_https_rescan_certs = true; +// Force time/root-certificate freshness to be reconsidered on the next HTTPS request. +client.now = null; // Disable TLS at compile time via std.options.http_disable_tls ``` @@ -274,25 +286,26 @@ client.next_https_rescan_certs = true; ```zig const std = @import("std"); -const net = std.net; +const net = std.Io.net; const http = std.http; -pub fn main() !void { - const address = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080); - var tcp_server = try address.listen(.{}); - defer tcp_server.deinit(); +pub fn main(init: std.process.Init) !void { + const io = init.io; + const address = try net.IpAddress.parseIp4("127.0.0.1", 8080); + var tcp_server = try address.listen(io, .{}); + defer tcp_server.deinit(io); while (true) { - const conn = try tcp_server.accept(); - defer conn.stream.close(); + const conn = try tcp_server.accept(io); + defer conn.close(io); var read_buf: [8192]u8 = undefined; var write_buf: [4096]u8 = undefined; - var reader = conn.stream.reader(&read_buf); - var writer = conn.stream.writer(&write_buf); + var reader = conn.reader(io, &read_buf); + var writer = conn.writer(io, &write_buf); - var server = http.Server.init(reader.interface(), &writer.interface); + var server = http.Server.init(&reader.interface, &writer.interface); const request = server.receiveHead() catch |err| { std.debug.print("Failed to receive: {}\n", .{err}); @@ -479,7 +492,7 @@ const Method = enum { GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH, pub fn requestHasBody(m: Method) bool; // POST, PUT, PATCH - pub fn responseHasBody(m: Method) bool; // GET, POST, DELETE, CONNECT, OPTIONS, PATCH + pub fn responseHasBody(m: Method) bool; // GET, POST, PUT, DELETE, CONNECT, OPTIONS, PATCH pub fn safe(m: Method) bool; // GET, HEAD, OPTIONS, TRACE pub fn idempotent(m: Method) bool; // GET, HEAD, PUT, DELETE, OPTIONS, TRACE pub fn cacheable(m: Method) bool; // GET, HEAD @@ -569,8 +582,8 @@ const Header = struct { ### JSON API Client ```zig -fn fetchJson(comptime T: type, allocator: Allocator, url: []const u8) !T { - var client: std.http.Client = .{ .allocator = allocator }; +fn fetchJson(comptime T: type, io: std.Io, allocator: Allocator, url: []const u8) !std.json.Parsed(T) { + var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); var body_buf: [65536]u8 = undefined; @@ -586,19 +599,22 @@ fn fetchJson(comptime T: type, allocator: Allocator, url: []const u8) !T { if (result.status != .ok) return error.HttpError; - const parsed = try std.json.parseFromSlice(T, allocator, body_writer.buffered(), .{}); - return parsed.value; + // alloc_always prevents returned strings from borrowing body_buf. The + // caller owns the returned Parsed(T) and must call deinit(). + return std.json.parseFromSlice(T, allocator, body_writer.buffered(), .{ + .allocate = .alloc_always, + }); } ``` ### POST JSON Data ```zig -fn postJson(allocator: Allocator, url: []const u8, data: anytype) !void { +fn postJson(io: std.Io, allocator: Allocator, url: []const u8, data: anytype) !void { const json = try std.json.stringifyAlloc(allocator, data, .{}); defer allocator.free(json); - var client: std.http.Client = .{ .allocator = allocator }; + var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); const result = try client.fetch(.{ @@ -617,8 +633,8 @@ fn postJson(allocator: Allocator, url: []const u8, data: anytype) !void { ### Download File ```zig -fn downloadFile(allocator: Allocator, url: []const u8, path: []const u8) !void { - var client: std.http.Client = .{ .allocator = allocator }; +fn downloadFile(io: std.Io, allocator: Allocator, url: []const u8, path: []const u8) !void { + var client: std.http.Client = .{ .allocator = allocator, .io = io }; defer client.deinit(); const uri = try std.Uri.parse(url); @@ -632,11 +648,11 @@ fn downloadFile(allocator: Allocator, url: []const u8, path: []const u8) !void { if (response.head.status != .ok) return error.HttpError; - const file = try std.fs.cwd().createFile(path, .{}); - defer file.close(); + const file = try std.Io.Dir.cwd().createFile(io, path, .{}); + defer file.close(io); var file_buf: [4096]u8 = undefined; - var file_writer = file.writer(&file_buf); + var file_writer = file.writer(io, &file_buf); var reader_buf: [4096]u8 = undefined; const body_reader = response.reader(&reader_buf); diff --git a/references/std-io.md b/references/std-io.md index eb3aa8a..760185c 100644 --- a/references/std-io.md +++ b/references/std-io.md @@ -116,7 +116,7 @@ while (try reader.takeDelimiter('\n')) |line| { } ``` -`takeDelimiter` returns `!?[]u8`: `null` means EOF, not an error. +`takeDelimiter` returns `!?[]u8`: `null` means EOF with no buffered bytes remaining. A final unterminated line is returned as data before a later call yields `null`. ### File Reader @@ -148,6 +148,8 @@ const header = try reader.takeStruct(Header, .little); const leb = try reader.takeLeb128(u64); ``` +`takeStruct` reads an extern/packed memory representation through the reader buffer; use a layout with a defined byte representation, ensure the reader can supply the full size, and do not treat a native-layout struct as a portable wire format. + ## File Integration Use `std.Io.Dir` and `std.Io.File`. @@ -206,11 +208,14 @@ Use `io.randomSecure` for fresh secure entropy with error reporting. ## Time -The release notes map: +Timestamp reads now require an explicit clock choice: -- `std.time.Instant` -> `std.Io.Timestamp` -- `std.time.Timer` -> `std.Io.Timestamp` -- `std.time.timestamp` -> `std.Io.Timestamp.now` +```zig +const now = std.Io.Timestamp.now(io, .real); +const elapsed_mark = std.Io.Timestamp.now(io, .awake); +``` + +`Timestamp` is not a one-for-one replacement for every old `Instant`/`Timer` behavior; choose `.real` versus an appropriate monotonic clock such as `.awake`, and build elapsed-time helpers around that choice. Use a shared application helper when common timestamp reads require consistent clock selection or conversion semantics. @@ -220,9 +225,9 @@ Use a shared application helper when common timestamp reads require consistent c - `io.async(...)` - `std.Io.Group` -- `std.Io.Select` +- `std.Io.Select(U)` where `U` is the tagged union of possible results - `std.Io.Batch` -- `std.Io.Queue(T)` +- `std.Io.Queue(Elem)`, initialized with caller-provided typed element storage Cancelation guidance: @@ -250,8 +255,8 @@ Blocking sync moved to `std.Io` equivalents: | `std.Thread.Semaphore` | `std.Io.Semaphore` | | `std.Thread.RwLock` | `std.Io.RwLock` | | `std.Thread.ResetEvent` | `std.Io.Event` | -| `std.Thread.WaitGroup` | `std.Io.Group` | -| `std.Thread.Futex` | `std.Io.Futex` | +| `std.Thread.WaitGroup` | Conceptually `std.Io.Group`; submit tasks, then await or cancel the group | +| `std.Thread.Futex` | `io.futexWait*` / `io.futexWake`; waits have cancelable and uncancelable forms | ```zig try mutex.lock(io); diff --git a/references/std-json.md b/references/std-json.md index 2654a4d..bfd72a2 100644 --- a/references/std-json.md +++ b/references/std-json.md @@ -128,7 +128,7 @@ defer allocator.free(json); // To writer var buf: [4096]u8 = undefined; -var writer = std.fs.File.stdout().writer(&buf); +var writer = std.Io.File.stdout().writer(io, &buf); try std.json.Stringify.value(config, .{}, &writer.interface); try writer.interface.flush(); ``` @@ -184,22 +184,23 @@ pub const Value = union(enum) { float: f64, number_string: []const u8, // unparsed number string: []const u8, - array: Array, // std.ArrayList(Value) - object: ObjectMap, // StringArrayHashMap(Value) + array: Array, // std.array_list.Managed(Value) + object: ObjectMap, // std.array_hash_map.String(Value) }; ``` ### Building Values Manually ```zig -var obj = std.json.ObjectMap.init(allocator); -try obj.put("name", .{ .string = "test" }); -try obj.put("count", .{ .integer = 42 }); +var obj: std.json.ObjectMap = .empty; +defer obj.deinit(allocator); +try obj.put(allocator, "name", .{ .string = "test" }); +try obj.put(allocator, "count", .{ .integer = 42 }); var arr = std.json.Array.init(allocator); try arr.append(.{ .integer = 1 }); try arr.append(.{ .integer = 2 }); -try obj.put("items", .{ .array = arr }); +try obj.put(allocator, "items", .{ .array = arr }); const value = std.json.Value{ .object = obj }; ``` @@ -283,7 +284,7 @@ const Point = struct { Build JSON incrementally: ```zig -var out: std.io.Writer.Allocating = .init(allocator); +var out: std.Io.Writer.Allocating = .init(allocator); defer out.deinit(); var jw: std.json.Stringify = .{ @@ -345,14 +346,16 @@ const Config = struct { debug: bool = false, }; -fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config { - const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { +fn loadConfig(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !Config { + const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) { error.FileNotFound => return Config{}, // defaults else => return err, }; - defer file.close(); + defer file.close(io); - const content = try file.readToEndAlloc(allocator, 1024 * 1024); + var read_buf: [4096]u8 = undefined; + var reader = file.reader(io, &read_buf); + const content = try reader.interface.allocRemaining(allocator, .limited(1024 * 1024)); defer allocator.free(content); const parsed = try std.json.parseFromSlice(Config, allocator, content, .{ @@ -360,7 +363,8 @@ fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config { }); defer parsed.deinit(); - // Copy strings to owned memory since parsed will be freed + // Copy strings to owned memory since parsed and the source buffer are + // about to be freed. The caller owns and must free the returned host. return Config{ .host = try allocator.dupe(u8, parsed.value.host), .port = parsed.value.port, diff --git a/references/std-linked-list.md b/references/std-linked-list.md index f24fc3d..a6c6456 100644 --- a/references/std-linked-list.md +++ b/references/std-linked-list.md @@ -5,7 +5,7 @@ Intrusive linked lists for O(1) insertion/removal. Nodes are embedded in user st ## When to Use - O(1) insertion/removal anywhere in list -- Elements that need to be in multiple lists +- Elements that need to be in multiple lists (embed one distinct node per simultaneous list membership) - Preallocated/arena-allocated nodes - No allocation on insert (nodes already exist) @@ -27,12 +27,13 @@ var list: std.DoublyLinkedList = .{}; var a: Item = .{ .data = 1 }; var b: Item = .{ .data = 2 }; var c: Item = .{ .data = 3 }; +var d: Item = .{ .data = 4 }; // Insert list.append(&a.node); // add to end list.prepend(&b.node); // add to start list.insertAfter(&a.node, &c.node); // insert c after a -list.insertBefore(&a.node, &c.node); // insert c before a +list.insertBefore(&a.node, &d.node); // insert a different unlinked node before a // Remove list.remove(&a.node); // O(1) remove specific node @@ -46,20 +47,26 @@ if (list.first) |node| { } // Traverse forward -var it = list.first; -while (it) |node| : (it = node.next) { - const item: *Item = @fieldParentPtr("node", node); - // use item.data +{ + var it = list.first; + while (it) |node| : (it = node.next) { + const item: *Item = @fieldParentPtr("node", node); + // use item.data + } } // Traverse backward -var it = list.last; -while (it) |node| : (it = node.prev) { - const item: *Item = @fieldParentPtr("node", node); - // use item.data +{ + var it = list.last; + while (it) |node| : (it = node.prev) { + const item: *Item = @fieldParentPtr("node", node); + // use item.data + } } // Concatenate (moves all from list2 to end of list1) +var list1: std.DoublyLinkedList = .{}; +var list2: std.DoublyLinkedList = .{}; list1.concatByMoving(&list2); // Length (O(n) - consider tracking separately) @@ -85,10 +92,11 @@ var b: Item = .{ .data = 2 }; list.prepend(&a.node); // add to front a.node.insertAfter(&b.node); // insert b after a -// Remove -const first = list.popFirst(); // remove and return first -_ = a.node.removeNext(); // remove node after a -list.remove(&b.node); // O(n) - must find predecessor +// Remove b after a, then reinsert it so the remaining operations are valid. +_ = a.node.removeNext(); // removes and returns b +list.prepend(&b.node); +list.remove(&a.node); // O(n) - must find predecessor +const first = list.popFirst(); // removes and returns b // Traverse (forward only) var it = list.first; @@ -122,9 +130,11 @@ node.insertAfter(new_node) node.removeNext() // ?*Node - removes and returns next node.findLast() // *Node node.countChildren() // usize -node.reverse(&optional_ptr) +std.SinglyLinkedList.Node.reverse(&optional_ptr) ``` +Insertion requires a node that is not currently linked into a list, and removal requires membership in that exact list. Removal updates surrounding links and list heads but does not clear every stale `prev`/`next` field on the removed node; reinitializing or deliberately reinserting the node establishes its next valid state. + ## Common Pattern: LRU Cache ```zig diff --git a/references/std-log.md b/references/std-log.md index 8ce82e8..e330baa 100644 --- a/references/std-log.md +++ b/references/std-log.md @@ -34,10 +34,12 @@ pub fn main() void { | Level | Build Mode Default | Purpose | |-------|-------------------|---------| -| `.err` | Always shown | Something went wrong | -| `.warn` | Always shown | Uncertain if wrong, worth investigating | -| `.info` | Debug + Release | General program state | -| `.debug` | Debug only | Messages only useful for debugging | +| `.err` | Enabled by the default configuration | Something went wrong | +| `.warn` | Enabled by the default configuration | Uncertain if wrong, worth investigating | +| `.info` | Enabled in common default modes | General program state | +| `.debug` | Commonly Debug-only by default | Messages only useful for debugging | + +Compile-time level/scope filtering is controlled by `std.options`; a custom `logFn` also decides what its sink actually emits. Default level by build mode: - **Debug**: `.debug` (all messages) @@ -122,14 +124,10 @@ fn myLogFn( const level_txt = comptime level.asText(); const prefix = "[" ++ level_txt ++ "] (" ++ scope_prefix ++ "): "; - std.debug.lockStdErr(); - defer std.debug.unlockStdErr(); - - const io = applicationIo(); // Application-owned accessor for std.Io. var buf: [64]u8 = undefined; - var stderr = std.Io.File.stderr().writer(io, &buf); - stderr.interface.print(prefix ++ format ++ "\n", args) catch return; - stderr.interface.flush() catch return; + const locked = std.debug.lockStderr(&buf); + defer std.debug.unlockStderr(); // flushes the returned writer + locked.file_writer.interface.print(prefix ++ format ++ "\n", args) catch return; } ``` @@ -148,7 +146,7 @@ fn process() void { } // For default scope - if (std.log.defaultLogEnabled(.debug)) { + if (std.log.logEnabled(.debug, .default)) { std.log.debug("Debug message", .{}); } } @@ -177,9 +175,15 @@ fn myLogFn( comptime format: []const u8, args: anytype, ) void { - // Add timestamp, then forward to default - std.debug.print("[{d}] ", .{applicationTimestampNow().toNanoseconds()}); - std.log.defaultLog(level, scope, format, args); + // Emit the timestamp and message under one stderr lock so concurrent + // records cannot split the prefix from the message. + var buf: [128]u8 = undefined; + const locked = std.debug.lockStderr(&buf); + defer std.debug.unlockStderr(); + locked.file_writer.interface.print( + "[{d}] " ++ format ++ "\n", + .{applicationTimestampNow().toNanoseconds()} ++ args, + ) catch return; } ``` @@ -243,8 +247,10 @@ fn fileLogFn( args: anytype, ) void { const io = applicationIo(); - const file = std.Io.Dir.cwd().openFile(io, "app.log", .{ .mode = .write_only }) catch return; - defer file.close(io); + // Returns a long-lived handle opened during startup with an explicit + // create/append policy. Plain write_only would require an existing file + // and reopening each record could overwrite earlier data. + const file = applicationLogFile(); var buf: [256]u8 = undefined; var writer = file.writer(io, &buf); @@ -257,3 +263,5 @@ fn fileLogFn( w.flush() catch return; } ``` + +If multiple tasks share this sink, serialize the complete record and keep the file/writer alive; opening, seeking, writing, and closing separately for each record is not atomic. diff --git a/references/std-math.md b/references/std-math.md index ae2a49c..56b02c7 100644 --- a/references/std-math.md +++ b/references/std-math.md @@ -108,7 +108,7 @@ const log10_int_val = std.math.log10_int(1000); // 3 ```zig // Powers const pow_val = std.math.pow(f64, 2.0, 3.0); // 2^3 = 8.0 -const powi_val = std.math.powi(f64, 2.0, 3); // 2^3 (integer exponent) +const powi_val = try std.math.powi(i32, 2, 3); // checked integer power // Roots const sqrt_val = std.math.sqrt(@as(f64, 16.0)); // 4.0 @@ -202,11 +202,13 @@ const negated = std.math.negate(@as(i8, -128)) catch |err| { }; // Shift with overflow check -const shifted = std.math.shlExact(u8, 1, 8) catch |err| { - return err; // Overflow: 1 << 8 doesn't fit in u8 +const shifted = std.math.shlExact(u8, 2, 7) catch |err| { + return err; // Overflow: 2 << 7 doesn't fit in u8 }; ``` +The shift amount has type `std.math.Log2Int(T)`, so an out-of-range count such as 8 for `u8` is rejected before `shlExact` can report value overflow. + ## Division Functions ```zig @@ -362,13 +364,17 @@ defer b.deinit(); // Arithmetic try a.add(&a, &b); try a.mul(&a, &b); -try a.div(&q, &r, &a, &b); // quotient and remainder +var q = try Managed.init(allocator); +defer q.deinit(); +var r = try Managed.init(allocator); +defer r.deinit(); +try q.divTrunc(&r, &a, &b); // quotient in q, remainder in r // Comparison const ord = a.order(b); // .lt, .eq, or .gt // Convert to primitive (if fits) -const val = a.to(i128) catch |err| { +const val = a.toInt(i128) catch |err| { // Value doesn't fit in i128 return err; }; @@ -386,8 +392,9 @@ try c.setString(10, "123456789012345678901234567890"); const gcd_val = std.math.gcd(@as(u32, 48), @as(u32, 18)); // 6 // Least common multiple -const lcm_val = try std.math.lcm(@as(u32, 4), @as(u32, 6)); // 12 -// Returns error.Overflow if result doesn't fit +const lcm_val = std.math.lcm(@as(u32, 4), @as(u32, 6)); // 12 +// lcm is not an error union; intermediate multiplication follows the selected +// integer overflow/safety behavior. ``` ## Gamma Functions @@ -402,9 +409,9 @@ const lg = std.math.lgamma(f64, 100.0); ## Notes -- Most functions work with `f16`, `f32`, `f64`, `f80`, `f128` and `comptime_float` +- Floating-point support varies by function and target; check each signature rather than assuming every float width and `comptime_float` are accepted - Many functions support SIMD vectors: `sin(@Vector(4, f32){...})` -- Integer overflow-checking functions return `error.Overflow` or `error.DivisionByZero` +- Integer helpers document their own error sets; `error.Overflow` and `error.DivisionByZero` are common examples, not a universal pair - Hardware instructions used when available (`@sin`, `@cos`, `@sqrt`, etc.) - `approxEqAbs` for values near zero, `approxEqRel` for larger values - Complex number operations available in `std.math.complex` diff --git a/references/std-mem.md b/references/std-mem.md index a99e824..f77d600 100644 --- a/references/std-mem.md +++ b/references/std-mem.md @@ -22,7 +22,9 @@ std.mem.cutPrefix(u8, path, "assets/") std.mem.cutSuffix(u8, file_name, ".zig") ``` -Older examples below may use 0.15 `indexOf` names; translate them to the `find`/`cut` APIs in Zig 0.16. +The examples below use the Zig 0.16 `find`/`cut` names rather than the older `indexOf` family. + +The legacy `copyForwards`/`copyBackwards` helpers are deprecated; use `@memmove` when source and destination may overlap (or `@memcpy` when they provably do not). ## Slice Comparison & Search @@ -101,10 +103,12 @@ defer allocator.free(joined); // "a, b, c" // Join with null terminator const joinedZ = try std.mem.joinZ(allocator, "/", &.{ "path", "to", "file" }); +defer allocator.free(joinedZ); // [:0]u8 = "path/to/file" // Concatenate without separator const concatted = try std.mem.concat(allocator, u8, &.{ "hello", " ", "world" }); +defer allocator.free(concatted); // "hello world" ``` @@ -125,7 +129,8 @@ std.mem.trim(u8, "\n\thello\n\t", " \t\n") // In-place replace (returns count) var buf: [100]u8 = undefined; const count = std.mem.replace(u8, "hello", "l", "L", &buf); -// buf contains "heLLo", count = 2 +const output_len = std.mem.replacementSize(u8, "hello", "l", "L"); +const replaced = buf[0..output_len]; // "heLLo"; count = 2 // Allocate new slice const result = try std.mem.replaceOwned(u8, allocator, "hello", "l", "L"); @@ -148,18 +153,20 @@ const bytes = std.mem.asBytes(&val); // *const [4]u8 const byte_copy = std.mem.toBytes(val); // [4]u8 (copy) // Bytes to value -const bytes = [_]u8{ 0xEF, 0xBE, 0xAD, 0xDE }; -const ptr = std.mem.bytesAsValue(u32, &bytes); // *const u32 -const val = std.mem.bytesToValue(u32, &bytes); // u32 (copy) +const word_bytes: [4]u8 align(@alignOf(u32)) = .{ 0xEF, 0xBE, 0xAD, 0xDE }; +const ptr = std.mem.bytesAsValue(u32, &word_bytes); // native-memory view retaining input alignment +const word = std.mem.bytesToValue(u32, &word_bytes); // u32 copy // Slice conversions const u16_slice = [_]u16{ 0x0102, 0x0304 }; const u8_slice = std.mem.sliceAsBytes(&u16_slice); // []const u8 -const u8_data = [_]u8{ 1, 0, 2, 0, 3, 0, 4, 0 }; +const u8_data: [8]u8 align(@alignOf(u16)) = .{ 1, 0, 2, 0, 3, 0, 4, 0 }; const u16_view = std.mem.bytesAsSlice(u16, &u8_data); // []const u16 ``` +These are native-memory views: alignment follows the input pointer and integer byte order follows the target. Use `readInt`/`writeInt` for portable byte-order decoding. + ## Alignment ```zig @@ -182,8 +189,9 @@ std.mem.isValidAlign(3) // false const ptr: [*]u8 = @ptrFromInt(0x123); const aligned = std.mem.alignPointer(ptr, 0x100); // ?[*]u8 = 0x200 -// Find aligned slice within bytes -const aligned_slice = std.mem.alignInBytes(bytes, 16); // ?[]align(16) u8 +// Find aligned mutable slice within bytes +var storage: [128]u8 = undefined; +const aligned_slice = std.mem.alignInBytes(&storage, 16); // ?[]align(16) u8 ``` ## Alignment Type @@ -208,14 +216,14 @@ const ok = align_val.check(0x100); // true if aligned ```zig // To/from native endianness -const native = std.mem.littleToNative(u32, 0x12345678); -const native = std.mem.bigToNative(u32, 0x12345678); +const native_from_little = std.mem.littleToNative(u32, 0x12345678); +const native_from_big = std.mem.bigToNative(u32, 0x12345678); const little = std.mem.nativeToLittle(u32, native_val); const big = std.mem.nativeToBig(u32, native_val); // General conversion -const val = std.mem.toNative(u32, x, .little); // from little to native -const val = std.mem.nativeTo(u32, x, .big); // from native to big +const decoded = std.mem.toNative(u32, x, .little); // from little to native +const encoded = std.mem.nativeTo(u32, x, .big); // from native to big // Byte swap all fields in struct std.mem.byteSwapAllFields(MyStruct, &my_struct); @@ -255,6 +263,8 @@ const partial = std.mem.zeroInit(MyStruct, .{ }); ``` +Prefer explicit field initialization. `zeroes` is mainly for types whose all-zero state is intentionally valid (often C interop); in review, verify that every enum, pointer-like field, invariant, and nested type has a meaningful zero state. + ## Min/Max ```zig @@ -264,9 +274,9 @@ std.mem.min(i32, &slice) // 1 std.mem.max(i32, &slice) // 5 std.mem.minMax(i32, &slice) // .{ 1, 5 } -std.mem.indexOfMin(i32, &slice) // 1 -std.mem.indexOfMax(i32, &slice) // 4 -std.mem.indexOfMinMax(i32, &slice) // .{ 1, 4 } +std.mem.findMin(i32, &slice) // 1 +std.mem.findMax(i32, &slice) // 4 +std.mem.findMinMax(i32, &slice) // .{ 1, 4 } ``` ## Reverse & Rotate @@ -300,7 +310,7 @@ const len = std.mem.len(c_string); // length of null-terminated string const slice = std.mem.span(c_string); // Index of first difference -std.mem.indexOfDiff(u8, "hello", "helps") // ?usize = 3 +std.mem.findDiff(u8, "hello", "helps") // ?usize = 3 // Collapse repeated elements var data = "aabbcc".*; diff --git a/references/std-meta.md b/references/std-meta.md index 227e22b..dfc7a31 100644 --- a/references/std-meta.md +++ b/references/std-meta.md @@ -7,7 +7,7 @@ Comptime type introspection and manipulation utilities. Essential for generic pr | Function | Purpose | |----------|---------| | `stringToEnum(T, str)` | Convert string to enum variant | -| `fields(T)` | Get struct/union/enum/error fields | +| `fields(T)` | Get struct/union/enum or concrete error-set fields | | `fieldNames(T)` | Get field names as string slice | | `fieldInfo(T, field)` | Get info for specific field | | `fieldIndex(T, name)` | Get field index by name | @@ -71,6 +71,8 @@ const MyError = error{ NotFound, Timeout }; const error_fields = std.meta.fields(MyError); ``` +`fields` cannot enumerate the global `anyerror` set; use it only with a concrete error set whose members are known. + ### Get Field Names ```zig @@ -153,6 +155,8 @@ inline for (std.meta.tags(PointField)) |field| { } ``` +For a tagged union, `FieldEnum` reuses the existing tag enum only when field names match in order and the tag values are consecutive from zero; otherwise it constructs a separate enum. + For tagged unions, returns the existing tag type if compatible. ### DeclEnum - Generate Enum from Declarations @@ -170,8 +174,8 @@ const ApiMethod = std.meta.DeclEnum(Api); ### Int/Float Type Construction ```zig -const U24 = std.meta.Int(.unsigned, 24); // u24 -const I7 = std.meta.Int(.signed, 7); // i7 +const U24 = @Int(.unsigned, 24); // u24 +const I7 = @Int(.signed, 7); // i7 const F32 = std.meta.Float(32); // f32 const F16 = std.meta.Float(16); // f16 ``` @@ -180,7 +184,7 @@ const F16 = std.meta.Float(16); // f16 ```zig // From type array -const T1 = std.meta.Tuple(&.{ u32, f32, bool }); +const T1 = std.meta.Tuple(&.{ u32, f32, bool }); // deprecated compatibility helper // Equivalent to: struct { u32, f32, bool } // From function signature @@ -213,9 +217,9 @@ std.meta.Elem(?[*]u8) // u8 (through optional) ### Sentinel ```zig -std.meta.sentinel([:0]u8) // @as(u8, 0) -std.meta.sentinel([*:0]u8) // @as(u8, 0) -std.meta.sentinel([5:0]u8) // @as(u8, 0) +const slice_sentinel = std.meta.sentinel([:0]u8).?; // @as(u8, 0) +const ptr_sentinel = std.meta.sentinel([*:0]u8).?; // @as(u8, 0) +const array_sentinel = std.meta.sentinel([5:0]u8).?; // @as(u8, 0) std.meta.sentinel([]u8) // null std.meta.sentinel([5]u8) // null ``` @@ -259,6 +263,8 @@ std.meta.eql(&p1, &p1) // true **Supported types:** structs, arrays, vectors, optionals, error unions, tagged unions, primitives. +Non-packed untagged unions cannot be compared generically because there is no active tag to select a field. Packed unions are the exception handled as their packed representation. + **Not supported:** untagged unions (compile error). ## Type Queries @@ -333,13 +339,17 @@ const decls = std.meta.declarations(S); ## Error Handling ```zig -// Check if value is error (deprecated: use std.enums.fromInt) +// Inspect an error union with ordinary error-union syntax. const result = std.math.divTrunc(u8, 5, 0); -std.meta.isError(result) // true +if (result) |value| { + _ = value; +} else |err| { + _ = err; +} -// Enum from int (deprecated: use std.enums.fromInt) +// Checked enum conversion returns an optional. const Color = enum { red, green, blue }; -const c = std.meta.intToEnum(Color, 1) catch unreachable; // Color.green +const c = std.enums.fromInt(Color, 1) orelse return error.InvalidColor; ``` ## TrailerFlags @@ -349,11 +359,12 @@ Memory-efficient optional field storage using bit flags: ```zig const std = @import("std"); -const Flags = std.meta.TrailerFlags(struct { +const Trailer = struct { name: []const u8, age: u32, email: []const u8, -}); +}; +const Flags = std.meta.TrailerFlags(Trailer); // Initialize with some fields active var flags = Flags.init(.{ @@ -364,7 +375,7 @@ var flags = Flags.init(.{ // Allocate only needed space const size = flags.sizeInBytes(); -const data = try allocator.alignedAlloc(u8, @alignOf(@TypeOf(flags).Fields), size); +const data = try allocator.alignedAlloc(u8, .of(Trailer), size); defer allocator.free(data); // Set values diff --git a/references/std-multi-array-list.md b/references/std-multi-array-list.md index 1572501..f2d17e7 100644 --- a/references/std-multi-array-list.md +++ b/references/std-multi-array-list.md @@ -1,6 +1,6 @@ # std.MultiArrayList -Struct-of-Arrays container for cache-efficient struct storage. Stores each field in a separate contiguous array, reducing padding overhead and improving cache locality when accessing individual fields. +Struct-of-Arrays container for cache-efficient struct storage. Stores each field in a separate contiguous array, reducing padding overhead and improving cache locality when accessing individual fields. The element type must be a struct or tagged union; untagged unions are not supported. ## When to Use @@ -54,7 +54,7 @@ const n = list.len; When accessing multiple fields, use `slice()` to compute pointers once: ```zig -const slices = list.slice(); +var slices = list.slice(); // Now access fields without recomputing offsets for (slices.items(.id), slices.items(.score)) |id, score| { @@ -75,8 +75,9 @@ list.swapRemove(index); // O(n) but preserves order list.orderedRemove(index); -// Remove multiple indices (must be sorted ascending) -list.orderedRemoveMany(&.{ 1, 5, 7, 9 }); +// Remove multiple in-bounds indices from the pre-removal list. They must be +// sorted ascending; duplicates are allowed and count as one removed element. +list.orderedRemoveMany(&.{ 0, 1 }); ``` ## Tagged Union Support @@ -96,7 +97,7 @@ try values.append(allocator, .{ .float = 3.14 }); // Access tags and data separately const tags = values.items(.tags); // []meta.Tag(Value) -const data = values.items(.data); // []Value.Bare (untagged union) +const data = values.items(.data); // slice of internal payload-only union storage // Reconstruct full union const full = values.get(0); // Value{ .int = 42 } @@ -132,6 +133,11 @@ list.clearAndFree(allocator); ## Clone and Transfer ```zig -const copy = try list.clone(allocator); -const owned_slice = list.toOwnedSlice(); // empties list, caller owns +var copy = try list.clone(allocator); +defer copy.deinit(allocator); + +var owned_slice = list.toOwnedSlice(); // empties list; Slice now owns storage +defer owned_slice.deinit(allocator); ``` + +A cached `Slice` contains derived field pointers and length metadata. Operations on the original list that grow, reallocate, remove, resize, or transfer storage can make that cached metadata stale; reacquire `list.slice()` after mutations. A successful growth can also invalidate previously returned field pointers. diff --git a/references/std-net.md b/references/std-net.md index 1e1b45c..aa15046 100644 --- a/references/std-net.md +++ b/references/std-net.md @@ -1,12 +1,12 @@ # std.Io.net Reference (Zig 0.16.0) -Cross-platform networking abstractions for TCP/IP connections, address handling, and DNS resolution. +Cross-platform networking abstractions for IP connections, address handling, DNS resolution, Unix-domain sockets, and lower-level socket operations. Zig 0.16 exposes these APIs under `std.Io.net`; there is no root `std.net` module. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html -Zig 0.16 moves networking under `std.Io.net`. New networking code should accept/use `std.Io`, and stream/socket close/read/write operations should use the `io` parameter. Older `std.net` examples below are historical; translate them to `std.Io.net` before using them in new code. +Networking operations take an explicit `std.Io`. Readers, writers, servers, sockets, and streams also use that `io` value for construction or cleanup. -High-level HTTP clients also store `io`: +High-level HTTP clients store it directly: ```zig var client: std.http.Client = .{ @@ -17,626 +17,550 @@ defer client.deinit(); ``` ## Table of Contents -- [TCP Client](#tcp-client) -- [TCP Server](#tcp-server) -- [Address Types](#address-types) + +- [API Map](#api-map) +- [TCP Clients](#tcp-clients) +- [TCP Servers](#tcp-servers) +- [IP Address Types](#ip-address-types) - [Stream I/O](#stream-io) -- [DNS Resolution](#dns-resolution) -- [Unix Sockets](#unix-sockets) +- [DNS and Host Names](#dns-and-host-names) +- [Unix-Domain Sockets](#unix-domain-sockets) +- [Sockets and Datagram APIs](#sockets-and-datagram-apis) - [Common Patterns](#common-patterns) +- [Error Sets](#error-sets) -## TCP Client +## API Map -### Connect by Hostname +```zig +const net = std.Io.net; + +net.IpAddress // tagged union: .ip4 or .ip6 +net.Ip4Address // value-oriented IPv4 bytes + port +net.Ip6Address // IPv6 bytes + port + flow + interface +net.HostName // validated DNS host name and lookup/connect APIs +net.UnixAddress // Unix-domain socket path +net.Socket // open socket plus resolved/bound address +net.Stream // reliable connected byte stream +net.Server // listening socket +net.Protocol // tcp, udp, and other protocol identifiers +``` + +Use `IpAddress.connect` when an IP is already known. Use `HostName.connect` when DNS resolution and address fallback are required. + +## TCP Clients + +### Connect by Host Name ```zig const std = @import("std"); -const net = std.net; +const net = std.Io.net; -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); +pub fn main(init: std.process.Init) !void { + const io = init.io; + const host: net.HostName = try .init("example.com"); - // Connect to host:port (handles DNS resolution) - const stream = try net.tcpConnectToHost(allocator, "example.com", 80); - defer stream.close(); + const stream = try host.connect(io, 80, .{ + .mode = .stream, + .protocol = .tcp, + }); + defer stream.close(io); - // Create buffered reader/writer var read_buf: [4096]u8 = undefined; var write_buf: [1024]u8 = undefined; + var reader = stream.reader(io, &read_buf); + var writer = stream.writer(io, &write_buf); - var reader = stream.reader(&read_buf); - var writer = stream.writer(&write_buf); - - // Write request - try writer.interface.writeAll("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"); + try writer.interface.writeAll( + "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n", + ); try writer.interface.flush(); - // Read response - while (reader.interface().take(4096)) |chunk| { + while (reader.interface.take(4096)) |chunk| { std.debug.print("{s}", .{chunk}); } else |err| switch (err) { error.EndOfStream => {}, - else => return err, + error.ReadFailed => return reader.err.?, } } ``` -### Connect by Address +`HostName.connect` performs lookup and races/falls back across returned addresses. The host's bytes are externally owned, so keep their backing storage alive for the call. + +### Connect to a Parsed Address ```zig -// Parse and connect to IP address directly (no DNS) -const address = try net.Address.parseIp4("192.168.1.1", 8080); -const stream = try net.tcpConnectToAddress(address); -defer stream.close(); +const address = try net.IpAddress.parseIp4("192.168.1.1", 8080); +const stream = try address.connect(io, .{ + .mode = .stream, + .protocol = .tcp, +}); +defer stream.close(io); ``` -### Connect with IPv6 +### IPv6 and Scoped IPv6 ```zig -// IPv6 address -const addr6 = try net.Address.parseIp6("::1", 8080); -const stream = try net.tcpConnectToAddress(addr6); -defer stream.close(); +// Pure parsing: no interface-name scope lookup. +const loopback = try net.IpAddress.parseIp6("::1", 8080); +const stream = try loopback.connect(io, .{ .mode = .stream, .protocol = .tcp }); +defer stream.close(io); -// IPv6 with scope ID (link-local) -const link_local = try net.Address.resolveIp6("fe80::1%eth0", 8080); +// Resolving `%eth0` / `%eno1` requires Io because the interface name must be +// converted to an operating-system interface index. +const link_local = try net.IpAddress.resolveIp6(io, "fe80::1%eth0", 8080); ``` -## TCP Server +### Connection Timeout + +```zig +const host: net.HostName = try .init("example.com"); +const stream = try host.connect(io, 443, .{ + .mode = .stream, + .protocol = .tcp, + .timeout = .{ .duration = .fromSeconds(5) }, +}); +defer stream.close(io); +``` + +Timeouts can be `.none`, a relative `.duration`, or an absolute `.deadline`. + +## TCP Servers ### Basic Server ```zig const std = @import("std"); -const net = std.net; +const net = std.Io.net; -pub fn main() !void { - // Create address to listen on - const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080); - - // Start listening - var server = try address.listen(.{ +fn serve(io: std.Io) !void { + const address: net.IpAddress = .{ .ip4 = .unspecified(8080) }; + var server = try address.listen(io, .{ .reuse_address = true, + .kernel_backlog = 256, }); - defer server.deinit(); + defer server.deinit(io); - std.debug.print("Listening on port {d}\n", .{server.listen_address.getPort()}); + std.debug.print("Listening on port {d}\n", .{server.socket.address.getPort()}); - // Accept loop while (true) { - const conn = try server.accept(); - defer conn.stream.close(); - - // Handle connection - try handleClient(conn.stream, conn.address); + const client = try server.accept(io); + defer client.close(io); + try handleClient(io, client); } } -fn handleClient(stream: net.Stream, client_addr: net.Address) !void { +fn handleClient(io: std.Io, stream: net.Stream) !void { var read_buf: [4096]u8 = undefined; var write_buf: [1024]u8 = undefined; + var reader = stream.reader(io, &read_buf); + var writer = stream.writer(io, &write_buf); - var reader = stream.reader(&read_buf); - var writer = stream.writer(&write_buf); - - // Read request - const request = reader.interface().takeDelimiter('\n') catch |err| switch (err) { - error.EndOfStream => return, - else => return err, + const request_line = reader.interface.takeDelimiter('\n') catch |err| switch (err) { + error.ReadFailed => return reader.err.?, + error.StreamTooLong => return error.RequestLineTooLong, } orelse return; + std.debug.print("Request from {f}: {s}\n", .{ stream.socket.address, request_line }); - std.debug.print("Request from client: {s}\n", .{request}); - - // Send response try writer.interface.writeAll("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello"); try writer.interface.flush(); } ``` +`Server.accept(io)` returns a `Stream`, not a separate connection wrapper. The accepted stream contains its socket and address. + ### Listen Options ```zig -const server = try address.listen(.{ - // Allow address reuse (SO_REUSEADDR + SO_REUSEPORT on POSIX) +const server = try address.listen(io, .{ + .kernel_backlog = 128, .reuse_address = true, - - // Connection backlog (default 128) - .kernel_backlog = 256, - - // Non-blocking accept (O_NONBLOCK) - .force_nonblocking = false, + .mode = .stream, + .protocol = .tcp, }); ``` -### Server on Any Available Port +`IpAddress.ListenOptions` contains exactly `kernel_backlog`, `reuse_address`, `mode`, and `protocol`. It does not contain the older `force_nonblocking` field. + +### Ephemeral Port ```zig -// Listen on port 0 to let OS assign an available port -const address = net.Address.initIp4(.{ 127, 0, 0, 1 }, 0); -var server = try address.listen(.{}); -defer server.deinit(); +const address: net.IpAddress = .{ .ip4 = .loopback(0) }; +var server = try address.listen(io, .{}); +defer server.deinit(io); -// Get the assigned port -const port = server.listen_address.getPort(); -std.debug.print("Listening on port {d}\n", .{port}); +const assigned_port = server.socket.address.getPort(); ``` -## Address Types +The resolved port is stored on `server.socket.address`; there is no `listen_address` field. -### Address Union +## IP Address Types + +### Tagged Union ```zig -pub const Address = extern union { - any: posix.sockaddr, - in: Ip4Address, - in6: Ip6Address, - un: posix.sockaddr.un, // Unix socket (if supported) +pub const IpAddress = union(enum) { + ip4: Ip4Address, + ip6: Ip6Address, }; ``` -### Creating Addresses +This is a value-oriented union, not an extern `sockaddr` overlay. OS-specific socket-address conversion is handled below this API. + +### Construction ```zig -// IPv4 from bytes -const addr4 = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080); +const loopback4: net.IpAddress = .{ .ip4 = .loopback(8080) }; +const any4: net.IpAddress = .{ .ip4 = .unspecified(8080) }; +const loopback6: net.IpAddress = .{ .ip6 = .loopback(8080) }; +const any6: net.IpAddress = .{ .ip6 = .unspecified(8080) }; -// IPv6 from bytes -const addr6 = net.Address.initIp6( - .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, // ::1 - 8080, // port - 0, // flowinfo - 0, // scope_id -); - -// Unix socket -const unix = try net.Address.initUnix("/tmp/my.sock"); +const explicit4: net.IpAddress = .{ .ip4 = .{ + .bytes = .{ 127, 0, 0, 1 }, + .port = 8080, +} }; ``` -### Parsing Addresses +`Ip6Address` also has `flow: u32 = 0` and `interface: net.Interface = .none` fields. + +### Parsing ```zig -// Parse IPv4 -const addr4 = try net.Address.parseIp4("192.168.1.1", 8080); +const addr4 = try net.IpAddress.parseIp4("192.168.1.1", 8080); +const addr6 = try net.IpAddress.parseIp6("2001:db8::1", 8080); +const either = try net.IpAddress.parse("::1", 8080); -// Parse IPv6 -const addr6 = try net.Address.parseIp6("2001:db8::1", 8080); +// Address plus optional port. IPv6 must be bracketed. +const literal4 = try net.IpAddress.parseLiteral("192.168.1.1:8080"); +const literal6 = try net.IpAddress.parseLiteral("[2001:db8::1]:8080"); -// Parse either (tries IPv4 first, then IPv6) -const addr = try net.Address.parseIp("::1", 8080); - -// Parse IP:port format -// IPv4: "192.168.1.1:8080" -// IPv6: "[::1]:8080" (brackets required) -const addr_port = try net.Address.parseIpAndPort("[::1]:8080"); - -// Resolve with interface lookup (for link-local IPv6) -const resolved = try net.Address.resolveIp6("fe80::1%eth0", 8080); +// Handles an IPv6 interface-name scope and therefore requires Io. +const scoped = try net.IpAddress.resolve(io, "fe80::1%eth0", 8080); ``` -### Address Methods +`parseLiteral` uses port zero when no port is present. `parse` accepts an explicit port and tries IPv4, then IPv6. `resolve` adds scoped-IPv6 interface lookup. + +### Methods and Formatting ```zig -var addr = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080); +var address = try net.IpAddress.parseIp4("127.0.0.1", 8080); +const port = address.getPort(); +address.setPort(9090); -// Get/set port (native endian) -const port = addr.getPort(); // 8080 -addr.setPort(9090); +const same = address.eql(&other_address); -// Get socket length for syscalls -const socklen = addr.getOsSockLen(); - -// Compare addresses -if (addr.eql(other_addr)) { - // addresses match -} - -// Format for printing -var buf: [64]u8 = undefined; +var buf: [128]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); -try addr.format(&writer); -const formatted = writer.buffered(); // "127.0.0.1:8080" +try address.format(&writer); // omits an IPv6 interface-name scope +try address.formatResolved(io, &writer); // includes a resolvable IPv6 scope ``` -### Ip4Address +`Ip4Address.format` and `Ip6Address.format` include the native-endian port. `IpAddress.fromIp6` converts IPv4-mapped IPv6 addresses back to `.ip4` where possible. + +### IPv4 Parse Errors ```zig -const Ip4Address = extern struct { - sa: posix.sockaddr.in, - - pub fn parse(buf: []const u8, port: u16) !Ip4Address; - pub fn init(addr: [4]u8, port: u16) Ip4Address; - pub fn getPort(self: Ip4Address) u16; - pub fn setPort(self: *Ip4Address, port: u16) void; - pub fn format(self: Ip4Address, w: *std.Io.Writer) !void; +pub const Ip4Address.ParseError = error{ + Overflow, + InvalidEnd, + InvalidCharacter, + Incomplete, + NonCanonical, }; ``` -### Ip6Address - -```zig -const Ip6Address = extern struct { - sa: posix.sockaddr.in6, - - pub fn parse(buf: []const u8, port: u16) !Ip6Address; - pub fn resolve(buf: []const u8, port: u16) !Ip6Address; // handles %interface - pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address; - pub fn getPort(self: Ip6Address) u16; - pub fn setPort(self: *Ip6Address, port: u16) void; - pub fn format(self: Ip6Address, w: *std.Io.Writer) !void; -}; -``` +For example, leading-zero forms such as `01.2.3.4` are non-canonical. ## Stream I/O -### Stream Type +### Stream Shape and Lifecycle -```zig +```text pub const Stream = struct { - handle: Handle, // fd on POSIX, SOCKET on Windows + socket: net.Socket, - pub fn close(s: Stream) void; - pub fn reader(stream: Stream, buffer: []u8) Reader; - pub fn writer(stream: Stream, buffer: []u8) Writer; + pub fn close(stream: *const Stream, io: std.Io) void; + pub fn shutdown(stream: *const Stream, io: std.Io, how: net.ShutdownHow) !void; + pub fn reader(stream: Stream, io: std.Io, buffer: []u8) Stream.Reader; + pub fn writer(stream: Stream, io: std.Io, buffer: []u8) Stream.Writer; }; ``` -### Reading from Stream +Do not close a stream twice. Flush a buffered writer before shutdown/close when its bytes must reach the peer. + +### Reading ```zig -const stream = try net.tcpConnectToHost(allocator, "example.com", 80); -defer stream.close(); +var read_buf: [4096]u8 = undefined; +var reader = stream.reader(io, &read_buf); +const r = &reader.interface; -var buf: [4096]u8 = undefined; -var reader = stream.reader(&buf); -const r = reader.interface(); - -// Read bytes const data = r.take(100) catch |err| switch (err) { - error.EndOfStream => &.{}, - error.ReadFailed => return reader.getError().?, + error.EndOfStream => return, + error.ReadFailed => return reader.err.?, }; -// Read until delimiter const line = r.takeDelimiter('\n') catch |err| switch (err) { - error.EndOfStream => null, + error.ReadFailed => return reader.err.?, error.StreamTooLong => return error.LineTooLong, - error.ReadFailed => return reader.getError().?, } orelse return; -// Discard bytes -_ = try r.discard(.limited(100)); - -// Stream to writer -_ = try r.streamRemaining(&output_writer); +_ = data; +_ = line; ``` -### Writing to Stream +The generic reader surface reports `error.ReadFailed`; the network-specific cause is stored in `reader.err`, with cases such as `ConnectionResetByPeer`, `Timeout`, `SocketUnconnected`, or `NetworkDown`. + +### Writing ```zig -var buf: [1024]u8 = undefined; -var writer = stream.writer(&buf); +var write_buf: [1024]u8 = undefined; +var writer = stream.writer(io, &write_buf); const w = &writer.interface; -// Write bytes try w.writeAll("Hello, World!"); - -// Formatted output try w.print("Count: {d}\n", .{42}); - -// MUST flush before close try w.flush(); ``` -### Error Handling +The generic writer reports `error.WriteFailed`; inspect `writer.err` for the network-specific cause. A successful `writeAll` may still be buffered until `flush`. + +### Half-Close ```zig -var reader = stream.reader(&buf); -const r = reader.interface(); - -const data = r.take(100) catch |err| switch (err) { - error.EndOfStream => { - // Connection closed normally - return; - }, - error.ReadFailed => { - // Get underlying error - const read_err = reader.getError().?; - switch (read_err) { - error.ConnectionResetByPeer => return error.Disconnected, - error.SocketNotConnected => return error.Disconnected, - else => return read_err, - } - }, -}; +try stream.shutdown(io, .send); // no more application writes +// Continue reading until EndOfStream if the protocol expects a response. ``` -## DNS Resolution +`ShutdownHow` is `.recv`, `.send`, or `.both`. -### Get Address List +## DNS and Host Names + +### Validation ```zig -const std = @import("std"); -const net = std.net; +const host = try net.HostName.init("example.com"); +try net.HostName.validate("api.example.com"); -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); +const same = host.eql(try .init("EXAMPLE.COM")); // DNS names compare case-insensitively +const child = host.sameParentDomain(try .init("www.example.com")); +``` - // Resolve hostname to addresses - const list = try net.getAddressList(allocator, "example.com", 80); - defer list.deinit(); +`HostName` retains a borrowed byte slice. Labels and total length are validated; the maximum is `net.HostName.max_len`. - // Canonical name (if available) - if (list.canon_name) |name| { - std.debug.print("Canonical name: {s}\n", .{name}); - } +### Queue-Based Lookup - // Iterate addresses - for (list.addrs) |addr| { - var buf: [64]u8 = undefined; - var w: std.Io.Writer = .fixed(&buf); - try addr.format(&w); - std.debug.print("Address: {s}\n", .{w.buffered()}); - } +```zig +const host: net.HostName = try .init("example.com"); +var result_storage: [16]net.HostName.LookupResult = undefined; +var results: std.Io.Queue(net.HostName.LookupResult) = .init(&result_storage); + +try host.lookup(io, &results, .{ .port = 443 }); + +while (results.getOne(io)) |result| switch (result) { + .address => |address| std.debug.print("address: {f}\n", .{address}), + .canonical_name => |name| std.debug.print("canonical: {s}\n", .{name.bytes}), +} else |err| switch (err) { + error.Closed => {}, + error.Canceled => return err, } ``` -### Connect with Fallback +`lookup` adds zero or more `.address` results and exactly one `.canonical_name`, then closes the queue even on error. Capacity 16 guarantees the call itself need not block waiting for a consumer. -`tcpConnectToHost` automatically tries all resolved addresses: +### Connect with Lookup and Fallback ```zig -// Tries each resolved address until one connects -const stream = net.tcpConnectToHost(allocator, "example.com", 80) catch |err| switch (err) { - error.ConnectionRefused => return error.ServerDown, - error.UnknownHostName => return error.DnsError, - error.TemporaryNameServerFailure => return error.DnsError, +const host: net.HostName = try .init("example.com"); +const stream = host.connect(io, 443, .{ + .mode = .stream, + .protocol = .tcp, +}) catch |err| switch (err) { + error.UnknownHostName, error.NoAddressReturned => return error.DnsFailure, + error.ConnectionRefused => return error.ServerUnavailable, else => return err, }; +defer stream.close(io); ``` -## Unix Sockets +For advanced callers, `HostName.connectMany` asynchronously attempts all resolved addresses and writes successes or per-address connection errors to a caller-provided queue. -### Check Platform Support +## Unix-Domain Sockets + +### Support and Client ```zig if (net.has_unix_sockets) { - // Unix sockets available + const address = try net.UnixAddress.init("/var/run/app.sock"); + const stream = try address.connect(io); + defer stream.close(io); + + var read_buf: [4096]u8 = undefined; + var reader = stream.reader(io, &read_buf); + _ = &reader; } ``` -### Connect to Unix Socket +`UnixAddress` borrows its path and rejects paths longer than `UnixAddress.max_len`. `isAbstract()` detects an empty/leading-NUL abstract address representation. + +### Server ```zig -const stream = try net.connectUnixSocket("/var/run/app.sock"); -defer stream.close(); +const socket_path = "/tmp/my.sock"; +std.Io.Dir.deleteFileAbsolute(io, socket_path) catch |err| switch (err) { + error.FileNotFound => {}, + else => return err, +}; +defer std.Io.Dir.deleteFileAbsolute(io, socket_path) catch {}; -var buf: [4096]u8 = undefined; -var reader = stream.reader(&buf); -var writer = stream.writer(&buf); -// ... use like TCP -``` - -### Unix Socket Server - -```zig -const address = try net.Address.initUnix("/tmp/my.sock"); -var server = try address.listen(.{ .reuse_address = true }); -defer server.deinit(); - -// Remove socket file on cleanup -defer std.fs.deleteFileAbsolute("/tmp/my.sock") catch {}; +const address = try net.UnixAddress.init(socket_path); +var server = try address.listen(io, .{ .kernel_backlog = 128 }); +defer server.deinit(io); while (true) { - const conn = try server.accept(); - defer conn.stream.close(); - // handle connection... + const client = try server.accept(io); + defer client.close(io); + // Handle one client in this loop body. } ``` +Unix listen options contain only `kernel_backlog`; IP `reuse_address` options do not apply to this type. + +## Sockets and Datagram APIs + +`IpAddress.bind` is the non-streaming counterpart to `listen`: + +```zig +const address: net.IpAddress = .{ .ip4 = .unspecified(5353) }; +const socket = try address.bind(io, .{ + .mode = .dgram, + .protocol = .udp, + .allow_broadcast = false, +}); +defer socket.close(io); +``` + +`BindOptions` contains `ip6_only`, `allow_broadcast`, required `mode`, and optional `protocol`. `Socket` also exposes message send/receive operations, shutdown, option accessors, and `closeMany`; use those when datagram boundaries or raw socket features matter. + +The listening API intentionally has a smaller option set than `bind`. In particular, `IpAddress.ListenOptions` has no `ip6_only` flag, so do not assume an IPv6 listener is unconditionally dual-stack on every target. Use explicitly managed IPv4/IPv6 listeners when that behavior must be controlled. + ## Common Patterns -### Echo Server +### Echo One Connection ```zig -const std = @import("std"); -const net = std.net; - -pub fn main() !void { - const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 7); // echo port - var server = try address.listen(.{ .reuse_address = true }); - defer server.deinit(); - - while (true) { - const conn = try server.accept(); - defer conn.stream.close(); - - var buf: [4096]u8 = undefined; - var reader = conn.stream.reader(&buf); - var writer = conn.stream.writer(&buf); - - // Echo back everything received - _ = reader.interface().streamRemaining(&writer.interface) catch {}; - writer.interface.flush() catch {}; - } -} -``` - -### Simple HTTP GET - -```zig -fn httpGet(allocator: Allocator, host: []const u8, path: []const u8) ![]u8 { - const stream = try net.tcpConnectToHost(allocator, host, 80); - defer stream.close(); - - var write_buf: [1024]u8 = undefined; - var writer = stream.writer(&write_buf); - const w = &writer.interface; - - try w.print("GET {s} HTTP/1.1\r\n", .{path}); - try w.print("Host: {s}\r\n", .{host}); - try w.writeAll("Connection: close\r\n\r\n"); - try w.flush(); - +fn echoConnection(io: std.Io, stream: net.Stream) !void { var read_buf: [4096]u8 = undefined; - var reader = stream.reader(&read_buf); + var write_buf: [4096]u8 = undefined; + var reader = stream.reader(io, &read_buf); + var writer = stream.writer(io, &write_buf); - var response: std.ArrayList(u8) = .empty; - defer response.deinit(allocator); - - while (true) { - const chunk = reader.interface().take(4096) catch |err| switch (err) { - error.EndOfStream => break, - error.ReadFailed => return reader.getError().?, - }; - try response.appendSlice(allocator, chunk); - } - - return response.toOwnedSlice(allocator); + _ = reader.interface.streamRemaining(&writer.interface) catch |err| switch (err) { + error.ReadFailed => return reader.err.?, + error.WriteFailed => return writer.err.?, + }; + try writer.interface.flush(); } ``` -### Non-blocking Accept with Timeout +For a concurrent server, schedule each accepted stream through the active `std.Io` implementation and make one owner responsible for closing it. + +### Address and Host Validation ```zig -const std = @import("std"); -const net = std.net; -const posix = std.posix; - -fn acceptWithTimeout(server: *net.Server, timeout_ms: i32) !?net.Server.Connection { - var pfd = [1]posix.pollfd{.{ - .fd = server.stream.handle, - .events = posix.POLL.IN, - .revents = undefined, - }}; - - const ready = try posix.poll(&pfd, timeout_ms); - if (ready == 0) return null; // timeout - - return try server.accept(); -} -``` - -### Address Validation - -```zig -fn isValidIpAddress(str: []const u8) bool { - _ = net.Address.parseIp(str, 0) catch return false; +fn isValidIpAddress(text: []const u8) bool { + _ = net.IpAddress.parse(text, 0) catch return false; return true; } -fn isValidHostname(hostname: []const u8) bool { - return net.isValidHostName(hostname); +fn isValidHostName(text: []const u8) bool { + net.HostName.validate(text) catch return false; + return true; } ``` -### Dual-Stack Server (IPv4 + IPv6) +Parsing an IP is a pure syntax/canonicality check. Host-name validation does not perform DNS lookup. + +### Index-Based Connection Pool ```zig -// Listen on IPv6 with dual-stack (accepts both IPv4 and IPv6) -const address = net.Address.initIp6( - .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // :: - 8080, - 0, - 0, -); - -var server = try address.listen(.{ .reuse_address = true }); -defer server.deinit(); - -// IPv4 clients appear as IPv4-mapped IPv6 addresses (::ffff:x.x.x.x) -``` - -### Connection Pool Pattern - -```zig -const Connection = struct { - stream: net.Stream, - in_use: bool, -}; - const Pool = struct { - connections: std.ArrayList(Connection), - allocator: Allocator, + const Entry = struct { stream: net.Stream, in_use: bool }; - pub fn acquire(self: *Pool, address: net.Address) !net.Stream { - // Find free connection - for (self.connections.items) |*conn| { - if (!conn.in_use) { - conn.in_use = true; - return conn.stream; + entries: std.ArrayList(Entry) = .empty, + allocator: std.mem.Allocator, + io: std.Io, + + fn acquire(self: *Pool, address: *const net.IpAddress) !usize { + for (self.entries.items, 0..) |*entry, index| { + if (!entry.in_use) { + entry.in_use = true; + return index; } } - // Create new connection - const stream = try net.tcpConnectToAddress(address); - try self.connections.append(self.allocator, .{ - .stream = stream, - .in_use = true, - }); - return stream; + + const stream = try address.connect(self.io, .{ .mode = .stream, .protocol = .tcp }); + errdefer stream.close(self.io); + try self.entries.append(self.allocator, .{ .stream = stream, .in_use = true }); + return self.entries.items.len - 1; } - pub fn release(self: *Pool, stream: net.Stream) void { - for (self.connections.items) |*conn| { - if (conn.stream.handle == stream.handle) { - conn.in_use = false; - return; - } - } + fn get(self: *Pool, index: usize) *net.Stream { + return &self.entries.items[index].stream; } - pub fn deinit(self: *Pool) void { - for (self.connections.items) |conn| { - conn.stream.close(); - } - self.connections.deinit(self.allocator); + fn release(self: *Pool, index: usize) void { + self.entries.items[index].in_use = false; + } + + fn deinit(self: *Pool) void { + for (self.entries.items) |entry| entry.stream.close(self.io); + self.entries.deinit(self.allocator); + self.* = undefined; } }; ``` -## Error Types +Indices are used because growing an `ArrayList` can invalidate pointers into its storage. A production pool also needs protocol-aware liveness checks, concurrency control, capacity limits, idle expiry, and a policy for discarding failed streams. -### Connection Errors +### Prefer Protocol-Specific Clients + +Direct stream examples are useful for custom protocols and learning the I/O model. For HTTP, use `std.http.Client`: it handles framing, redirects/options, response-body lifecycle, proxies, and TLS concerns that a raw `GET` snippet does not. + +## Error Sets + +### IP Connection Errors + +`net.IpAddress.ConnectError` includes address/family and resource failures plus network outcomes such as: ```zig -pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError; - -pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError; -// Includes: ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, etc. +error.ConnectionRefused +error.ConnectionResetByPeer +error.HostUnreachable +error.NetworkUnreachable +error.NetworkDown +error.Timeout +error.WouldBlock +error.AccessDenied ``` -### DNS Errors +It also includes cancellation and implementation-specific unexpected I/O errors. Match only cases the caller can handle meaningfully and propagate the rest. + +### Host Lookup and Connect Errors ```zig -pub const GetAddressListError = error{ - TemporaryNameServerFailure, - NameServerFailure, - AddressFamilyNotSupported, - UnknownHostName, - HostLacksNetworkAddresses, - // ... and others -}; +net.HostName.LookupError // UnknownHostName, NameServerFailure, + // NoAddressReturned, configuration/DNS record errors, ... + +net.HostName.ConnectError // LookupError || net.IpAddress.ConnectError ``` -### Address Parse Errors +The old `GetAddressListError` and `TcpConnectToHostError` aliases are not Zig 0.16 APIs. -```zig -pub const IPv4ParseError = error{ - Overflow, - InvalidEnd, - InvalidCharacter, - Incomplete, - NonCanonical, // e.g., leading zeros like "01.02.03.04" -}; +### Stream Error Translation -pub const IPv6ParseError = error{ - Overflow, - InvalidEnd, - InvalidCharacter, - Incomplete, - InvalidIpv4Mapping, -}; -``` +`Stream.Reader` and `Stream.Writer` deliberately adapt network errors to the generic `std.Io.Reader`/`std.Io.Writer` interfaces: + +- On `error.ReadFailed`, inspect `reader.err`. +- On `error.WriteFailed`, inspect `writer.err`. +- `EndOfStream` is the normal generic-reader signal for an orderly peer close. +- Close, shutdown, accept, connect, lookup, bind, and listen all take the explicit `io` used to create or operate the resource. diff --git a/references/std-os.md b/references/std-os.md index 25bd896..f78c316 100644 --- a/references/std-os.md +++ b/references/std-os.md @@ -23,13 +23,9 @@ std.os.wasi // WebAssembly System Interface std.os.plan9 // Plan 9 system calls std.os.uefi // UEFI firmware interface std.os.emscripten // Emscripten runtime -std.os.freebsd // FreeBSD-specific definitions - -std.os.environ // Environment variables (populated at startup) -std.os.argv // Command line arguments (POSIX only) ``` -**Note**: For most use cases, prefer `std.posix` (cross-platform POSIX-like APIs) or `std.fs`/`std.process` (high-level abstractions). Use `std.os` when you need direct OS-specific functionality. +Those six target modules are the complete public surface of `std.os` in Zig 0.16. For most use cases, prefer `std.Io` (portable, capability-based I/O) or `std.posix` (cross-platform POSIX-like APIs). Process startup arguments and environment data are supplied through `std.process.Init`, not `std.os.argv` or `std.os.environ`. ## Platform Submodules @@ -37,13 +33,14 @@ std.os.argv // Command line arguments (POSIX only) ```zig // High-level (recommended for most code) -const file = try std.fs.cwd().openFile("data.txt", .{}); +const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{}); +defer file.close(io); // POSIX-level (cross-platform low-level) const fd = try std.posix.open("data.txt", .{}, 0); // OS-specific (platform-specific features) -const result = std.os.linux.syscall3(.read, fd, buf.ptr, buf.len); +const result = std.os.linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len); ``` ## Linux-Specific APIs @@ -55,7 +52,7 @@ const linux = std.os.linux; // Raw syscall interface const result = linux.syscall3(.write, fd, @intFromPtr(buf.ptr), buf.len); -if (linux.E.init(result) != .SUCCESS) { +if (linux.errno(result) != .SUCCESS) { // handle error } @@ -74,7 +71,7 @@ _ = linux.chroot(path); const linux = std.os.linux; // mmap with typed flags -const addr = linux.mmap( +const result = linux.mmap( null, length, linux.PROT.READ | linux.PROT.WRITE, @@ -82,9 +79,10 @@ const addr = linux.mmap( -1, 0, ); -if (addr == linux.MAP_FAILED) { - // handle error -} +const addr: [*]u8 = switch (linux.errno(result)) { + .SUCCESS => @ptrFromInt(result), + else => |err| return std.posix.unexpectedErrno(err), +}; // Remap _ = linux.mremap(old_addr, old_size, new_size, .{ .MAYMOVE = true }, null); @@ -121,7 +119,7 @@ const linux = std.os.linux; // Wait on futex _ = linux.futex( &futex_word, - .{ .op = .WAIT, .PRIVATE = true }, + .{ .cmd = .WAIT, .private = true }, expected_value, .{ .timeout = &timeout }, null, @@ -131,7 +129,7 @@ _ = linux.futex( // Wake waiters _ = linux.futex( &futex_word, - .{ .op = .WAKE, .PRIVATE = true }, + .{ .cmd = .WAKE, .private = true }, num_to_wake, .{ .val2 = 0 }, null, @@ -150,7 +148,10 @@ var act: linux.Sigaction = .{ .mask = linux.empty_sigset, .flags = .{}, }; -_ = linux.sigaction(linux.SIG.INT, &act, null); +const result = linux.sigaction(.INT, &act, null); +if (linux.errno(result) != .SUCCESS) { + // translate or handle the raw errno +} // Kill process _ = linux.kill(pid, linux.SIG.TERM); @@ -173,7 +174,11 @@ _ = linux.epoll_ctl(epfd, .ADD, client_fd, &event); // Wait for events var events: [64]linux.epoll_event = undefined; -const n = linux.epoll_wait(epfd, &events, -1); +const result = linux.epoll_wait(epfd, &events, @intCast(events.len), -1); +const n: usize = switch (linux.errno(result)) { + .SUCCESS => result, + else => |err| return std.posix.unexpectedErrno(err), +}; for (events[0..n]) |ev| { // handle event } @@ -195,19 +200,13 @@ const platform = linux.getauxval(std.elf.AT_PLATFORM); ### File Operations ```zig -const windows = std.os.windows; - -// Open file with NT API -const handle = try windows.OpenFile(path_utf16, .{ - .access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE, - .creation = windows.FILE_OPEN, - .share_access = windows.FILE_SHARE_READ, - .filter = .file_only, - .follow_symlinks = true, -}); -defer windows.CloseHandle(handle); +// Prefer the portable std.Io layer even in Windows-only programs. +const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{}); +defer file.close(io); ``` +For Win32 or NT features that `std.Io` does not expose, use declarations actually exported by `std.os.windows` and its `kernel32`/`ntdll` submodules. There is no public `std.os.windows.OpenFile` helper in Zig 0.16. + ### Process Information ```zig @@ -225,26 +224,13 @@ const err = windows.GetLastError(); ### Pipes -```zig -const windows = std.os.windows; - -var read_handle: windows.HANDLE = undefined; -var write_handle: windows.HANDLE = undefined; -var sa: windows.SECURITY_ATTRIBUTES = .{ - .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), - .lpSecurityDescriptor = null, - .bInheritHandle = windows.TRUE, -}; - -try windows.CreatePipe(&read_handle, &write_handle, &sa); -``` +Use the `std.Io` pipe/process APIs for portable pipes. If raw Windows handles are required, call a declaration that is actually exported from the relevant Windows submodule and translate its Win32 error explicitly; Zig 0.16 does not expose a fallible `std.os.windows.CreatePipe` wrapper with the signature shown in older examples. ### Submodules ```zig windows.kernel32 // kernel32.dll functions windows.ntdll // ntdll.dll functions (NT native API) -windows.advapi32 // advapi32.dll (security, registry) windows.ws2_32 // Winsock 2 networking windows.crypt32 // Cryptographic functions windows.nls // National Language Support @@ -386,21 +372,21 @@ const submitted = try ring.submit(); _ = try ring.submit_and_wait(1); // Process completions -while (ring.cq_ready() > 0) { - const cqe = ring.peek_cqe() orelse break; - +var cqes: [32]std.os.linux.io_uring_cqe = undefined; +const ready = try ring.copy_cqes(&cqes, 1); +for (cqes[0..ready]) |cqe| { const user_data = cqe.user_data; const result = cqe.res; // bytes transferred or -errno if (result < 0) { - const err = std.os.linux.E.init(@intCast(-result)); + const err: std.os.linux.E = @enumFromInt(@as(u16, @intCast(-result))); // handle error } - - ring.cq_advance(1); // mark CQE as consumed } ``` +`copy_cqes` copies and consumes completions as a batch. `copy_cqe` is the corresponding wait-for-one convenience method. Do not additionally call `cqe_seen` or `cq_advance` after either copying API. + ### Common Operations ```zig @@ -411,8 +397,8 @@ sqe.prep_readv(fd, iovecs, offset); sqe.prep_writev(fd, iovecs, offset); // Fixed buffers (pre-registered, zero-copy) -sqe.prep_read_fixed(fd, buf, offset, buf_index); -sqe.prep_write_fixed(fd, data, offset, buf_index); +sqe.prep_read_fixed(fd, registered_iovec, offset, buf_index); +sqe.prep_write_fixed(fd, registered_iovec, offset, buf_index); // Network sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0); @@ -463,7 +449,7 @@ defer ring.unregister_buffers() catch {}; // Use registered buffer const sqe = try ring.get_sqe(); -sqe.prep_read_fixed(fd, &buffers[0], 0, 0); // buf_index = 0 +sqe.prep_read_fixed(fd, &iovecs[0], 0, 0); // buf_index = 0 ``` ### File Descriptor Registration @@ -482,51 +468,14 @@ sqe.flags |= std.os.linux.IOSQE_FIXED_FILE; ## Common Functions -### getFdPath +Zig 0.16 deliberately has no cross-platform function layer at the `std.os` root. Older references may mention root functions such as `std.os.getFdPath`, `std.os.accessW`, `std.os.fstatat_wasi`, or `std.os.fstat_wasi`; those are not public Zig 0.16 APIs. -Get canonical path from file descriptor (not all platforms). +Choose the API by intent: -```zig -var buf: [std.fs.max_path_bytes]u8 = undefined; -const path = try std.os.getFdPath(fd, &buf); -std.debug.print("Path: {s}\n", .{path}); - -// Check if supported at comptime -if (comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) { - // safe to call -} -``` - -**Supported**: Linux, macOS, FreeBSD, Windows, Solaris/illumos, DragonFly (6.0+), NetBSD (10.0+) - -### accessW (Windows) - -Check file accessibility with WTF-16LE path. - -```zig -const path_w = std.unicode.utf8ToUtf16LeStringLiteral("C:\\file.txt"); -std.os.accessW(path_w) catch |err| switch (err) { - error.FileNotFound => {}, - error.AccessDenied => {}, - else => return err, -}; -``` - -### WASI stat functions - -```zig -// stat by path -const stat = try std.os.fstatat_wasi(dirfd, path, .{ .SYMLINK_FOLLOW = true }); - -// stat by fd -const stat = try std.os.fstat_wasi(fd); - -stat.size; // file size -stat.filetype; // .REGULAR_FILE, .DIRECTORY, .SYMBOLIC_LINK, etc. -stat.atim; // access time (nanoseconds) -stat.mtim; // modification time -stat.ctim; // status change time -``` +- Use `std.Io.Dir` and `std.Io.File` for portable path, access, and metadata operations. +- Use `std.posix` for POSIX-like file-descriptor operations. +- Use `std.os.windows`, `std.os.wasi`, or another exported target module when the behavior is intentionally ABI-specific. +- Preserve the path separately when an application needs to associate a portable path with an open file; a descriptor does not portably retain a recoverable canonical pathname. ## Common Patterns @@ -562,11 +511,10 @@ fn eventLoop(ring: *std.os.linux.IoUring) !void { // Submit pending and wait for completions _ = try ring.submit_and_wait(1); - // Process all available completions - while (ring.cq_ready() > 0) { - const cqe = ring.peek_cqe() orelse break; - defer ring.cq_advance(1); - + // Copying also advances the completion queue. + var cqes: [64]std.os.linux.io_uring_cqe = undefined; + const count = try ring.copy_cqes(&cqes, 1); + for (cqes[0..count]) |cqe| { const ctx = @as(*Context, @ptrFromInt(cqe.user_data)); try ctx.handle_completion(cqe.res); } @@ -582,7 +530,7 @@ const linux = std.os.linux; fn readSyscall(fd: i32, buf: []u8) !usize { const result = linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len); - switch (linux.E.init(result)) { + switch (linux.errno(result)) { .SUCCESS => return result, .INTR => return error.Interrupted, .AGAIN => return error.WouldBlock, @@ -601,28 +549,17 @@ fn readSyscall(fd: i32, buf: []u8) !usize { ```zig const windows = std.os.windows; -fn windowsOperation() !void { - const result = windows.kernel32.SomeFunction(...); - if (result == windows.FALSE) { - switch (windows.GetLastError()) { - .ERROR_FILE_NOT_FOUND => return error.FileNotFound, - .ERROR_ACCESS_DENIED => return error.AccessDenied, - else => |e| return windows.unexpectedError(e), - } +fn translateLastError() !void { + // Call this immediately after a Win32 API reports failure; another Win32 + // call may overwrite the thread's last-error value. + switch (windows.GetLastError()) { + .FILE_NOT_FOUND => return error.FileNotFound, + .ACCESS_DENIED => return error.AccessDenied, + else => |e| return windows.unexpectedError(e), } } ``` -### Cross-Platform File Descriptor Path +### Retaining a Portable File Path -```zig -fn getFilePath(fd: std.posix.fd_t, allocator: Allocator) ![]u8 { - if (comptime !std.os.isGetFdPathSupportedOnTarget(builtin.os)) { - return error.Unsupported; - } - - var buf: [std.fs.max_path_bytes]u8 = undefined; - const path = try std.os.getFdPath(fd, &buf); - return try allocator.dupe(u8, path); -} -``` +If later logic needs both a file and its pathname, store an owned copy of the pathname when opening the file. Trying to reconstruct a canonical path from a descriptor is target-specific and can be ambiguous after rename, unlink, mount, or namespace changes. diff --git a/references/std-priority-dequeue.md b/references/std-priority-dequeue.md index 8fd3c4d..9df81e8 100644 --- a/references/std-priority-dequeue.md +++ b/references/std-priority-dequeue.md @@ -7,12 +7,12 @@ Zig 0.16 changed priority dequeues to align with unmanaged containers: - Initialize with `.empty`. - `add` -> `push`. - `addSlice` -> `pushSlice`. -- `addUnchecked` -> `pushUnchecked`. +- The old unchecked insertion helper has no public `pushUnchecked` replacement. - `removeMin` / `removeMinOrNull` -> `popMin`. - `removeMax` / `removeMaxOrNull` -> `popMax`. - `removeIndex` -> `popIndex`. -Old examples below may use removed 0.15 names; translate them before using in 0.16 code. +All examples below use the Zig 0.16 unmanaged API. `.empty` leaves `context` undefined; use `initContext` whenever the comparator reads it. A min-max heap that efficiently supports both min and max extraction. Unlike `PriorityQueue`, you can pop from either end. @@ -35,20 +35,20 @@ fn compare(context: void, a: u32, b: u32) std.math.Order { const PDQ = std.PriorityDequeue(u32, void, compare); -var dequeue = PDQ.init(allocator, {}); -defer dequeue.deinit(); +var dequeue = PDQ.initContext({}); +defer dequeue.deinit(allocator); ``` ## Basic Operations ```zig // Add elements -try dequeue.add(54); -try dequeue.add(12); -try dequeue.add(7); +try dequeue.push(allocator, 54); +try dequeue.push(allocator, 12); +try dequeue.push(allocator, 7); // Add multiple -try dequeue.addSlice(&[_]u32{ 1, 2, 3 }); +try dequeue.pushSlice(allocator, &[_]u32{ 1, 2, 3 }); // Peek at min/max (doesn't remove) if (dequeue.peekMin()) |min| { @@ -59,12 +59,8 @@ if (dequeue.peekMax()) |max| { } // Remove min/max -const min = dequeue.removeMin(); // asserts non-empty -const max = dequeue.removeMax(); // asserts non-empty - -// Safe removal (returns null if empty) -const maybe_min = dequeue.removeMinOrNull(); -const maybe_max = dequeue.removeMaxOrNull(); +const maybe_min = dequeue.popMin(); // ?T; null if empty +const maybe_max = dequeue.popMax(); // ?T; null if empty // Size const n = dequeue.count(); @@ -76,21 +72,22 @@ const cap = dequeue.capacity(); ```zig // Take ownership of slice, heapify in place var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 }); -var dequeue = PDQ.fromOwnedSlice(allocator, items, {}); -defer dequeue.deinit(); +var dequeue = PDQ.fromOwnedSlice(items, {}); +defer dequeue.deinit(allocator); ``` ## Update Priority ```zig try dequeue.update(old_value, new_value); -// Error if old_value not found +// Lookup uses comparator equality. Equal-priority duplicates are ambiguous; +// the API does not identify a particular equal element. Errors if none exists. ``` ## Remove by Index ```zig -const removed = dequeue.removeIndex(index); +const removed = dequeue.popIndex(index); // asserts in bounds; heap index is not priority rank ``` ## Iteration @@ -107,9 +104,9 @@ it.reset(); ## Capacity Management ```zig -try dequeue.ensureTotalCapacity(100); -try dequeue.ensureUnusedCapacity(10); -dequeue.shrinkAndFree(new_capacity); +try dequeue.ensureTotalCapacity(allocator, 100); +try dequeue.ensureUnusedCapacity(allocator, 10); +dequeue.shrinkAndFree(allocator, new_capacity); ``` ## Context-Based Comparator @@ -122,7 +119,8 @@ fn compareByScore(scores: []const u32, a: usize, b: usize) std.math.Order { const IndexPDQ = std.PriorityDequeue(usize, []const u32, compareByScore); const scores = [_]u32{ 50, 30, 80, 20 }; -var dequeue = IndexPDQ.init(allocator, &scores); +var dequeue = IndexPDQ.initContext(scores[0..]); +defer dequeue.deinit(allocator); ``` ## Complete Example: Bounded Range Tracker @@ -140,15 +138,16 @@ pub fn main() !void { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); - var tracker = RangePDQ.init(gpa.allocator(), {}); - defer tracker.deinit(); + const allocator = gpa.allocator(); + var tracker = RangePDQ.initContext({}); + defer tracker.deinit(allocator); // Add values - try tracker.add(10); - try tracker.add(5); - try tracker.add(20); - try tracker.add(3); - try tracker.add(15); + try tracker.push(allocator, 10); + try tracker.push(allocator, 5); + try tracker.push(allocator, 20); + try tracker.push(allocator, 3); + try tracker.push(allocator, 15); // Get range without removing const min = tracker.peekMin().?; // 3 @@ -158,8 +157,8 @@ pub fn main() !void { std.debug.print("Range: {} to {} = {}\n", .{ min, max, range }); // Pop from both ends - _ = tracker.removeMin(); // removes 3 - _ = tracker.removeMax(); // removes 20 + _ = tracker.popMin(); // removes 3 + _ = tracker.popMax(); // removes 20 // New range is 5 to 15 } @@ -177,7 +176,7 @@ pub fn main() !void { ## Notes -- Both `removeMin()` and `removeMax()` are O(log n) -- `peekMin()` is O(1), `peekMax()` is O(1) after first 2 elements +- Both `popMin()` and `popMax()` are O(log n) +- Both nullable peeks are O(1), including empty and one-element deques - Iterator order is heap array order, not priority order - Use when you need efficient access to both extremes diff --git a/references/std-priority-queue.md b/references/std-priority-queue.md index 8d86de0..50eef16 100644 --- a/references/std-priority-queue.md +++ b/references/std-priority-queue.md @@ -8,12 +8,12 @@ Zig 0.16 changed priority queues to align with unmanaged containers: - Empty queues can use `.empty`. - `init` -> `initContext` when context is needed. - `add` -> `push`. -- `addUnchecked` -> `pushUnchecked`. +- The old unchecked insertion helper has no public `pushUnchecked` replacement; reserve and use supported public operations. - `addSlice` -> `pushSlice`. - `remove` / `removeOrNull` -> `pop`. - `removeIndex` -> `popIndex`. -Old examples below may use removed 0.15 names; translate them before using in 0.16 code. +All examples below use the Zig 0.16 unmanaged API. A binary heap-based priority queue. Efficiently retrieves elements by priority order. @@ -37,8 +37,8 @@ fn lessThan(context: void, a: u32, b: u32) std.math.Order { const PQ = std.PriorityQueue(u32, void, lessThan); -var queue = PQ.init(allocator, {}); -defer queue.deinit(); +var queue = PQ.initContext({}); +defer queue.deinit(allocator); ``` ## Max-Heap @@ -56,12 +56,12 @@ const MaxPQ = std.PriorityQueue(u32, void, greaterThan); ```zig // Add elements -try queue.add(54); -try queue.add(12); -try queue.add(7); +try queue.push(allocator, 54); +try queue.push(allocator, 12); +try queue.push(allocator, 7); // Add multiple -try queue.addSlice(&[_]u32{ 1, 2, 3 }); +try queue.pushSlice(allocator, &[_]u32{ 1, 2, 3 }); // Peek at highest priority (doesn't remove) if (queue.peek()) |top| { @@ -69,8 +69,7 @@ if (queue.peek()) |top| { } // Remove highest priority -const top = queue.remove(); // asserts non-empty -const maybe = queue.removeOrNull(); // returns ?T +const maybe_top = queue.pop(); // ?T; null when empty // Size const n = queue.count(); @@ -82,8 +81,8 @@ const cap = queue.capacity(); ```zig // Take ownership of slice, heapify in place var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 }); -var queue = PQ.fromOwnedSlice(allocator, items, {}); -defer queue.deinit(); +var queue = PQ.fromOwnedSlice(items, {}); +defer queue.deinit(allocator); // Now queue is a valid heap ``` @@ -92,14 +91,15 @@ defer queue.deinit(); ```zig // Change priority of existing element try queue.update(old_value, new_value); -// Error if old_value not found +// Selection uses comparator equality. If duplicates compare equal, which one +// is updated is not a stable identity guarantee. Errors if no equal value exists. ``` ## Remove by Index ```zig // Remove element at specific position (not priority order) -const removed = queue.removeIndex(index); +const removed = queue.popIndex(index); // asserts index < count; heap index is not priority rank ``` ## Iteration (Non-Priority Order) @@ -113,14 +113,16 @@ while (it.next()) |elem| { it.reset(); // restart iteration ``` +Any queue mutation invalidates the iterator. + ## Capacity Management ```zig -try queue.ensureTotalCapacity(100); -try queue.ensureUnusedCapacity(10); -queue.shrinkAndFree(new_capacity); +try queue.ensureTotalCapacity(allocator, 100); +try queue.ensureUnusedCapacity(allocator, 10); +queue.shrinkAndFree(allocator, new_capacity); queue.clearRetainingCapacity(); -queue.clearAndFree(); +queue.clearAndFree(allocator); ``` ## Context-Based Comparator @@ -135,16 +137,16 @@ fn compareByScore(scores: []const u32, a: usize, b: usize) std.math.Order { const IndexPQ = std.PriorityQueue(usize, []const u32, compareByScore); const scores = [_]u32{ 50, 30, 80, 20 }; -var queue = IndexPQ.init(allocator, &scores); -defer queue.deinit(); +var queue = IndexPQ.initContext(scores[0..]); +defer queue.deinit(allocator); -try queue.add(0); // score 50 -try queue.add(1); // score 30 -try queue.add(2); // score 80 -try queue.add(3); // score 20 +try queue.push(allocator, 0); // score 50 +try queue.push(allocator, 1); // score 30 +try queue.push(allocator, 2); // score 80 +try queue.push(allocator, 3); // score 20 // Removes index 3 (score 20 is smallest) -const best = queue.remove(); // 3 +const best = queue.pop().?; // 3 ``` ## Complete Example: Task Scheduler @@ -167,14 +169,15 @@ pub fn main() !void { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); - var tasks = TaskQueue.init(gpa.allocator(), {}); - defer tasks.deinit(); + const allocator = gpa.allocator(); + var tasks = TaskQueue.initContext({}); + defer tasks.deinit(allocator); - try tasks.add(.{ .name = "low priority", .priority = 100 }); - try tasks.add(.{ .name = "urgent", .priority = 1 }); - try tasks.add(.{ .name = "medium", .priority = 50 }); + try tasks.push(allocator, .{ .name = "low priority", .priority = 100 }); + try tasks.push(allocator, .{ .name = "urgent", .priority = 1 }); + try tasks.push(allocator, .{ .name = "medium", .priority = 50 }); - while (tasks.removeOrNull()) |task| { + while (tasks.pop()) |task| { std.debug.print("Processing: {s}\n", .{task.name}); } // Output: @@ -187,6 +190,6 @@ pub fn main() !void { ## Notes - Heap property: parent has higher priority than children -- `remove()` is O(log n), `peek()` is O(1) +- `pop()` is nullable and O(log n); `peek()` is nullable and O(1) - Iterator order is NOT priority order (it's heap array order) -- Use `removeOrNull()` for safe extraction from potentially empty queue +- Use `pop()` for extraction from a potentially empty queue diff --git a/references/std-process.md b/references/std-process.md index 454e813..1caa298 100644 --- a/references/std-process.md +++ b/references/std-process.md @@ -78,6 +78,10 @@ const cwd = try std.process.currentPathAlloc(io, gpa); defer gpa.free(cwd); ``` +On Windows these paths use WTF-8. On other platforms they are opaque path bytes +with no guaranteed text encoding; do not assume UTF-8 when displaying or +parsing them. + Do not add new `std.process.getCwd*` callsites. ## Run and Capture Output @@ -113,6 +117,10 @@ Important options: - `disable_aslr` - `timeout` +Supplying `.environ_map` replaces the child's environment, but its `PATH` does +not resolve `argv[0]`; executable lookup still uses the parent environment. +Use `std.process.spawnPath` for deterministic directory-relative resolution. + ## Spawn Child Process Use `std.process.spawn(io, options)`. `std.process.Child.init` is not the 0.16 pattern. @@ -214,6 +222,8 @@ Memory locking/protection APIs moved under `std.process`: - `std.process.unlockMemory` - `std.process.lockMemoryAll` - `std.process.unlockMemoryAll` +- `std.process.MemoryProtection` +- `std.process.protectMemory` Use them only for explicit platform/security needs. diff --git a/references/std-random.md b/references/std-random.md index 9591286..fdebb6c 100644 --- a/references/std-random.md +++ b/references/std-random.md @@ -6,7 +6,7 @@ Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/releas ## Zig 0.16 Entropy Rule -System entropy moved under `std.Io`. +Random bytes moved under `std.Io`. `io.random` is the process CSPRNG stream and may use a less-secure seeding fallback on platforms where fresh system entropy is unavailable. `io.randomSecure` requests fresh external entropy and reports failure. ```zig var seed: [32]u8 = undefined; @@ -18,7 +18,7 @@ const rng = source.interface(); Use `io.randomSecure(&bytes)` when fresh OS-backed secure entropy is required and errors must be surfaced. -Old examples below may mention `std.crypto.random`; use the `std.Io` patterns above in new 0.16 code. +The examples below use those `std.Io` patterns for current Zig 0.16 code. ## Quick Reference @@ -37,10 +37,8 @@ Old examples below may mention `std.crypto.random`; use the `std.Io` patterns ab ``` Need crypto security? -├─ Yes → ChaCha (DefaultCsprng) or Ascon -└─ No → Need speed? - ├─ Yes → Xoshiro256 (DefaultPrng), Sfc64, or RomuTrio - └─ No → Pcg (smaller state), Xoroshiro128 +|- Yes -> use `io.random`/`io.randomSecure`, or seed ChaCha/Ascon securely +`- No -> use Xoshiro256 (DefaultPrng), Sfc64, RomuTrio, or Pcg ``` | PRNG | State | Output | Use Case | @@ -50,8 +48,8 @@ Need crypto security? | `Pcg` | 128-bit | 32-bit | Compact, statistically excellent | | `Sfc64` | 256-bit | 64-bit | Very fast | | `RomuTrio` | 192-bit | 64-bit | Fast, small code size | -| `Isaac64` | 8KB | 64-bit | Cryptographic-ish (prefer ChaCha) | -| `ChaCha` | 512-bit | stream | CSPRNG, forward secure | +| `Isaac64` | 8KB | 64-bit | Non-default PRNG; do not select it as the documented CSPRNG path | +| `ChaCha` | Internal state | stream | CSPRNG, forward secure | | `Ascon` | 320-bit | stream | CSPRNG, lightweight | ## Basic Usage @@ -79,14 +77,13 @@ pub fn main() void { ```zig const std = @import("std"); -pub fn main() void { - // Use std.crypto.random for system entropy - const secure = std.crypto.random; - +pub fn main(init: std.process.Init) void { + const io = init.io; var key: [32]u8 = undefined; - secure.bytes(&key); // fill with cryptographically secure random bytes + io.random(&key); // process CSPRNG stream - const token = secure.int(u64); + var source: std.Random.IoSource = .{ .io = io }; + const token = source.interface().int(u64); } ``` @@ -94,7 +91,7 @@ pub fn main() void { ```zig var seed: u64 = undefined; -std.crypto.random.bytes(std.mem.asBytes(&seed)); +io.random(std.mem.asBytes(&seed)); var prng = std.Random.DefaultPrng.init(seed); ``` @@ -115,7 +112,7 @@ prng.jump(); ```zig // Requires 32-byte secret seed var secret_seed: [std.Random.ChaCha.secret_seed_length]u8 = undefined; -std.crypto.random.bytes(&secret_seed); +try io.randomSecure(&secret_seed); // or io.random when process-CSPRNG semantics suffice var csprng = std.Random.ChaCha.init(secret_seed); const random = csprng.random(); @@ -257,10 +254,13 @@ const int_weights = [_]u32{ 5, 3, 2 }; const int_choice = random.weightedIndex(u32, &int_weights); ``` +Weights must be finite/nonnegative for floating-point use (or valid nonnegative integer weights), their sum must be positive, and accumulation must remain representable. Validate untrusted weight arrays before calling. + ### Random Element from Slice ```zig fn randomElement(comptime T: type, random: std.Random, slice: []const T) T { + std.debug.assert(slice.len != 0); const index = random.uintLessThan(usize, slice.len); return slice[index]; } @@ -272,10 +272,11 @@ const color = randomElement([]const u8, random, &colors); ### Random Sample (Without Replacement) ```zig -fn sample(comptime T: type, random: std.Random, source: []const T, dest: []T) void { +fn sample(comptime T: type, random: std.Random, source: []const T, dest: []T, indices: []usize) void { + std.debug.assert(dest.len <= source.len); + std.debug.assert(indices.len >= source.len); // Fisher-Yates partial shuffle - var indices: [source.len]usize = undefined; - for (&indices, 0..) |*idx, i| idx.* = i; + for (indices[0..source.len], 0..) |*idx, i| idx.* = i; for (dest, 0..) |*d, i| { const j = random.intRangeLessThan(usize, i, source.len); @@ -304,10 +305,10 @@ std.debug.assert(prng1.random().int(u64) == prng2.random().int(u64)); ```zig threadlocal var tls_prng: ?std.Random.DefaultPrng = null; -fn getThreadRandom() std.Random { +fn getThreadRandom(io: std.Io) std.Random { if (tls_prng == null) { var seed: u64 = undefined; - std.crypto.random.bytes(std.mem.asBytes(&seed)); + io.random(std.mem.asBytes(&seed)); tls_prng = std.Random.DefaultPrng.init(seed); } return tls_prng.?.random(); @@ -318,6 +319,7 @@ fn getThreadRandom() std.Random { ```zig fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator) ![]std.Random.Xoshiro256 { + std.debug.assert(n != 0); const prngs = try allocator.alloc(std.Random.Xoshiro256, n); prngs[0] = std.Random.Xoshiro256.init(base_seed); @@ -330,6 +332,8 @@ fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator) } ``` +The caller owns the returned slice and must free it with the same allocator. + ### Monte Carlo Simulation ```zig @@ -357,7 +361,8 @@ fn generatePassword(random: std.Random, buf: []u8) void { // Usage var password: [16]u8 = undefined; -generatePassword(std.crypto.random, &password); +var source: std.Random.IoSource = .{ .io = io }; +generatePassword(source.interface(), &password); ``` ### Gaussian Random with Box-Muller @@ -406,7 +411,7 @@ const MyPrng = struct { - `DefaultPrng` is `Xoshiro256` - fast, high quality, not cryptographic - `DefaultCsprng` is `ChaCha` - cryptographically secure with forward secrecy -- For crypto: use `std.crypto.random` which provides system entropy +- For crypto: use caller-provided `std.Io` randomness, choosing `randomSecure` when fresh external entropy and explicit errors are required - `uintLessThan`/`intRangeLessThan` may reject values (not constant-time) - Use biased variants (`*Biased`) for timing-sensitive applications - `jump()` on Xoshiro256 advances 2^128 steps for parallel streams diff --git a/references/std-segmented-list.md b/references/std-segmented-list.md index d70e4cb..c9b5b3e 100644 --- a/references/std-segmented-list.md +++ b/references/std-segmented-list.md @@ -1,10 +1,10 @@ -# std.SegmentedList - removed in Zig 0.16.0 +# Historical std.SegmentedList Reference (Unavailable in Zig 0.16.0) Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html -`std.SegmentedList` was removed in Zig 0.16. Do not use the API below in new code. Choose a project-owned segmented container, an arena-backed structure, an index-based `std.ArrayList`, or another stable-handle design depending on the ownership/lifetime requirement. +`std.SegmentedList` is not exported by the Zig 0.16 standard library, and there is no compatibility alias with these methods. The material below is retained as a **legacy API inventory** for migration and source archaeology; none of its snippets compile against Zig 0.16 as written. Choose a project-owned segmented container, an arena-backed structure, an index-based `std.ArrayList`, or another stable-handle design depending on the ownership/lifetime requirement. -A dynamic list where element pointers remain stable across growth. Unlike ArrayList, appending never invalidates existing pointers. Elements are stored in exponentially-sized segments. +Historically, the implementation described here was a dynamic list whose element pointers remained stable across growth. Unlike `ArrayList`, appending did not invalidate existing pointers, and elements were stored in exponentially sized segments. These are historical behavior and layout notes, not Zig 0.16 guarantees. ## When to Use @@ -15,11 +15,11 @@ A dynamic list where element pointers remain stable across growth. Unlike ArrayL ## Trade-offs -- Elements not contiguous (most are, but not guaranteed) +- Elements were not globally contiguous; each individual segment was contiguous - O(log n) random access (vs O(1) for ArrayList) - Higher per-element overhead -## Initialization +## Legacy Initialization ```zig // Without preallocation @@ -32,7 +32,7 @@ var list = std.SegmentedList(i32, 16){}; defer list.deinit(allocator); ``` -## Basic Operations +## Legacy Basic Operations ```zig // Append (pointer remains valid forever) @@ -84,7 +84,7 @@ if (it.peek()) |ptr| { it.set(50); ``` -## Capacity Management +## Legacy Capacity Management ```zig // Grow capacity @@ -125,7 +125,7 @@ prealloc=4: prealloc: 4 elements (inline) ... ``` -## Common Pattern: Object Pool with Stable References +## Legacy Pattern: Object Pool with Stable References ```zig const Object = struct { diff --git a/references/std-simd.md b/references/std-simd.md index 2c53f9f..534de16 100644 --- a/references/std-simd.md +++ b/references/std-simd.md @@ -171,7 +171,7 @@ const any_true = @reduce(.Or, mask); // true ### suggestVectorLength -Query the optimal vector length for the current CPU: +Query the suggested vector length for the current target. This is a comptime heuristic: its element type must be comptime-known. ```zig const std = @import("std"); @@ -187,7 +187,7 @@ Returns `null` if scalars are recommended (no SIMD benefit). ### suggestVectorLengthForCpu -Query optimal length for a specific CPU target: +Query the suggested length for a specific CPU target. Both the element type and `std.Target.Cpu` argument are comptime parameters: ```zig const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2; @@ -196,7 +196,7 @@ const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2; **Architecture support:** - **x86**: SSE (128-bit), AVX2 (256-bit), AVX-512 (512-bit) - **ARM**: NEON (128-bit) -- **AArch64**: NEON (128-bit), SVE (128-bit default) +- **AArch64**: SVE (256-bit heuristic), otherwise NEON (128-bit) - **RISC-V**: V extension (32-bit to 65536-bit via zvl* features) - **WebAssembly**: simd128 (128-bit) - **PowerPC**: AltiVec (128-bit) @@ -289,7 +289,7 @@ const result = std.simd.deinterlace(2, interleaved); ### extract - Get Subvector -Extract a contiguous slice of elements: +Extract a contiguous slice of elements. `first` and `count` are comptime-known: ```zig const vec: @Vector(8, u32) = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; @@ -299,7 +299,7 @@ const slice = std.simd.extract(vec, 2, 3); ### shiftElementsLeft / shiftElementsRight -Shift elements, filling with a value: +Shift elements, filling with a value. The shift amount is comptime-known: ```zig const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 }; @@ -315,7 +315,7 @@ const right = std.simd.shiftElementsRight(vec, 2, 999); ### rotateElementsLeft / rotateElementsRight -Circular rotation (elements wrap around): +Circular rotation (elements wrap around). The rotation amount is comptime-known: ```zig const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 }; @@ -339,7 +339,7 @@ const reversed = std.simd.reverseOrder(vec); ### mergeShift -Combine two vectors and extract a shifted window: +Combine two vectors and extract a shifted window. The shift amount is comptime-known: ```zig const a: @Vector(4, u32) = .{ 1, 2, 3, 4 }; @@ -400,7 +400,7 @@ const count = std.simd.countElementsWithValue(vec, 4); // 3 ### prefixScan -Compute cumulative operations across vector lanes: +Compute cumulative operations across vector lanes. This is an O(log N), non-linear associative scan; floating-point results can differ from a scalar left-to-right scan because the grouping and rounding differ. ```zig const vec: @Vector(4, i32) = .{ 11, 23, 9, -21 }; @@ -443,7 +443,7 @@ const rev = std.simd.prefixScan(.Add, -1, vec); ### prefixScanWithFunc -Use a custom associative function: +Use a custom associative function. The callback may return an error union; choose `ErrorType` accordingly and use `try` on `prefixScanWithFunc` to propagate callback errors. Passing `void` selects the non-erroring form: ```zig fn myMax(a: @Vector(4, f32), b: @Vector(4, f32)) @Vector(4, f32) { @@ -576,11 +576,11 @@ fn atan2Simd(y: @Vector(4, f64), x: @Vector(4, f64)) @Vector(4, f64) { ## Performance Notes - **Optimal vector size:** Use `suggestVectorLength` for portable code; don't hardcode lane counts. Powers of two (2-64) are most efficient -- **Compilation:** Short vectors → single SIMD instruction; long vectors → multiple instructions; no SIMD → scalar fallback +- **Compilation:** Lowering depends on the target, vector width, operation, and optimizer; inspect generated code for performance-critical paths - **Alignment:** Vectors are automatically aligned; use `@alignCast` when loading from byte pointers - **Branching:** Replace scalar branches with `@select` for branchless SIMD code. `and`/`or` keywords don't work on bool vectors - **Reductions:** `@reduce` operations break SIMD parallelism; minimize their use in hot paths - **Memory layout:** Prefer Struct-of-Arrays over Array-of-Structs for better vectorization - **Cache tiling:** For large datasets, process in cache-sized chunks (e.g., 64 elements) to maintain data locality -- **Fused operations:** Use `@mulAdd(a, b, c)` for `(a * b) + c` - rounds once, more accurate +- **Fused operations:** For floating-point `T`, use `@mulAdd(T, a, b, c)` for a fused `(a * b) + c` with one final rounding - **MIPS limitation:** `interlace` and `prefixScan` don't work on MIPS architecture diff --git a/references/std-sort.md b/references/std-sort.md index 718578a..c7474b9 100644 --- a/references/std-sort.md +++ b/references/std-sort.md @@ -1,14 +1,14 @@ -# std.sort +# std.sort (Zig 0.16.0) Sorting algorithms and binary search utilities. All sorts are in-place and require no allocator. ## Quick Reference -| Function | Stable | Complexity | When to Use | +| Function | Stable | Worst-case | When to Use | |----------|--------|------------|-------------| | `block` | Yes | O(n log n) | Default choice when stability matters | | `pdq` | No | O(n log n) | Default choice when stability doesn't matter | -| `insertion` | Yes | O(n²) | Small arrays (<20), nearly sorted data | +| `insertion` | Yes | O(n²) | Small arrays or nearly sorted data | | `heap` | No | O(n log n) | Guaranteed worst-case, no recursion | ## Comparator Functions @@ -33,7 +33,7 @@ const desc_i32 = std.sort.desc(i32); // descending ```zig var items = [_]i32{ 5, 2, 8, 1, 9 }; -// Unstable sort (fastest general-purpose) +// General-purpose unstable sort std.sort.pdq(i32, &items, {}, std.sort.asc(i32)); // items = [1, 2, 5, 8, 9] @@ -188,7 +188,7 @@ var items = [_]i32{ 5, 2, 8, 1 }; const ctx = Context{ .items = &items }; // Sort a subrange using indices -std.sort.pdqContext(1, 4, ctx); // sort indices 1..4 +std.sort.pdqContext(1, 4, ctx); // sort end-exclusive range [1, 4): indices 1, 2, 3 // items = [5, 1, 2, 8] ``` @@ -220,15 +220,19 @@ std.sort.block(Item, &items, {}, byPriority); ## Algorithm Selection -- **`pdq`** (Pattern-Defeating Quicksort): Best general-purpose unstable sort. Adapts to input patterns, falls back to heapsort for worst cases. -- **`block`**: Best general-purpose stable sort. Preserves relative order of equal elements. +- **`pdq`** (Pattern-Defeating Quicksort): General-purpose unstable sort. Adapts to input patterns, falls back to heapsort for worst cases. +- **`block`**: General-purpose stable sort. Preserves relative order of equal elements. - **`insertion`**: O(n) on nearly sorted data. Use for small arrays or as final pass. - **`heap`**: Guaranteed O(n log n) with O(1) memory. No recursion, predictable performance. ## Notes - All sorts are **in-place** with O(1) or O(log n) auxiliary memory -- Comparators must define strict weak ordering (if `a < b` then not `b < a`) +- Comparators must define a strict weak ordering: irreflexive and transitive, + with a transitive equivalence relation for elements where neither compares + less than the other. `block` is limited to less-than/greater-than style + comparisons as documented by that algorithm. - `asc`/`desc` helpers work with any type supporting `<` operator - Binary search functions require the array to already be sorted -- `equalRange` is more efficient than calling `lowerBound` + `upperBound` separately +- `equalRange` returns both bounds in O(log n); do not assume it is faster than + calling the two bound helpers without measurement. diff --git a/references/std-static-string-map.md b/references/std-static-string-map.md index cb69161..f5cd5cf 100644 --- a/references/std-static-string-map.md +++ b/references/std-static-string-map.md @@ -1,6 +1,6 @@ # std.StaticStringMap -Compile-time optimized string lookup. Perfect hash for small, fixed sets of string keys. +Compile-time optimized lookup for small, fixed sets of string keys. Keys are grouped by length; lookup quickly rejects absent lengths and then compares candidates in the matching length group. This is not a perfect hash. ## When to Use @@ -8,7 +8,7 @@ Compile-time optimized string lookup. Perfect hash for small, fixed sets of stri - Command/option parsing - Static configuration keys - When string set is known at compile time -- Very fast O(1) lookups by string length +- Fast length-based narrowing, followed by a linear scan of keys with the queried length (plus string comparison cost) ## Basic Usage @@ -52,7 +52,7 @@ if (reserved.has("break")) { } ``` -## Case-Insensitive Lookup +## ASCII Case-Insensitive Lookup ```zig const commands = std.StaticStringMapWithEql( @@ -86,6 +86,8 @@ defer map.deinit(allocator); _ = map.get("two"); // 2 ``` +Runtime initialization copies the entry arrays and key slices, but not the bytes behind each key slice. Keep the key backing storage alive and unchanged for the lifetime of the map. + ## Get Index ```zig diff --git a/references/std-tar.md b/references/std-tar.md index ddc1926..7b4dc04 100644 --- a/references/std-tar.md +++ b/references/std-tar.md @@ -4,7 +4,7 @@ Tar archive reading and writing. Zig 0.16 file and stream APIs use `std.Io.Dir`, Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html -Old examples below may contain 0.15 `std.fs` or `std.io` patterns; translate them to `std.Io` before using in new code. The 0.16 release notes also call out path traversal sanitization in `tar.extract`. +The examples below use Zig 0.16's explicit `std.Io` APIs. `tar.extract` also sanitizes archive paths to prevent traversal outside the destination. ## Table of Contents - [Module Structure](#module-structure) @@ -21,8 +21,8 @@ std.tar.Iterator // Iterate over entries in tar archive std.tar.Writer // Create tar archives std.tar.Diagnostics // Collect errors during extraction std.tar.FileKind // .file, .directory, .sym_link -std.tar.PipeOptions // Options for pipeToFileSystem -std.tar.pipeToFileSystem() // Extract archive to directory +std.tar.ExtractOptions // Options for extract +std.tar.extract() // Extract archive to directory ``` ## Reading Tar Archives @@ -36,8 +36,8 @@ const data = @embedFile("archive.tar"); var reader: std.Io.Reader = .fixed(data); // Buffers must be provided by caller -var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; -var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; +var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; +var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var it: std.tar.Iterator = .init(&reader, .{ .file_name_buffer = &file_name_buffer, @@ -70,16 +70,16 @@ pub const File = struct { ### Reading File Contents -File content must be read before calling `next()` again: +Read or copy file content before calling `next()` again if the content is needed. If content remains unread, `next()` discards it while advancing rather than failing: ```zig while (try it.next()) |file| { if (file.kind == .file) { // Option 1: Stream to writer var buf: [1024]u8 = undefined; - var output_file = try dir.createFile(file.name, .{}); - defer output_file.close(); - var file_writer = output_file.writer(&buf); + var output_file = try dir.createFile(io, file.name, .{}); + defer output_file.close(io); + var file_writer = output_file.writer(io, &buf); try it.streamRemaining(file, &file_writer.interface); try file_writer.interface.flush(); @@ -104,7 +104,7 @@ pub const Options = struct { ## Extracting to Filesystem -### pipeToFileSystem +### extract Extract entire archive to a directory: @@ -112,7 +112,7 @@ Extract entire archive to a directory: const data = @embedFile("archive.tar"); var reader: std.Io.Reader = .fixed(data); -try std.tar.pipeToFileSystem(std.fs.cwd(), &reader, .{ +try std.tar.extract(io, std.Io.Dir.cwd(), &reader, .{ .strip_components = 1, // remove leading path component .mode_mode = .executable_bit_only, .exclude_empty_directories = false, @@ -122,19 +122,19 @@ try std.tar.pipeToFileSystem(std.fs.cwd(), &reader, .{ ### From File ```zig -const file = try std.fs.cwd().openFile("archive.tar", .{}); -defer file.close(); +const file = try std.Io.Dir.cwd().openFile(io, "archive.tar", .{}); +defer file.close(io); var buf: [4096]u8 = undefined; -var file_reader = file.reader(&buf); +var file_reader = file.reader(io, &buf); -try std.tar.pipeToFileSystem(output_dir, &file_reader.interface, .{}); +try std.tar.extract(io, output_dir, &file_reader.interface, .{}); ``` -### PipeOptions +### ExtractOptions ```zig -pub const PipeOptions = struct { +pub const ExtractOptions = struct { strip_components: u32 = 0, // directories to strip from paths mode_mode: ModeMode = .executable_bit_only, exclude_empty_directories: bool = false, @@ -180,24 +180,26 @@ try w.writeLink("latest", "v1.0", .{}); const tar_bytes = output.written(); ``` +`setRoot` first emits a directory entry for a non-empty root and then applies that prefix to subsequent entries. + ### Writing from File ```zig -var output_file = try std.fs.cwd().createFile("archive.tar", .{}); -defer output_file.close(); +var output_file = try std.Io.Dir.cwd().createFile(io, "archive.tar", .{}); +defer output_file.close(io); var buf: [4096]u8 = undefined; -var file_writer = output_file.writer(&buf); +var file_writer = output_file.writer(io, &buf); var w: std.tar.Writer = .{ .underlying_writer = &file_writer.interface }; // Write file from disk -var src_file = try std.fs.cwd().openFile("data.txt", .{}); -defer src_file.close(); +var src_file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{}); +defer src_file.close(io); var src_buf: [4096]u8 = undefined; -var src_reader = src_file.reader(&src_buf); -const stat = try src_file.stat(); +var src_reader = src_file.reader(io, &src_buf); +const stat = try src_file.stat(io); -try w.writeFile("data.txt", &src_reader, stat.mtime); +try w.writeFileTimestamp("data.txt", &src_reader, stat.mtime); try file_writer.interface.flush(); ``` @@ -226,12 +228,16 @@ pub fn writeFileBytes(w: *Writer, sub_path: []const u8, content: []const u8, opt pub fn writeFileStream(w: *Writer, sub_path: []const u8, size: u64, reader: *std.Io.Reader, options: Options) WriteFileStreamError!void // Write file from file reader -pub fn writeFile(w: *Writer, sub_path: []const u8, file_reader: *std.fs.File.Reader, stat_mtime: i128) WriteFileError!void +pub fn writeFile(w: *Writer, sub_path: []const u8, file_reader: *std.Io.File.Reader, mtime_seconds: u64) WriteFileError!void + +// Convenience overload for std.Io.Timestamp +pub fn writeFileTimestamp(w: *Writer, sub_path: []const u8, file_reader: *std.Io.File.Reader, mtime: std.Io.Timestamp) WriteFileError!void // Write symbolic link pub fn writeLink(w: *Writer, sub_path: []const u8, link_name: []const u8, options: Options) Error!void -// Write two zero blocks (optional, per spec) +// Write two zero blocks. The stdlib recommends not calling this: readers must +// accept archives without them, and omitting them avoids unnecessary output. pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void ``` @@ -240,7 +246,7 @@ pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void ```zig pub const Options = struct { mode: u32 = 0, // POSIX mode (0 = default: 0o664 for files) - mtime: u64 = 0, // modification time (0 = current time) + mtime: u64 = 0, // seconds since POSIX epoch; zero is written as zero }; ``` @@ -254,7 +260,7 @@ Collect errors instead of failing immediately: var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator }; defer diagnostics.deinit(); -std.tar.pipeToFileSystem(dir, &reader, .{ +std.tar.extract(io, dir, &reader, .{ .diagnostics = &diagnostics, }) catch |err| { // Some errors are still fatal @@ -283,10 +289,12 @@ for (diagnostics.errors.items) |item| { std.debug.print("Root dir: {s}, entries: {d}\n", .{ diagnostics.root_dir, diagnostics.entries }); ``` -### Diagnostics.Error Types +### Diagnostics.Error Variants -```zig -pub const Error = union(enum) { +```text +// Descriptive shape only: the unsupported_file_type payload contains a +// private header-kind enum, so std.tar.Header.Kind is not a public type name. +union(enum) { unable_to_create_sym_link: struct { code: anyerror, file_name: []const u8, @@ -298,7 +306,7 @@ pub const Error = union(enum) { }, unsupported_file_type: struct { file_name: []const u8, - file_type: Header.Kind, + file_type: /* private archive header kind */, }, components_outside_stripped_prefix: struct { file_name: []const u8, @@ -311,13 +319,13 @@ pub const Error = union(enum) { ### Extract and Process Archive ```zig -fn extractTar(allocator: Allocator, tar_data: []const u8, dest: std.fs.Dir) !void { +fn extractTar(io: std.Io, allocator: Allocator, tar_data: []const u8, dest: std.Io.Dir) !void { var reader: std.Io.Reader = .fixed(tar_data); var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator }; defer diagnostics.deinit(); - try std.tar.pipeToFileSystem(dest, &reader, .{ + try std.tar.extract(io, dest, &reader, .{ .strip_components = 1, .diagnostics = &diagnostics, }); @@ -337,8 +345,8 @@ fn listTar(allocator: Allocator, tar_data: []const u8) !void { _ = allocator; var reader: std.Io.Reader = .fixed(tar_data); - var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; - var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; + var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var it: std.tar.Iterator = .init(&reader, .{ .file_name_buffer = &file_name_buffer, @@ -365,7 +373,7 @@ fn listTar(allocator: Allocator, tar_data: []const u8) !void { ### Create Archive from Directory ```zig -fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []const u8) ![]u8 { +fn createTarFromDir(io: std.Io, allocator: Allocator, source_dir: std.Io.Dir, root_name: []const u8) ![]u8 { var output: std.Io.Writer.Allocating = .init(allocator); errdefer output.deinit(); @@ -375,21 +383,21 @@ fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []c var walker = try source_dir.walk(allocator); defer walker.deinit(); - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { switch (entry.kind) { .directory => try w.writeDir(entry.path, .{}), .file => { - var file = try entry.dir.openFile(entry.basename, .{}); - defer file.close(); + var file = try entry.dir.openFile(io, entry.basename, .{}); + defer file.close(io); var buf: [4096]u8 = undefined; - var file_reader = file.reader(&buf); - const stat = try file.stat(); - try w.writeFile(entry.path, &file_reader, stat.mtime); + var file_reader = file.reader(io, &buf); + const stat = try file.stat(io); + try w.writeFileTimestamp(entry.path, &file_reader, stat.mtime); }, .sym_link => { - var link_buf: [std.fs.max_path_bytes]u8 = undefined; - const target = try entry.dir.readLink(entry.basename, &link_buf); - try w.writeLink(entry.path, target, .{}); + var link_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + const target_len = try entry.dir.readLink(io, entry.basename, &link_buf); + try w.writeLink(entry.path, link_buf[0..target_len], .{}); }, else => {}, // skip special files } @@ -405,8 +413,8 @@ fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []c fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocator) !?[]u8 { var reader: std.Io.Reader = .fixed(tar_data); - var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; - var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; + var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var it: std.tar.Iterator = .init(&reader, .{ .file_name_buffer = &file_name_buffer, @@ -427,10 +435,10 @@ fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocat ## Supported Features -**Formats**: POSIX ustar, GNU long name/link extensions, pax extended headers +**Formats**: A deliberately non-comprehensive reader/writer for POSIX ustar, GNU long name/link extensions, and selected PAX attributes (`path`, `linkpath`, and `size`). Global PAX headers are ignored. **Entry types**: Regular files, directories, symbolic links -**Not supported**: Hard links, device nodes, FIFOs, sparse files (logged via diagnostics) +**Not supported**: Hard links, device nodes, FIFOs, sparse files, and other special entries. With diagnostics supplied, unsupported entries can be recorded; without diagnostics, extraction returns `error.TarUnsupportedHeader`. **Path handling**: Automatic prefix/name splitting, GNU extended headers for paths > 256 bytes diff --git a/references/std-testing.md b/references/std-testing.md index 67c8319..0aea702 100644 --- a/references/std-testing.md +++ b/references/std-testing.md @@ -53,7 +53,7 @@ try testing.expectEqualSlices(u32, &[_]u32{1, 2, 3}, result_slice); // Sentinel-terminated slice equality try testing.expectEqualSentinel(u8, 0, expected_cstr, actual_cstr); -// Deep equality (recursively compares structs, arrays, pointers) +// Deep equality (recursively compares supported structs, arrays, and pointers) try testing.expectEqualDeep(expected_struct, actual_struct); // Float comparison (absolute tolerance) @@ -68,7 +68,7 @@ try testing.expectApproxEqRel(@as(f64, 100.0), result, 0.01); ```zig const Point = struct { x: i32, y: i32 }; -// expectEqual - compares by value for primitives, by identity for pointers +// expectEqual - recursively compares structs/arrays, but pointer and slice identity const p1 = Point{ .x = 1, .y = 2 }; const p2 = Point{ .x = 1, .y = 2 }; try testing.expectEqual(p1, p2); // OK - structs compared field-by-field @@ -78,11 +78,13 @@ const a = [_]u8{ 1, 2, 3 }; const b = [_]u8{ 1, 2, 3 }; // testing.expectEqual(&a, &b); // FAILS - different pointers -// expectEqualDeep - follows pointers, compares contents +// expectEqualDeep - follows single-item pointers and compares supported contents try testing.expectEqualDeep(&a, &b); // OK - compares contents try testing.expectEqualDeep("abc", "abc"); // OK ``` +`expectEqualDeep` is not cycle-aware: self-referential values can recurse indefinitely. C pointers, many-item pointers, function pointers, and opaque pointers are compared by identity rather than dereferenced. + ## Error Assertions ```zig @@ -164,7 +166,7 @@ std.debug.print("Deallocations: {}\n", .{failing.deallocations}); ## Exhaustive Allocation Failure Testing -`checkAllAllocationFailures` tests that your code handles `OutOfMemory` at every allocation point without leaking: +`checkAllAllocationFailures` tests that your code handles `OutOfMemory` at every allocation point without leaking. The tested function must take an allocator as its first argument and return `!void`; reset any shared state between runs: ```zig fn myFunction(allocator: std.mem.Allocator, size: usize) !void { @@ -190,11 +192,13 @@ test "no leaks on allocation failure" { 2. Runs N more times, failing allocation 0, then 1, then 2... 3. Verifies `OutOfMemory` is returned and no memory leaked -**Errors returned:** +**Harness-specific errors include:** - `error.MemoryLeakDetected` - allocation failed but memory wasn't freed - `error.SwallowedOutOfMemoryError` - `OutOfMemory` was caught but not propagated - `error.NondeterministicMemoryUsage` - allocation count varies between runs +Other errors returned by the tested function are propagated unchanged. + ## Temporary Directory Create an isolated temp directory for file system tests: @@ -205,12 +209,18 @@ test "file operations" { defer tmp.cleanup(); // Write and read files - var file = try tmp.dir.createFile("test.txt", .{}); - defer file.close(); - try file.writeAll("hello"); + const io = std.testing.io; + { + const file = try tmp.dir.createFile(io, "test.txt", .{}); + defer file.close(io); + var write_buf: [256]u8 = undefined; + var file_writer = file.writer(io, &write_buf); + try file_writer.interface.writeAll("hello"); + try file_writer.interface.flush(); + } // Use tmp.dir for all operations - const content = try tmp.dir.readFileAlloc(std.testing.allocator, "test.txt", 1024); + const content = try tmp.dir.readFileAlloc(io, "test.txt", std.testing.allocator, .limited(1024)); defer std.testing.allocator.free(content); try testing.expectEqualStrings("hello", content); } @@ -237,10 +247,9 @@ comptime { std.testing.refAllDecls(@This()); } -// Recursive version for nested types -comptime { - std.testing.refAllDeclsRecursive(@This()); -} +// refAllDecls visits the immediate declarations of the supplied type. +// Zig 0.16 has no std.testing.refAllDeclsRecursive helper; recurse through +// selected nested types explicitly when that is part of the test's intent. ``` ## Skip Tests @@ -265,7 +274,7 @@ test "skip if feature unavailable" { ```zig test "with logging" { - // Only shown when test fails or with --verbose + // std.debug.print writes directly to stderr; it is not gated by log_level. std.debug.print("Debug info: {}\n", .{value}); } @@ -294,8 +303,9 @@ test "fuzz parser" { try std.testing.fuzz( {}, // context (passed to test function) struct { - fn testOne(_: void, input: []const u8) !void { - // This runs with many different inputs + fn testOne(_: void, smith: *std.testing.Smith) !void { + var storage: [4096]u8 = undefined; + const input = storage[0..smith.slice(&storage)]; _ = myParser.parse(input) catch |err| switch (err) { error.InvalidInput => return, // expected else => return err, @@ -376,6 +386,5 @@ test "arena for test allocations" { zig build test # Run all tests zig test src/lib.zig # Test single file zig test --test-filter "name" # Filter by name substring -zig test -fsummary # Show test summary -zig test --verbose # Show debug output +zig test --help # List options supported by this Zig version ``` diff --git a/references/std-thread.md b/references/std-thread.md index 6f11a36..297192e 100644 --- a/references/std-thread.md +++ b/references/std-thread.md @@ -26,7 +26,6 @@ Useful thread utilities: ```zig const id = std.Thread.getCurrentId(); const cpu_count = std.Thread.getCpuCount() catch 1; -std.Thread.sleep(10 * std.time.ns_per_ms); std.Thread.yield() catch {}; ``` @@ -49,7 +48,7 @@ Release-note migration map: | `std.Thread.RwLock` | `std.Io.RwLock` | | `std.Thread.ResetEvent` | `std.Io.Event` | | `std.Thread.WaitGroup` | `std.Io.Group` | -| `std.Thread.Futex` | `std.Io.Futex` | +| `std.Thread.Futex` operations | `io.futexWait` / `io.futexWaitTimeout` / `io.futexWaitUncancelable` / `io.futexWake` | Lock-free atomics do not need `std.Io`. @@ -68,7 +67,9 @@ fn increment(io: std.Io) !void { } ``` -Use uncancelable locking for short critical sections where interruption would corrupt state or skip required cleanup: +Use uncancelable locking when waiting to acquire the lock must not be a +cancelation point. Cancelation does not interrupt an already-entered critical +section: ```zig mutex.lockUncancelable(io); @@ -186,7 +187,8 @@ Rules: ## Application Guidance - Queues, allocators, registries, timers, and worker systems that use blocking sync should accept/store `std.Io`. -- Use `lockUncancelable(io)` for queue integrity and allocator metadata updates when cancellation cannot safely interrupt the critical section. +- Use `lockUncancelable(io)` only when waiting for queue or allocator locks must + not observe cancelation; keep the acquired critical section short. - Keep OS threads for code that is explicitly thread-owned, such as dedicated worker threads or external API callbacks. - Prefer an application's existing scheduler for application work unless the standard `std.Io` task API is explicitly the better fit. diff --git a/references/std-time.md b/references/std-time.md index a42b389..fafb3a8 100644 --- a/references/std-time.md +++ b/references/std-time.md @@ -6,11 +6,9 @@ Zig 0.16 moves time APIs that depend on the runtime behind `std.Io`. Use `std.Io ## Migration Summary -Release-note map: - -- `std.time.Instant` -> `std.Io.Timestamp` -- `std.time.Timer` -> `std.Io.Timestamp` -- `std.time.timestamp` -> `std.Io.Timestamp.now` +Choose an explicit clock when migrating old time APIs. Use +`std.Io.Timestamp.now(io, .real)` for wall time and `.boot` or `.awake` for +elapsed-time measurements; `Timestamp` is not a one-for-one timer replacement. - `{D}` duration formatting -> format `std.Io.Duration` with `{f}` `std.time` still provides constants and calendar helpers such as `ns_per_ms`, `ns_per_s`, and `std.time.epoch`. @@ -79,7 +77,8 @@ const elapsed = start.untilNow(io); ## Sleeping -Use clock-aware durations/timestamps rather than `std.Thread.sleep` when the code should cooperate with the selected `std.Io` backend. +Use clock-aware durations/timestamps so sleeping cooperates with the selected +`std.Io` backend and propagates cancelation. ```zig try std.Io.Clock.Duration{ @@ -88,8 +87,6 @@ try std.Io.Clock.Duration{ }.sleep(io); ``` -For low-level OS-thread code that deliberately blocks a thread and is not part of I/O task scheduling, `std.Thread.sleep` is still available. - ## Resolution Clock resolution may fail or return zero for unsupported clocks. @@ -112,6 +109,9 @@ const seconds: u64 = @intCast(now.toSeconds()); const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = seconds }; ``` +This conversion is only valid for non-negative Unix timestamps and truncates +sub-second precision. Guard pre-epoch values or retain a signed representation. + ## Application Guidance - Put common wall-clock timestamp reads behind a shared application helper when consistent clock selection matters. @@ -125,4 +125,4 @@ const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = seconds }; - Is the clock choice documented by usage (`.real` for wall time, monotonic clocks for elapsed time)? - Is duration formatting using `{f}` with `std.Io.Duration`? - Does the API receive or store `io` rather than constructing a fallback locally? -- Is `std.Thread.sleep` only used for deliberate OS-thread blocking? +- Does sleeping use a clock-aware `std.Io` API and handle `error.Canceled`? diff --git a/references/std-treap.md b/references/std-treap.md index 877b033..3d7e8b7 100644 --- a/references/std-treap.md +++ b/references/std-treap.md @@ -24,6 +24,9 @@ var treap: MyTreap = .{}; Nodes are user-managed (intrusive design): +An inserted node must remain alive at a stable address until it is removed or +replaced; the treap stores raw parent/child pointers. + ```zig var nodes: [100]MyTreap.Node = undefined; @@ -55,7 +58,8 @@ if (entry.node) |node| { // found, node.key == key } -// Get entry for existing node (O(1) if you have the node) +// O(1), but node must currently belong to this same treap. Passing a stale, +// removed, or foreign node is illegal behavior. var entry = treap.getEntryForExisting(node); ``` @@ -74,7 +78,7 @@ entry.set(null); ```zig var entry = treap.getEntryForExisting(old_node); -entry.set(&new_node); // replaces old with new (same key) +entry.set(&new_node); // copies the old entry's key/links into new_node ``` ## Min/Max Access @@ -168,4 +172,5 @@ pub fn main() !void { - No allocator needed (nodes are user-managed) - Balancing uses randomized priorities (xorshift PRNG) - `node.priority == 0` indicates node is not in treap -- Entry API allows atomic check-and-modify patterns +- Entry API provides a lookup-and-update slot; it does not add synchronization + or atomic memory operations. diff --git a/references/std-tz.md b/references/std-tz.md index b81da3e..f263c03 100644 --- a/references/std-tz.md +++ b/references/std-tz.md @@ -1,6 +1,6 @@ # std.Tz - TZif Timezone Database Parsing (Zig 0.16.0) -Parse IANA Time Zone Database files (TZif format, RFC 8536). Used to look up UTC offsets, DST rules, and timezone abbreviations. +Parse IANA Time Zone Database files (TZif format, RFC 8536) into transitions, time types, leap seconds, and an optional POSIX footer. `std.Tz` stores this data; applications perform their own timestamp lookup and, if needed, interpret future rules from the footer. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html @@ -20,17 +20,15 @@ When reading timezone files in Zig 0.16, use `std.Io.Dir`/`std.Io.File` and expl ```zig const std = @import("std"); -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); - +fn load(io: std.Io, allocator: std.mem.Allocator) !void { // Open system timezone file - const file = try std.fs.openFileAbsolute("/usr/share/zoneinfo/America/New_York", .{}); - defer file.close(); + const file = try std.Io.Dir.openFileAbsolute(io, "/usr/share/zoneinfo/America/New_York", .{}); + defer file.close(io); + var read_buf: [4096]u8 = undefined; + var file_reader = file.reader(io, &read_buf); // Parse TZif data - var tz = try std.Tz.parse(allocator, file.reader()); + var tz = try std.Tz.parse(allocator, &file_reader.interface); defer tz.deinit(); // Access timezone information @@ -47,9 +45,9 @@ const std = @import("std"); // Embed TZif file at compile time const tokyo_tz = @embedFile("tz/asia_tokyo.tzif"); -pub fn main() !void { - var stream = std.io.fixedBufferStream(tokyo_tz); - var tz = try std.Tz.parse(std.heap.page_allocator, stream.reader()); +pub fn parseEmbedded(allocator: std.mem.Allocator) !void { + var reader: std.Io.Reader = .fixed(tokyo_tz); + var tz = try std.Tz.parse(allocator, &reader); defer tz.deinit(); // Use timezone data... @@ -66,7 +64,7 @@ pub const Tz = struct { leapseconds: []const Leapsecond, footer: ?[]const u8, // POSIX TZ string for future dates - pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz + pub fn parse(allocator: std.mem.Allocator, reader: *std.Io.Reader) !Tz pub fn deinit(self: *Tz) void }; ``` @@ -115,7 +113,7 @@ pub const Leapsecond = struct { ```zig fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 { // Find the last transition before or at the given timestamp - var result: ?*const std.Timetype = null; + var result: ?*const std.Tz.Timetype = null; for (tz.transitions) |t| { if (t.ts <= unix_timestamp) { @@ -134,8 +132,9 @@ fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 { return 0; } -// Usage -const offset = getUtcOffset(&tz, std.time.timestamp()); +// Usage for a timestamp supplied by the caller +const unix_timestamp: i64 = 1_700_000_000; +const offset = getUtcOffset(&tz, unix_timestamp); const local_time = unix_timestamp + offset; ``` @@ -143,14 +142,16 @@ const local_time = unix_timestamp + offset; ```zig fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool { + var active: ?*const std.Tz.Timetype = null; for (tz.transitions) |t| { if (t.ts <= unix_timestamp) { - if (t.timetype.isDst()) return true; + active = t.timetype; } else { break; } } - return false; + if (active) |tt| return tt.isDst(); + return tz.timetypes.len > 0 and tz.timetypes[0].isDst(); } ``` @@ -158,7 +159,7 @@ fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool { ```zig fn getTimezoneAbbrev(tz: *const std.Tz, unix_timestamp: i64) []const u8 { - var result: ?*const std.Timetype = null; + var result: ?*const std.Tz.Timetype = null; for (tz.transitions) |t| { if (t.ts <= unix_timestamp) { @@ -194,14 +195,16 @@ fn printTransitions(tz: *const std.Tz) void { } ``` -## Parse Errors +## Selected Parse Errors | Error | Cause | |-------|-------| | `error.BadHeader` | Invalid TZif magic bytes (not "TZif") | | `error.BadVersion` | Unsupported TZif version (only 0, 2, 3 supported) | | `error.Malformed` | RFC 8536 validation failure | -| `error.OverlargeFooter` | POSIX TZ string exceeds 128 bytes | +| `error.OverlargeFooter` | Footer exceeded the active reader's capacity while scanning to its newline | + +Allocation failures and `std.Io.Reader` failures such as `EndOfStream` or `ReadFailed` can also propagate. ## System Timezone Paths @@ -218,7 +221,7 @@ Common timezone identifiers: ## POSIX TZ Footer -Modern TZif files (v2+) include a POSIX TZ string in the footer for calculating offsets beyond the last transition: +Modern TZif files (v2+) may include a POSIX TZ string in the footer. `std.Tz` stores a non-empty footer but does not interpret it to calculate future offsets: ```zig if (tz.footer) |posix_tz| { diff --git a/references/std-unicode.md b/references/std-unicode.md index f0f1e9e..93b2909 100644 --- a/references/std-unicode.md +++ b/references/std-unicode.md @@ -49,14 +49,15 @@ while (it2.nextCodepointSlice()) |slice| { // slice is []const u8: "h", "é", "l", "l", "o", " ", "世", "界" } -// Peek ahead without advancing -const next3 = it.peek(3); // next 3 codepoints as UTF-8 bytes +// Peek ahead without advancing a fresh iterator +var peek_it = view.iterator(); +const next3 = peek_it.peek(3); // next 3 codepoints as UTF-8 bytes // Comptime-validated view -const view = unicode.Utf8View.initComptime("hello"); +const comptime_view = unicode.Utf8View.initComptime("hello"); // Unchecked (when you know it's valid) -const view = unicode.Utf8View.initUnchecked(trusted_utf8); +const unchecked_view = unicode.Utf8View.initUnchecked(trusted_utf8); ``` ## Encoding/Decoding Codepoints @@ -64,17 +65,17 @@ const view = unicode.Utf8View.initUnchecked(trusted_utf8); ```zig // Encode codepoint to UTF-8 var buf: [4]u8 = undefined; -const len = try unicode.utf8Encode('é', &buf); // len = 2 -// buf[0..len] contains UTF-8 bytes +const encoded_len = try unicode.utf8Encode('é', &buf); // len = 2 +// buf[0..encoded_len] contains UTF-8 bytes // Comptime encoding (returns fixed-size array) const bytes = unicode.utf8EncodeComptime('世'); // [3]u8 // Get UTF-8 sequence length for a codepoint -const len = try unicode.utf8CodepointSequenceLength('世'); // 3 +const codepoint_len = try unicode.utf8CodepointSequenceLength('世'); // 3 // Get sequence length from first byte -const len = try unicode.utf8ByteSequenceLength(0xE4); // 3 (for 3-byte sequence) +const sequence_len = try unicode.utf8ByteSequenceLength(0xE4); // 3 (for 3-byte sequence) ``` ## UTF-8 ↔ UTF-16 Conversion @@ -109,23 +110,25 @@ defer allocator.free(utf8z); // UTF-8 to UTF-16LE (caller provides buffer) var utf16_buf: [128]u16 = undefined; const len = try unicode.utf8ToUtf16Le(&utf16_buf, "hello"); -const utf16 = utf16_buf[0..len]; +const utf16_result = utf16_buf[0..len]; // UTF-16LE to UTF-8 (caller provides buffer) var utf8_buf: [256]u8 = undefined; -const len = try unicode.utf16LeToUtf8(&utf8_buf, utf16_data); -const utf8 = utf8_buf[0..len]; +const utf8_len = try unicode.utf16LeToUtf8(&utf8_buf, utf16_data); +const utf8_result = utf8_buf[0..utf8_len]; ``` +These functions assert that the destination buffer is large enough; insufficient capacity is not reported as a recoverable error. Compute or conservatively bound the required capacity first. + ### ArrayList Conversion ```zig -var list = std.ArrayList(u16).empty; -defer list.deinit(allocator); +var list = std.array_list.Managed(u16).init(allocator); +defer list.deinit(); try unicode.utf8ToUtf16LeArrayList(&list, "hello"); -var list8 = std.ArrayList(u8).empty; -defer list8.deinit(allocator); +var list8 = std.array_list.Managed(u8).init(allocator); +defer list8.deinit(); try unicode.utf16LeToUtf8ArrayList(&list8, utf16_data); ``` @@ -166,7 +169,8 @@ while (try it.nextCodepoint()) |cp| { WTF-8 is like UTF-8 but allows unpaired surrogates (for Windows compatibility). ```zig -// Validate WTF-8 (allows surrogates) +// Validate the accepted byte encoding. This does not reject paired surrogate +// halves, so acceptance alone does not establish well-formed WTF-8. unicode.wtf8ValidateSlice(data) // bool // WTF-8 iteration @@ -178,13 +182,17 @@ while (it.nextCodepoint()) |cp| { // WTF-8 ↔ WTF-16 conversion const wtf8 = try unicode.wtf16LeToWtf8Alloc(allocator, wtf16_data); +defer allocator.free(wtf8); const wtf16 = try unicode.wtf8ToWtf16LeAlloc(allocator, wtf8_data); +defer allocator.free(wtf16); // Convert WTF-8 to UTF-8 (lossy - replaces surrogates with U+FFFD) const utf8 = try unicode.wtf8ToUtf8LossyAlloc(allocator, wtf8_data); +defer allocator.free(utf8); -// In-place lossy conversion -try unicode.wtf8ToUtf8Lossy(buffer, wtf8_data); +// In-place is supported when input and output are exactly the same slice. +// Otherwise output must be at least as long as input. +try unicode.wtf8ToUtf8Lossy(buffer, buffer); ``` ## Formatting @@ -230,7 +238,9 @@ fn callWindowsApi(path: []const u8) !void { } ``` -### Grapheme-aware truncation +### Codepoint-counted truncation + +This does not implement Unicode grapheme segmentation. It can split combining sequences, emoji ZWJ sequences, and other user-perceived characters made from multiple codepoints. ```zig fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 { const view = try unicode.Utf8View.init(s); @@ -246,7 +256,7 @@ fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 { } ``` -## Error Types +## Selected Error Types | Error | Meaning | |-------|---------| @@ -255,7 +265,7 @@ fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 { | `Utf8InvalidStartByte` | Invalid first byte in sequence | | `Utf8ExpectedContinuation` | Missing continuation byte | | `Utf8OverlongEncoding` | Overlong encoding detected | -| `Utf8EncodesSurrogateHalf` | Surrogate in UTF-8 (use WTF-8) | +| `Utf8CannotEncodeSurrogateHalf` | Attempted to encode a surrogate as UTF-8 (use WTF-8 if intentional) | | `CodepointTooLarge` | Codepoint > 0x10FFFF | ## Notes diff --git a/references/std-uri.md b/references/std-uri.md index ad8f6e7..81cb758 100644 --- a/references/std-uri.md +++ b/references/std-uri.md @@ -4,7 +4,7 @@ Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/releas URI parsing remains mostly independent of the I/O migration. For network or file operations derived from URIs, pass/use `std.Io`. -URI parsing and formatting conforming to RFC 3986, with percent-encoding/decoding and resolution support. +URI parsing and formatting that roughly adhere to RFC 3986, with percent-encoding/decoding and resolution support. The parser intentionally does not perform complete grammar or character-class validation. ## Table of Contents - [Parsing URIs](#parsing-uris) @@ -64,6 +64,10 @@ const uri = std.Uri.parse(input) catch |err| switch (err) { std.debug.print("Port not a valid u16\n", .{}); return err; }, + error.InvalidHostName => { + std.debug.print("Invalid host name\n", .{}); + return err; + }, }; ``` @@ -81,8 +85,6 @@ const Uri = struct { path: Component = Component.empty, query: ?Component = null, fragment: ?Component = null, - - pub const host_name_max = 255; }; ``` @@ -106,13 +108,14 @@ const Component = union(enum) { ### Getting Host ```zig -var buffer: [std.Uri.host_name_max]u8 = undefined; +var buffer: [std.Io.net.HostName.max_len]u8 = undefined; const host = uri.getHost(&buffer) catch |err| switch (err) { error.UriMissingHost => return error.NoHost, - error.UriHostTooLong => return error.HostTooLong, }; ``` +The result is a `std.Io.net.HostName`. A URI host is validated during parsing, so the fixed buffer is sized to the hostname limit and `getHost` exposes only `error.UriMissingHost`. + With allocation: ```zig @@ -152,6 +155,8 @@ const raw = try component.toRaw(&buf); // "hello world" const raw_alloc = try component.toRawMaybeAlloc(allocator); // "hello world" ``` +`toRawMaybeAlloc` may return the component's original borrowed slice or arena-allocated decoded bytes. Treat the result as tied to both lifetimes and do not free it individually. + ## Formatting URIs ### Full URI @@ -236,7 +241,7 @@ const decoded = std.Uri.percentDecodeInPlace(&buffer); // decoded == "hello world!" ``` -### Decode Backwards (Safe for Aliasing) +### Decode Backwards (Conditionally Safe for Aliasing) ```zig const input = "%48%65%6C%6C%6F"; @@ -245,6 +250,8 @@ const decoded = std.Uri.percentDecodeBackwards(&output, input); // decoded == "Hello" ``` +The output must be large enough. Aliasing is supported only when `output.ptr <= input.ptr`; there is no recoverable undersized-buffer error. + ### Encode with Component ```zig @@ -264,7 +271,7 @@ var buf: [256]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); // Encode with custom character validation -std.Uri.Component.percentEncode(&writer, "custom data", struct { +try std.Uri.Component.percentEncode(&writer, "custom data", struct { fn isValid(c: u8) bool { return std.ascii.isAlphanumeric(c); } @@ -338,103 +345,101 @@ const name = getQueryParam(uri, "name"); // "alice" ### Build URL with Query Parameters ```zig -fn buildUrl(allocator: Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 { - var result: std.ArrayList(u8) = .empty; - defer result.deinit(allocator); +fn buildUrl(allocator: std.mem.Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 { + var result: std.Io.Writer.Allocating = .init(allocator); + errdefer result.deinit(); - try result.appendSlice(allocator, base); + try result.writer.writeAll(base); for (params, 0..) |param, i| { - try result.append(allocator, if (i == 0) '?' else '&'); - - // Encode key - for (param[0]) |c| { - if (std.Uri.isUnreserved(c)) { - try result.append(allocator, c); - } else { - try result.appendSlice(allocator, try std.fmt.allocPrint(allocator, "%{X:0>2}", .{c})); - } - } - - try result.append(allocator, '='); - - // Encode value - for (param[1]) |c| { - if (std.Uri.isUnreserved(c)) { - try result.append(allocator, c); - } else { - try result.appendSlice(allocator, try std.fmt.allocPrint(allocator, "%{X:0>2}", .{c})); - } - } + try result.writer.writeByte(if (i == 0) '?' else '&'); + try (std.Uri.Component{ .raw = param[0] }).formatEscaped(&result.writer); + try result.writer.writeByte('='); + try (std.Uri.Component{ .raw = param[1] }).formatEscaped(&result.writer); } - return result.toOwnedSlice(allocator); + return result.toOwnedSlice(); } ``` -### Normalize URI +### Serialize a Parsed URI ```zig -fn normalizeUri(allocator: Allocator, uri_str: []const u8) ![]u8 { +fn serializeUri(allocator: std.mem.Allocator, uri_str: []const u8) ![]u8 { const uri = try std.Uri.parse(uri_str); var buf: [4096]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); - // Format with all components to normalize encoding + // Parsed percent-encoded components are preserved when formatted. try uri.format(&writer); return try allocator.dupe(u8, writer.buffered()); } ``` +This is serialization, not general normalization: existing escape spelling is preserved. Formatting with all components also emits `/` for an empty included path. + ### Validate URI ```zig -fn isValidUri(str: []const u8) bool { +fn isAcceptedByUriParser(str: []const u8) bool { _ = std.Uri.parse(str) catch return false; return true; } -fn isValidHttpUri(str: []const u8) bool { +fn hasHttpSchemeAndHost(str: []const u8) bool { const uri = std.Uri.parse(str) catch return false; - return std.mem.eql(u8, uri.scheme, "http") or std.mem.eql(u8, uri.scheme, "https"); + return uri.host != null and + (std.mem.eql(u8, uri.scheme, "http") or std.mem.eql(u8, uri.scheme, "https")); } ``` +These remain lightweight parser/scheme checks, not complete URI or HTTP-URL validation. + ### Join Path Segments ```zig -fn joinPath(allocator: Allocator, base_uri: std.Uri, segments: []const []const u8) !std.Uri { - var path: std.ArrayList(u8) = .empty; - defer path.deinit(allocator); +const OwnedUri = struct { + value: std.Uri, + path_storage: []u8, - // Start with base path (remove trailing slash if any) - const base_path = base_uri.path.percent_encoded; - if (base_path.len > 0 and base_path[base_path.len - 1] == '/') { - try path.appendSlice(allocator, base_path[0 .. base_path.len - 1]); - } else { - try path.appendSlice(allocator, base_path); + fn deinit(self: *OwnedUri, allocator: std.mem.Allocator) void { + allocator.free(self.path_storage); + self.* = undefined; } +}; - // Append segments +fn joinPath(allocator: std.mem.Allocator, base_uri: std.Uri, segments: []const []const u8) !OwnedUri { + var path: std.Io.Writer.Allocating = .init(allocator); + errdefer path.deinit(); + + // Preserve/encode the existing component according to path rules. + try base_uri.path.formatPath(&path.writer); + + // Each segment is escaped as a segment, so '/' inside a segment becomes %2F. for (segments) |seg| { - try path.append(allocator, '/'); - try path.appendSlice(allocator, seg); + const current = path.written(); + if (current.len == 0 or current[current.len - 1] != '/') + try path.writer.writeByte('/'); + try (std.Uri.Component{ .raw = seg }).formatEscaped(&path.writer); } + const owned_path = try path.toOwnedSlice(); var result = base_uri; - result.path = .{ .percent_encoded = try path.toOwnedSlice(allocator) }; + result.path = .{ .percent_encoded = owned_path }; result.query = null; result.fragment = null; - return result; + return .{ .value = result, .path_storage = owned_path }; } ``` +The returned URI owns its assembled path; call `OwnedUri.deinit` after use. + ### Extract Base URL ```zig -fn getBaseUrl(allocator: Allocator, uri: std.Uri) ![]u8 { +fn getBaseUrl(allocator: std.mem.Allocator, uri: std.Uri) ![]u8 { var buf: [1024]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); @@ -461,6 +466,7 @@ pub const ParseError = error{ UnexpectedCharacter, // Invalid character in URI component InvalidFormat, // Malformed URI structure InvalidPort, // Port not a valid u16 + InvalidHostName, // Host did not pass std.Io.net.HostName validation }; ``` @@ -477,7 +483,6 @@ pub const ResolveInPlaceError = ParseError || error{ ```zig // getHost errors error.UriMissingHost // URI has no host component -error.UriHostTooLong // Host exceeds host_name_max (255) // toRaw errors error.NoSpaceLeft // Buffer too small for decoded string diff --git a/references/std-zig.md b/references/std-zig.md index a0d6f2c..f3b8c07 100644 --- a/references/std-zig.md +++ b/references/std-zig.md @@ -2,6 +2,8 @@ Utilities for parsing, tokenizing, and working with Zig source code. Used for tooling, linters, formatters, and custom analysis. +These compiler-distributed utilities have no API-stability guarantee. Treat this page as a Zig 0.16-specific reference and expect tooling code to need updates across Zig releases. + Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html When tooling reads/writes files or buffers output in Zig 0.16, use `std.Io.Reader`, `std.Io.Writer`, `std.Io.Dir`, and explicit `std.Io`. When searching source text, prefer `std.mem.find*` and `std.mem.cut*` APIs. @@ -32,7 +34,7 @@ pub fn analyzeSource(allocator: std.mem.Allocator, source: [:0]const u8) !void { if (tree.errors.len > 0) { for (tree.errors) |err| { var buf: [256]u8 = undefined; - var w: std.io.Writer = .fixed(&buf); + var w: std.Io.Writer = .fixed(&buf); try tree.renderError(err, &w); std.debug.print("Error: {s}\n", .{w.buffered()}); } @@ -164,7 +166,7 @@ defer allocator.free(formatted); // Or render to writer var buf: [8192]u8 = undefined; -var writer = std.fs.File.stdout().writer(&buf); +var writer = std.Io.File.stdout().writer(io, &buf); try tree.render(allocator, &writer.interface, .{}); try writer.interface.flush(); ``` @@ -195,8 +197,19 @@ const token_tag = tree.tokenTag(token_index); for (tree.rootDecls()) |decl| { switch (tree.nodeTag(decl)) { .fn_decl => handleFunction(tree, decl), - .global_var_decl, .simple_var_decl => handleVariable(tree, decl), - .container_decl, .container_decl_two => handleStruct(tree, decl), + .global_var_decl, .simple_var_decl, .local_var_decl, .aligned_var_decl => { + // A normal top-level `const Name = struct { ... };` is a variable + // declaration. Inspect its initializer to find the container expression. + const var_decl = tree.fullVarDecl(decl).?; + if (var_decl.ast.init_node.unwrap()) |init_node| { + var container_buf: [2]std.zig.Ast.Node.Index = undefined; + if (tree.fullContainerDecl(&container_buf, init_node)) |container| { + handleStruct(tree, init_node, container); + } else { + handleVariable(tree, decl); + } + } + }, else => {}, } } @@ -499,7 +512,7 @@ if (tree.errors.len > 0) { // Format error message var buf: [512]u8 = undefined; - var w: std.io.Writer = .fixed(&buf); + var w: std.Io.Writer = .fixed(&buf); try tree.renderError(err, &w); std.debug.print("{s}:{d}:{d}: error: {s}\n", .{ @@ -530,7 +543,7 @@ var bundle = try wip_errors.toOwnedBundle(""); defer bundle.deinit(allocator); // Render to stderr -bundle.renderToStdErr(.{ .ttyconf = .no_color }); +try bundle.renderToStderr(io, .{}, .off); // Or iterate errors for (bundle.getMessages()) |msg_idx| { @@ -578,7 +591,7 @@ switch (result) { ```zig // Format identifier, escaping if needed var buf: [256]u8 = undefined; -var w: std.io.Writer = .fixed(&buf); +var w: std.Io.Writer = .fixed(&buf); try w.print("{f}", .{std.zig.fmtId("while")}); // @"while" try w.print("{f}", .{std.zig.fmtId("hello")}); // hello try w.print("{f}", .{std.zig.fmtId("123abc")}); // @"123abc" @@ -596,7 +609,7 @@ std.zig.isValidId("a b") // false (contains space) ```zig // Escape string for Zig string literal var buf: [256]u8 = undefined; -var w: std.io.Writer = .fixed(&buf); +var w: std.Io.Writer = .fixed(&buf); try w.print("\"{f}\"", .{std.zig.fmtString("hello\nworld")}); // Output: "hello\nworld" @@ -621,17 +634,18 @@ const hash = std.zig.hashSrc(source); // Compare hashes if (std.zig.srcHashEql(hash1, hash2)) { - // Sources are identical + // The 128-bit source hashes match (appropriate as a cache signal, but + // hash equality is not mathematical proof that the source bytes match). } ``` ### Read Source File ```zig // Read and decode source file (handles UTF-16LE BOM) -const file = try std.fs.cwd().openFile("source.zig", .{}); -defer file.close(); +const file = try std.Io.Dir.cwd().openFile(io, "source.zig", .{}); +defer file.close(io); -var reader = file.reader(&buf); +var reader = file.reader(io, &buf); const source = try std.zig.readSourceFileToEndAlloc(allocator, &reader); defer allocator.free(source); ``` diff --git a/references/std-zip.md b/references/std-zip.md index 18b8b0c..d1aa654 100644 --- a/references/std-zip.md +++ b/references/std-zip.md @@ -4,7 +4,7 @@ ZIP archive reading and extraction. Zig 0.16 file and stream APIs use `std.Io.Di Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html -Old examples below may contain 0.15 `std.fs` or `std.io` patterns; translate them to `std.Io` before using in new code. +The examples below use Zig 0.16's explicit `std.Io` file and directory APIs. ## Table of Contents - [Module Structure](#module-structure) @@ -33,11 +33,11 @@ std.zip.CompressionMethod // .store, .deflate Extract all files from a ZIP archive to a directory: ```zig -const file = try std.fs.cwd().openFile("archive.zip", .{}); -defer file.close(); +const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{}); +defer file.close(io); var buf: [4096]u8 = undefined; -var file_reader = file.reader(&buf); +var file_reader = file.reader(io, &buf); try std.zip.extract(output_dir, &file_reader, .{}); ``` @@ -65,10 +65,12 @@ if (diagnostics.root_dir.len > 0) { pub const ExtractOptions = struct { allow_backslashes: bool = false, // normalize \ to / in filenames diagnostics: ?*Diagnostics = null, // track extraction metadata - verify_checksums: bool = false, // TODO: not yet implemented + verify_checksums: bool = false, // true currently panics: TODO unimplemented }; ``` +Leave `verify_checksums` false in Zig 0.16. Setting it true immediately panics, and normal extraction does not otherwise verify each entry's CRC-32 payload checksum. + ## Iterating Over Entries ### Iterator API @@ -76,15 +78,15 @@ pub const ExtractOptions = struct { For more control, iterate over entries individually: ```zig -const file = try std.fs.cwd().openFile("archive.zip", .{}); -defer file.close(); +const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{}); +defer file.close(io); var buf: [4096]u8 = undefined; -var file_reader = file.reader(&buf); +var file_reader = file.reader(io, &buf); var iter = try std.zip.Iterator.init(&file_reader); -var filename_buf: [std.fs.max_path_bytes]u8 = undefined; +var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; while (try iter.next()) |entry| { // Read filename from archive try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader)); @@ -99,12 +101,14 @@ while (try iter.next()) |entry| { } ``` -### Iterator.Entry Structure +### Iterator.Entry Fields -```zig -pub const Entry = struct { +```text +// Descriptive field inventory, not a declaration to copy: the concrete type +// of `flags` is private to std.zip. +struct { version_needed_to_extract: u16, - flags: GeneralPurposeFlags, + flags: /* private general-purpose-flags type */, compression_method: CompressionMethod, // .store or .deflate last_modification_time: u16, // DOS time format last_modification_date: u16, // DOS date format @@ -124,7 +128,7 @@ pub const Entry = struct { ```zig var iter = try std.zip.Iterator.init(&file_reader); -var filename_buf: [std.fs.max_path_bytes]u8 = undefined; +var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; while (try iter.next()) |entry| { // Extract this entry to destination directory try entry.extract(&file_reader, .{}, &filename_buf, output_dir); @@ -138,7 +142,7 @@ Extract only specific files: ```zig var iter = try std.zip.Iterator.init(&file_reader); -var filename_buf: [std.fs.max_path_bytes]u8 = undefined; +var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; while (try iter.next()) |entry| { // Read filename first try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader)); @@ -234,15 +238,15 @@ std.zip.end_locator64_sig // "PK\x06\x07" ### Extract ZIP to Directory ```zig -fn extractZip(allocator: Allocator, zip_path: []const u8, dest_path: []const u8) !void { - const file = try std.fs.cwd().openFile(zip_path, .{}); - defer file.close(); +fn extractZip(io: std.Io, allocator: std.mem.Allocator, zip_path: []const u8, dest_path: []const u8) !void { + const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{}); + defer file.close(io); var buf: [4096]u8 = undefined; - var file_reader = file.reader(&buf); + var file_reader = file.reader(io, &buf); - var dest = try std.fs.cwd().makeOpenPath(dest_path, .{}); - defer dest.close(); + var dest = try std.Io.Dir.cwd().createDirPathOpen(io, dest_path, .{}); + defer dest.close(io); var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator }; defer diagnostics.deinit(); @@ -257,16 +261,16 @@ fn extractZip(allocator: Allocator, zip_path: []const u8, dest_path: []const u8) ### List ZIP Contents ```zig -fn listZip(zip_path: []const u8) !void { - const file = try std.fs.cwd().openFile(zip_path, .{}); - defer file.close(); +fn listZip(io: std.Io, zip_path: []const u8) !void { + const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{}); + defer file.close(io); var buf: [4096]u8 = undefined; - var file_reader = file.reader(&buf); + var file_reader = file.reader(io, &buf); var iter = try std.zip.Iterator.init(&file_reader); - var filename_buf: [std.fs.max_path_bytes]u8 = undefined; + var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; var total_size: u64 = 0; var file_count: u64 = 0; @@ -299,13 +303,13 @@ fn listZip(zip_path: []const u8) !void { ```zig fn extractFile( - file_reader: *std.fs.File.Reader, + file_reader: *std.Io.File.Reader, target_name: []const u8, - dest: std.fs.Dir, + dest: std.Io.Dir, ) !bool { var iter = try std.zip.Iterator.init(file_reader); - var filename_buf: [std.fs.max_path_bytes]u8 = undefined; + var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; while (try iter.next()) |entry| { try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader)); const filename = filename_buf[0..entry.filename_len]; @@ -323,12 +327,12 @@ fn extractFile( ### Check if File is ZIP ```zig -fn isZipFile(path: []const u8) !bool { - const file = std.fs.cwd().openFile(path, .{}) catch return false; - defer file.close(); +fn isZipFile(io: std.Io, path: []const u8) !bool { + const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch return false; + defer file.close(io); var buf: [4096]u8 = undefined; - var file_reader = file.reader(&buf); + var file_reader = file.reader(io, &buf); _ = std.zip.EndRecord.findFile(&file_reader) catch return false; return true; @@ -337,7 +341,7 @@ fn isZipFile(path: []const u8) !bool { ## Supported Features -**Formats**: ZIP, ZIP64 (large files > 4GB, > 65535 entries) +**Formats**: ZIP plus core single-disk ZIP64 records/extents. ZIP64 end-record extra data, unsupported versions, and multi-disk/locator variants are rejected, so this is not unrestricted ZIP64 compatibility. **Compression**: Store (uncompressed), Deflate diff --git a/references/std-zon.md b/references/std-zon.md index 392a73d..0407063 100644 --- a/references/std-zon.md +++ b/references/std-zon.md @@ -1,6 +1,6 @@ # std.zon - ZON Parsing and Serialization -ZON ("Zig Object Notation") parsing and stringification. ZON's grammar is a subset of Zig's syntax. In Zig 0.16, examples that read/write files should use `std.Io.Dir`/`std.Io.File` and an explicit `std.Io`. +ZON ("Zig Object Notation") parsing and stringification. Its grammar is a subset of Zig's syntax except for the supported `nan` and `inf` literals. In Zig 0.16, file examples use `std.Io.Dir`/`std.Io.File` and an explicit `std.Io`. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html @@ -70,7 +70,7 @@ pub fn main() !void { defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const config = try std.zon.parse.fromSlice(Config, allocator, zon_str, null, .{}); + const config = try std.zon.parse.fromSliceAlloc(Config, allocator, zon_str, null, .{}); defer std.zon.parse.free(allocator, config); // config.name == "server" @@ -85,7 +85,7 @@ pub fn main() !void { var diag: std.zon.parse.Diagnostics = .{}; defer diag.deinit(allocator); -const result = std.zon.parse.fromSlice(Config, allocator, zon_str, &diag, .{}) catch |err| { +const result = std.zon.parse.fromSliceAlloc(Config, allocator, zon_str, &diag, .{}) catch |err| { // Print diagnostic errors var errors = diag.iterateErrors(); while (errors.next()) |parse_err| { @@ -104,7 +104,7 @@ defer std.zon.parse.free(allocator, result); ### Parse Options ```zig -const result = try std.zon.parse.fromSlice(T, allocator, zon_str, diag, .{ +const result = try std.zon.parse.fromSliceAlloc(T, allocator, zon_str, diag, .{ // Ignore unknown fields (default: false - errors on unknown) .ignore_unknown_fields = true, @@ -128,10 +128,12 @@ const version = build_zon.version; ### Free Parsed Values ```zig -const result = try std.zon.parse.fromSlice(T, allocator, zon_str, null, .{}); +const result = try std.zon.parse.fromSliceAlloc(T, allocator, zon_str, null, .{}); defer std.zon.parse.free(allocator, result); ``` +Use `fromSlice` only when `T` contains no pointers; that result owns no allocations and needs no `free`. Use `fromSliceAlloc` for pointer-containing values such as structs with string slices, and release the result with `std.zon.parse.free`. + ## Serializing to ZON ### Simple Serialization @@ -252,10 +254,12 @@ try tuple.end(); var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = true }, // Always wrap fields // .whitespace_style = .{ .wrap = false }, // Never wrap (single line) - // .whitespace_style = .{ .fields = 2 }, // Auto-wrap if > 2 fields + // .whitespace_style = .{ .fields = 2 }, // caller says this container has 2 fields; no wrap }); ``` +For `.fields = n`, the caller supplies the expected field count. Values greater than two select wrapped output; the serializer does not look ahead and count future fields. + ### Nested Containers ```zig @@ -394,42 +398,40 @@ const Config = struct { debug: bool = false, }; -fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config { - const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { +fn loadConfig(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !Config { + const content = std.Io.Dir.cwd().readFileAllocOptions( + io, + path, + allocator, + .limited(1024 * 1024), + .of(u8), + 0, + ) catch |err| switch (err) { error.FileNotFound => return Config{}, else => return err, }; - defer file.close(); - - const content = try file.readToEndAllocOptions( - allocator, - 1024 * 1024, - null, - @alignOf(u8), - 0, // null terminator - ); defer allocator.free(content); - return std.zon.parse.fromSlice(Config, allocator, content, null, .{ + return std.zon.parse.fromSliceAlloc(Config, allocator, content, null, .{ .ignore_unknown_fields = true, .free_on_error = true, }); } ``` +The caller owns a successfully loaded `Config` and should pass it to `std.zon.parse.free` when finished. + ### Serialize to File ```zig -fn saveConfig(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { - var aw: std.Io.Writer.Allocating = .init(allocator); - defer aw.deinit(); +fn saveConfig(io: std.Io, config: Config, path: []const u8) !void { + const file = try std.Io.Dir.cwd().createFile(io, path, .{}); + defer file.close(io); + var buffer: [4096]u8 = undefined; + var file_writer = file.writer(io, &buffer); - try std.zon.stringify.serialize(config, .{ .whitespace = true }, &aw.writer); - - const file = try std.fs.cwd().createFile(path, .{}); - defer file.close(); - - try file.writeAll(aw.written()); + try std.zon.stringify.serialize(config, .{ .whitespace = true }, &file_writer.interface); + try file_writer.interface.flush(); } ``` @@ -472,7 +474,7 @@ try std.zon.stringify.serialize(settings, .{ ### Round-Trip ZON Data ```zig -fn roundTrip(comptime T: type, allocator: std.mem.Allocator, value: T) !T { +fn roundTripAlloc(comptime T: type, allocator: std.mem.Allocator, value: T) !T { // Serialize var aw: std.Io.Writer.Allocating = .init(allocator); defer aw.deinit(); @@ -484,6 +486,8 @@ fn roundTrip(comptime T: type, allocator: std.mem.Allocator, value: T) !T { const terminated: [:0]const u8 = zon_str[0 .. zon_str.len - 1 :0]; // Parse back - return std.zon.parse.fromSlice(T, allocator, terminated, null, .{}); + return std.zon.parse.fromSliceAlloc(T, allocator, terminated, null, .{}); } ``` + +The returned value may own allocations; the caller must eventually call `std.zon.parse.free(allocator, result)`. For a pointer-free `T`, a `fromSlice`-based variant can return a value that needs no cleanup. diff --git a/references/style-guide.md b/references/style-guide.md index 4575bf2..0756d81 100644 --- a/references/style-guide.md +++ b/references/style-guide.md @@ -1,6 +1,6 @@ # Zig Style Guide -Official coding conventions from the Zig language reference. These are implemented and enforced by `zig fmt`. +Coding conventions based on the Zig language reference. `zig fmt` enforces syntactic layout; naming, documentation, and API-design conventions still require human judgment. Interoperability and established external APIs can justify exceptions. ## Naming Conventions @@ -169,22 +169,22 @@ fn processRequest( - **Omit redundant information** that's already clear from the name - **Duplicate information** across similar functions (helps IDEs) -- Use **"assume"** for invariants that cause *unchecked* illegal behavior when violated -- Use **"assert"** for invariants that cause *safety-checked* illegal behavior when violated +- Use **"assume"** for unchecked preconditions whose violation may cause illegal behavior +- Use **"assert"** when the implementation actively checks an invariant and panics when it is violated ```zig /// Reads a little-endian u32 from the buffer. /// -/// Caller must **assume** buffer has at least 4 bytes remaining. -/// This is not checked and will cause undefined behavior if violated. +/// Caller must provide at least 4 bytes. The slice expression performs a +/// bounds check in safety-enabled builds and panics if the buffer is shorter. fn readU32Le(buf: []const u8) u32 { return std.mem.readInt(u32, buf[0..4], .little); } /// Pops the last element from the list. /// -/// **Asserts** the list is not empty. In safe modes, returns an error -/// or panics if the list is empty. +/// **Asserts** the list is not empty. Assertion failure panics; this function +/// has no error return. fn pop(self: *Self) T { std.debug.assert(self.items.len > 0); // ... @@ -197,7 +197,7 @@ fn pop(self: *Self) T { - **LF** (`\n`, 0x0a) line endings (CRLF discouraged but tolerated) - End files with a newline - No hard tabs (spaces only) -- `zig fmt` enforces all these conventions +- `zig fmt` normalizes source layout; encoding, naming, and documentation rules are separate checks ## Applying the Style Guide diff --git a/references/zig-0.16-release-notes.md b/references/zig-0.16-release-notes.md index d0c285a..1b458ab 100644 --- a/references/zig-0.16-release-notes.md +++ b/references/zig-0.16-release-notes.md @@ -64,7 +64,6 @@ For ABI-sensitive bindings: - `@Struct` - `@Union` - `@Enum` -- `@Opaque` Migration rule: @@ -81,7 +80,9 @@ Practical notes: - Runtime vector indexes are forbidden. Use scalar extraction patterns, compile-time indexes, or restructure the vector operation. - Vectors and arrays no longer support in-memory coercion. Make conversions explicit with loads, stores, or element-wise construction. - Unary float builtins forward result type. Code such as `const x: f64 = @sqrt(@floatFromInt(n));` now works as intended. -- `@floor`, `@ceil`, `@round`, and `@trunc` can convert floats to integer result types. `@intFromFloat` is now redundant with `@trunc` and is deprecated. +- `@floor`, `@ceil`, `@round`, and `@trunc` perform result-typed floating + operations. Use `@intFromFloat` for finite, in-range float-to-integer + conversion. ### Returning Local Addresses @@ -132,7 +133,8 @@ Important removals/renames: - `std.fmt.format` is replaced by `std.Io.Writer.print`. - `std.fmt.Formatter` is renamed to `std.fmt.Alt`. - `std.fmt.FormatOptions` is renamed to `std.fmt.Options`. -- `std.fmt.bufPrintZ` is renamed to `std.fmt.bufPrintSentinel`. +- `std.fmt.bufPrintZ` remains as a deprecated zero-sentinel wrapper; use + `std.fmt.bufPrintSentinel` for new code. - `std.DynLib` removed Windows support; use platform APIs (`LoadLibraryExW`, `GetProcAddress`) directly or through a local abstraction. - `BitSet` and `EnumSet` use decl literals instead of `initEmpty` / `initFull`. @@ -145,9 +147,11 @@ Important error changes: ### I/O as an Interface -The core 0.16 rule: all input/output functionality requires an `std.Io` instance. - -Anything that might block, interact with the outside world, depend on the OS, wait on concurrency, use entropy, or introduce nondeterminism belongs under the `Io` interface. +The core 0.16 direction is to route new blocking, OS-facing, and +nondeterministic APIs through an `std.Io` instance. Entropy, time, networking, +process, and file operations generally follow this design, but existing stdlib +APIs are not uniformly parameterized by `std.Io`; verify the installed +signature before applying the rule mechanically. Main patterns: @@ -203,7 +207,8 @@ Map: - `std.Thread.ResetEvent` -> `std.Io.Event` - `std.Thread.WaitGroup` -> `std.Io.Group` -- `std.Thread.Futex` -> `std.Io.Futex` +- `std.Thread.Futex` operations -> `io.futexWait`, `io.futexWaitTimeout`, + `io.futexWaitUncancelable`, and `io.futexWake` - `std.Thread.Mutex` -> `std.Io.Mutex` - `std.Thread.Condition` -> `std.Io.Condition` - `std.Thread.Semaphore` -> `std.Io.Semaphore` @@ -246,11 +251,10 @@ Use `io.randomSecure(...)` when fresh OS-backed cryptographic entropy is require The old wall-clock/monotonic split is now routed through `std.Io` time types for clock operations that may depend on the runtime. -Migration highlights from the release notes: - -- `std.time.Instant` -> `std.Io.Timestamp` -- `std.time.Timer` -> `std.Io.Timestamp` -- `std.time.timestamp` -> `std.Io.Timestamp.now` +Migration requires an explicit clock choice. Use +`std.Io.Timestamp.now(io, .real)` for wall-clock timestamps and `.boot` or +`.awake` for elapsed-time measurement; `Timestamp` is not a one-for-one timer +replacement. Application preference: @@ -382,7 +386,9 @@ Several low-level stdlib wrappers were removed as part of moving blocking/nondet ### Allocators -`heap.ArenaAllocator` is now thread-safe and lock-free. +`heap.ArenaAllocator`'s allocator interface is thread-safe when its child +allocator is thread-safe. The stdlib does not promise unconditional lock-free +behavior. `heap.ThreadSafe` allocator was removed. If shared allocation still needs protection, choose an allocator that is already thread-safe or place synchronization at the owner of the shared data.