This commit is contained in:
peterino2 2026-07-13 09:01:11 -07:00
parent 5e60be2add
commit 3d8375cf39
58 changed files with 1691 additions and 1641 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
scripts/
audit-results/

View File

@ -11,7 +11,10 @@ Always load **[Zig 0.16 Release Notes Migration Reference](references/zig-0.16-r
## Critical: I/O Is an Interface ## 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: Preferred app entry:
@ -158,11 +161,11 @@ defer client.deinit();
### Time ### Time
0.16 routes time through `std.Io` types: 0.16 routes runtime-dependent time through `std.Io`. Choose an explicit
`std.Io.Clock` and pass `io`; for example, use
- `std.time.Instant` -> `std.Io.Timestamp` `std.Io.Timestamp.now(io, .real)` for wall time and `.boot` or `.awake` for
- `std.time.Timer` -> `std.Io.Timestamp` elapsed-time measurements. `std.Io.Timestamp` is not a one-for-one timer
- `std.time.timestamp` -> `std.Io.Timestamp.now` 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. 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.RwLock` -> `std.Io.RwLock`
- `std.Thread.ResetEvent` -> `std.Io.Event` - `std.Thread.ResetEvent` -> `std.Io.Event`
- `std.Thread.WaitGroup` -> `std.Io.Group` - `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: Cancelable lock:
@ -212,12 +217,14 @@ Do not replace these with custom spin loops. Use std primitives unless a measure
### Removed or Deprecated ### 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()`. - `@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. - `async`/`await` keywords remain removed; use `std.Io` task APIs, an application scheduler, or explicit threads.
- `usingnamespace` is removed; explicitly re-export names. - `usingnamespace` is removed; explicitly re-export names.
- `@fence` is removed; use stronger atomic orderings or RMW operations. - `@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 ### Packed and Extern Layout
@ -253,7 +260,8 @@ try list.append(gpa, 42);
- Priority queues use `push`/`pop` terminology and `.empty` initialization. - Priority queues use `push`/`pop` terminology and `.empty` initialization.
- `std.SegmentedList` removed. - `std.SegmentedList` removed.
- `std.heap.ThreadSafe` 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 ## 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. - **[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. - **[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. 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.

View File

@ -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 ## Table of Contents
- [Type Conversions](#type-conversions) - [Type Conversions](#type-conversions)
@ -199,10 +199,10 @@ const x = @abs(@as(i32, -5)); // 5
### @min / @max ### @min / @max
```zig ```zig
@min(a: T, b: T) T @min(a: T, b: T, ...) T
@max(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 ```zig
const m = @max(3, 7); // 7 const m = @max(3, 7); // 7
``` ```
@ -546,13 +546,13 @@ Get type of a struct field.
### @fieldParentPtr ### @fieldParentPtr
```zig ```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). Get pointer to containing struct from field pointer (for intrusive data structures).
```zig ```zig
const Node = struct { data: u32, hook: Hook }; const Node = struct { data: u32, hook: Hook };
fn getNode(hook: *Hook) *Node { fn getNode(hook: *Hook) *Node {
return @fieldParentPtr(hook, "hook"); return @fieldParentPtr("hook", hook);
} }
``` ```
@ -629,7 +629,7 @@ fn method(self: *Self) void { ... }
```zig ```zig
@src() std.builtin.SourceLocation @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 ### @inComptime
```zig ```zig
@ -771,7 +771,7 @@ if (unlikely_condition) {
// rarely executed // rarely executed
} }
``` ```
Hints: `.none`, `.likely`, `.unlikely`, `.cold` Hints: `.none`, `.likely`, `.unlikely`, `.cold`, `.unpredictable`
### @breakpoint ### @breakpoint
```zig ```zig
@ -836,7 +836,7 @@ Call function with modifier.
```zig ```zig
const result = @call(.always_inline, my_fn, .{ arg1, arg2 }); 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 ### @prefetch
```zig ```zig

View File

@ -26,7 +26,8 @@ Minimal C-compatible library:
```zig ```zig
const std = @import("std"); 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; var context: ?*Context = null;
const Context = struct { const Context = struct {
@ -36,6 +37,7 @@ const Context = struct {
/// Initialize the library. Returns 0 on success, -1 on failure. /// Initialize the library. Returns 0 on success, -1 on failure.
export fn mylib_init() c_int { export fn mylib_init() c_int {
if (context != null) return -1;
const gpa = std.heap.c_allocator; const gpa = std.heap.c_allocator;
context = gpa.create(Context) catch return -1; context = gpa.create(Context) catch return -1;
context.?.* = .{ .allocator = gpa, .value = 0 }; context.?.* = .{ .allocator = gpa, .value = 0 };
@ -76,12 +78,10 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/lib.zig"), .root_source_file = b.path("src/lib.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = true,
}), }),
}); });
// Link libc if using std.heap.c_allocator
lib.linkLibC();
b.installArtifact(lib); b.installArtifact(lib);
// Install header alongside library // Install header alongside library
@ -284,10 +284,9 @@ const lib = b.addLibrary(.{
.root_source_file = b.path("src/lib.zig"), .root_source_file = b.path("src/lib.zig"),
.target = target, .target = target,
.optimize = optimize, .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); b.installArtifact(lib);
``` ```
@ -301,11 +300,11 @@ const lib = b.addLibrary(.{
.root_source_file = b.path("src/lib.zig"), .root_source_file = b.path("src/lib.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = true,
}), }),
.version = .{ .major = 1, .minor = 0, .patch = 0 }, .version = .{ .major = 1, .minor = 0, .patch = 0 },
}); });
lib.linkLibC();
b.installArtifact(lib); b.installArtifact(lib);
``` ```
@ -406,7 +405,9 @@ const std = @import("std");
pub const Context = struct { pub const Context = struct {
allocator: std.mem.Allocator, 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, callback: ?Callback = null,
const Callback = struct { const Callback = struct {
@ -417,7 +418,9 @@ pub const Context = struct {
export fn mylib_create() ?*Context { export fn mylib_create() ?*Context {
const allocator = std.heap.c_allocator; 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 { export fn mylib_destroy(ctx: ?*Context) void {
@ -663,7 +666,7 @@ Name: MyLib
Functions: Functions:
- Name: mylib_create - Name: mylib_create
SwiftName: "MyLibContext.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 - Name: mylib_destroy
SwiftName: "MyLibContext.destroy(self:)" SwiftName: "MyLibContext.destroy(self:)"
- Name: mylib_get_error - Name: mylib_get_error
@ -802,43 +805,29 @@ export fn get_greeting() [*:0]const u8 {
return greeting.ptr; 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 { export fn alloc_string(len: usize) ?[*:0]u8 {
const allocator = std.heap.c_allocator; const allocator = std.heap.c_allocator;
const buf = allocator.allocSentinel(u8, len, 0) catch return null; const buf = allocator.allocSentinel(u8, len, 0) catch return null;
return buf.ptr; return buf.ptr;
} }
export fn free_string(s: ?[*:0]u8) void { export fn free_string(s: ?[*:0]u8, len: usize) void {
if (s) |ptr| { if (s) |ptr| {
const allocator = std.heap.c_allocator; const allocator = std.heap.c_allocator;
// Need to know length to free - typically tracked separately allocator.free(ptr[0..len :0]);
// or use c_allocator which can query allocation size
_ = allocator;
_ = ptr;
} }
} }
``` ```
### Thread Safety ### 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 ```zig
const std = @import("std"); 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); var atomic_counter: std.atomic.Value(c_int) = .init(0);
export fn atomic_increment() c_int { export fn atomic_increment() c_int {

View File

@ -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 | | `@intCast(val)` without bounds check | Runtime panic | No `std.math.cast` or prior validation |
| `.?` unwrap | Runtime panic | Not guarded by `if` or `orelse` | | `.?` 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 | | `return &local_var` | Dangling pointer | Returns address of stack variable |
| `&packed_struct.field` | Undefined behavior | Pointer to packed struct field | | `&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 | | 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 #### 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 | | 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 #### 1.6.4 Returning Stack Pointer
@ -536,7 +536,7 @@ fn parseColor(byte: u8) Color {
**Right:** **Right:**
```zig ```zig
fn parseColor(byte: u8) ?Color { 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()`. **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 | | Risk | Memory corruption |
**Wrong:** **Wrong:**
@ -585,12 +585,14 @@ fn dangerous(ptr: *u32) *u64 {
**Right:** **Right:**
```zig ```zig
fn reinterpret(ptr: *u32) *[4]u8 { fn firstBytes(ptr: *u32) *align(@alignOf(u32)) [4]u8 {
return @ptrCast(ptr); // Same size // 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 #### 1.7.3 Packed Struct Field Pointer
@ -1311,13 +1313,13 @@ fn formatVersion(allocator: Allocator, major: u32, minor: u32) ![]u8 {
**Right (caller provides buffer):** **Right (caller provides buffer):**
```zig ```zig
fn formatVersion(buf: []u8, major: u32, minor: u32) []u8 { fn formatVersion(buf: []u8, major: u32, minor: u32) ![]u8 {
return std.fmt.bufPrint(buf, "{d}.{d}", .{ major, minor }) catch unreachable; return std.fmt.bufPrint(buf, "{d}.{d}", .{ major, minor });
} }
// Call site — buffer outlives the returned slice // Call site — buffer outlives the returned slice
var buf: [32]u8 = undefined; var buf: [32]u8 = undefined;
const version = formatVersion(&buf, 1, 2); const version = try formatVersion(&buf, 1, 2);
``` ```
### 3.11 Comptime Optimization ### 3.11 Comptime Optimization

View File

@ -61,7 +61,7 @@ comptime {
### Container-Level 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 ```zig
// These are computed at compile time automatically // These are computed at compile time automatically
@ -187,7 +187,7 @@ fn sumComptime(comptime values: []const i32) i32 {
### inline for ### 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 ```zig
fn printFields(value: anytype) void { fn printFields(value: anytype) void {
@ -219,7 +219,7 @@ fn eqlAny(comptime T: type, a: T, b: T) bool {
| Need | Use | Reason | | 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 | | Access runtime values in body | `inline for` | Comptime can't see runtime |
| Type-level computation only | `comptime for` | Clearer intent, no code gen | | Type-level computation only | `comptime for` | Clearer intent, no code gen |
| Generate code per iteration | `inline for` | Each iteration = separate code | | 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(); const native_endian = builtin.cpu.arch.endian();
pub fn readIntBig(comptime T: type, bytes: []const u8) T { 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)].*); const value: T = @bitCast(bytes[0..@sizeOf(T)].*);
if (comptime native_endian == .big) { if (comptime native_endian == .big) {
return value; return value;
@ -261,15 +263,15 @@ pub fn readIntBig(comptime T: type, bytes: []const u8) T {
### Propagating Across Functions ### 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 ```zig
// WITHOUT inline: branch exists at runtime // The enabled branch is specialized at compile time.
fn maybeLog(comptime enabled: bool, msg: []const u8) void { fn maybeLog(comptime enabled: bool, msg: []const u8) void {
if (enabled) std.debug.print("{s}\n", .{msg}); 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 { inline fn maybeLogInline(comptime enabled: bool, msg: []const u8) void {
if (comptime enabled) std.debug.print("{s}\n", .{msg}); 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 { ... } fn typeFromName(name: []const u8) type { ... }
``` ```
### No I/O at Comptime ### Comptime I/O Boundary
```zig ```zig
// NOT POSSIBLE // Ordinary runtime filesystem I/O is not available at comptime.
const config = comptime std.fs.cwd().readFile("config.json"); // 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: // Alternatives:
const config = @embedFile("config.json"); // Static embedding 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` | | Type reflection | Yes | `@typeInfo`, `@TypeOf` |
| Generate types | Yes | Return struct from function | | Generate types | Yes | Return struct from function |
| Add methods to types | No | Define in type definition | | 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 | | Syscalls | No | build.zig runs as program |
| Parse strings to code | No | Parse to data structures | | Parse strings to code | No | Parse to data structures |
| Host detection | No | Build system queries | | Host detection | No | Build system queries |

View File

@ -31,7 +31,7 @@ Key 0.16 language changes:
### Primitive Types ### Primitive Types
```zig ```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 i8, i16, i32, i64, i128, isize // signed
u8, u16, u32, u64, u128, usize // unsigned u8, u16, u32, u64, u128, usize // unsigned
i7, u24, i53 // arbitrary widths i7, u24, i53 // arbitrary widths
@ -75,6 +75,7 @@ const z: u32 = @bitCast(float_val); // reinterpret bits
```zig ```zig
// Fixed-size arrays // Fixed-size arrays
const arr: [5]u8 = .{ 1, 2, 3, 4, 5 }; 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 arr2 = [_]u8{ 1, 2, 3 }; // infer length
const zeros = [_]u8{0} ** 100; // repeat pattern const zeros = [_]u8{0} ** 100; // repeat pattern
@ -89,7 +90,7 @@ const len = arr.len;
// Iteration // Iteration
for (arr) |elem| { ... } for (arr) |elem| { ... }
for (arr, 0..) |elem, i| { ... } // with index for (arr, 0..) |elem, i| { ... } // with index
for (&arr) |*elem| { elem.* = 0; } // mutable for (&mutable_arr) |*elem| { elem.* = 0; } // mutable
``` ```
### Tuples ### Tuples
@ -220,7 +221,10 @@ for (a, b, c) |x, y, z| { ... }
// Mutable iteration // Mutable iteration
for (&items) |*item| { item.* = new_value; } 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| { ... } inline for (0..10) |i| { ... }
``` ```
@ -257,7 +261,7 @@ fn example() void {
} }
// Only runs on error return // Only runs on error return
fn example() !void { fn example() !*Resource {
const ptr = try allocate(); const ptr = try allocate();
errdefer free(ptr); // runs only if function returns error errdefer free(ptr); // runs only if function returns error
try doSomething(ptr); try doSomething(ptr);
@ -587,8 +591,12 @@ const ptr: [*]u8 = buffer.ptr;
const next = ptr + 1; const next = ptr + 1;
const offset = ptr + n; const offset = ptr + n;
// Single-item pointers do NOT support arithmetic // A single-item pointer can only establish a one-element slice by itself.
// Use slicing instead: 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]; const slice = ptr[0..n];
``` ```
@ -650,15 +658,9 @@ fn isInteger(comptime T: type) bool {
return @typeInfo(T) == .int; return @typeInfo(T) == .int;
} }
fn fieldNames(comptime T: type) []const []const u8 { fn fieldNames(comptime T: type) *const [std.meta.fields(T).len][:0]const u8 {
const info = @typeInfo(T); // std.meta.fieldNames returns comptime-backed fixed storage.
if (info != .@"struct") @compileError("expected struct"); return std.meta.fieldNames(T);
var names: [info.@"struct".fields.len][]const u8 = undefined;
for (info.@"struct".fields, 0..) |field, i| {
names[i] = field.name;
}
return &names;
} }
``` ```

View File

@ -64,7 +64,8 @@ Comprehensive patterns for writing idiomatic Zig code. Zig 0.16.0 changes I/O ow
#### Allocator Setup #### Allocator Setup
```zig ```zig
// Debug allocator (development - detects leaks, use-after-free) // 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; var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit(); defer _ = gpa.deinit();
const allocator = gpa.allocator(); 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. **When to use:** Multiple return values, array element extraction, SIMD vector unpacking.
#### Hashed Mappings Storage #### 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 ```zig
// From llvm/Builder.zig - demonstrating the pattern of parallel maps for interned data // 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_indices: std.ArrayListUnmanaged(u32),
string_bytes: std.ArrayListUnmanaged(u8), string_bytes: std.ArrayListUnmanaged(u8),
types: std.AutoArrayHashMapUnmanaged(String, Type), types: std.array_hash_map.Auto(String, Type),
type_map: std.AutoArrayHashMapUnmanaged(void, void), type_map: std.array_hash_map.Auto(void, void),
type_items: std.ArrayListUnmanaged(Type.Item), type_items: std.ArrayListUnmanaged(Type.Item),
type_extra: std.ArrayListUnmanaged(u32), type_extra: std.ArrayListUnmanaged(u32),
attributes: std.AutoArrayHashMapUnmanaged(Attribute.Storage, void), attributes: std.array_hash_map.Auto(Attribute.Storage, void),
attributes_map: std.AutoArrayHashMapUnmanaged(void, void), attributes_map: std.array_hash_map.Auto(void, void),
attributes_indices: std.ArrayListUnmanaged(u32), attributes_indices: std.ArrayListUnmanaged(u32),
``` ```
@ -751,19 +752,19 @@ try stdout.print("{f}", .{version});
**When to use:** Any type that needs custom string representation. **When to use:** Any type that needs custom string representation.
#### Custom Type JSON #### 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 ```zig
pub fn ArrayHashMap(comptime T: type) type { pub fn ArrayHashMap(comptime T: type) type {
return struct { return struct {
map: std.StringArrayHashMapUnmanaged(T) = .empty, map: std.array_hash_map.String(T) = .empty,
pub fn jsonParse( pub fn jsonParse(
allocator: Allocator, allocator: Allocator,
source: anytype, source: anytype,
options: ParseOptions, options: ParseOptions,
) !@This() { ) !@This() {
var map: std.StringArrayHashMapUnmanaged(T) = .empty; var map: std.array_hash_map.String(T) = .empty;
errdefer map.deinit(allocator); errdefer map.deinit(allocator);
if (.object_begin != try source.next()) return error.UnexpectedToken; if (.object_begin != try source.next()) return error.UnexpectedToken;
@ -1096,11 +1097,9 @@ pub const Node = enum(u32) {
return @enumFromInt(@intFromEnum(r.start) + i); return @enumFromInt(@intFromEnum(r.start) + i);
} }
/// Iterate over all nodes in range. // A Range stores indices, not node values or an address. Use at() to
pub fn slice(r: Range) []const Node { // iterate, or pass the owning node storage to an accessor that returns
// Note: requires nodes stored contiguously // the corresponding data slice.
return @ptrCast(@as([*]const u32, @ptrFromInt(@intFromEnum(r.start)))[0..r.len]);
}
}; };
}; };
@ -1121,6 +1120,7 @@ When individual node deletion is needed, maintain a freelist stack:
```zig ```zig
pub const NodePool = struct { pub const NodePool = struct {
allocator: std.mem.Allocator,
nodes: std.ArrayListUnmanaged(Node.Data), nodes: std.ArrayListUnmanaged(Node.Data),
/// Head of freelist, or none if no free slots. /// Head of freelist, or none if no free slots.
free_head: OptionalNode = .none, free_head: OptionalNode = .none,
@ -1133,7 +1133,7 @@ pub const NodePool = struct {
} }
// Allocate new slot // Allocate new slot
const index: Node = @enumFromInt(self.nodes.items.len); const index: Node = @enumFromInt(self.nodes.items.len);
try self.nodes.append(undefined); try self.nodes.append(self.allocator, undefined);
return index; 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 ```zig
fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void { fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void {
var map: Self = .{}; 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) - String/symbol interning (hash table + byte array)
- Any operation where failure after partial mutation leaves invalid state - 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 ### IV. Performance Patterns

View File

@ -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.testing.allocator` | Unit tests (leak detection) | No |
| `std.heap.FixedBufferAllocator` | Stack-based, bounded size known | Optional | | `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.page_allocator` | Backing for other allocators | Yes |
| `std.heap.c_allocator` | Linking libc, interop | 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.DebugAllocator` | Debug builds, leak/corruption detection | Configurable |
| `std.heap.smp_allocator` | ReleaseFast production multithreaded | Yes | | `std.heap.smp_allocator` | ReleaseFast production multithreaded | Yes |
| `std.heap.MemoryPool` | High-frequency same-type allocations | No | | `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.StackFallbackAllocator` | Stack buffer with heap fallback | Depends |
| `std.heap.wasm_allocator` | WebAssembly targets | Yes | | `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 7. **Many same-type objects?** Use `MemoryPool(T)` for fast create/destroy
8. **Debug build?** Use `DebugAllocator` for leak/corruption detection 8. **Debug build?** Use `DebugAllocator` for leak/corruption detection
9. **ReleaseFast production?** Use `std.heap.smp_allocator` 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 ## Common Allocators
@ -187,7 +185,7 @@ allocator.free(data);
fba.reset(); 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 ```zig
const ts_allocator = fba.threadSafeAllocator(); const ts_allocator = fba.threadSafeAllocator();
``` ```
@ -231,9 +229,9 @@ while (running) {
- `.retain_capacity` - Keep allocated pages for reuse (faster) - `.retain_capacity` - Keep allocated pages for reuse (faster)
- `.{ .retain_with_limit = N }` - Retain up to N bytes - `.{ .retain_with_limit = N }` - Retain up to N bytes
**Query current usage:** **Query retained capacity:**
```zig ```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: **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: Fast allocator for many objects of the same type. Outperforms general-purpose allocators when allocating/freeing objects in rapid succession:
```zig ```zig
var pool = std.heap.MemoryPool(MyStruct).init(std.heap.page_allocator); const allocator = std.heap.page_allocator;
defer pool.deinit(); var pool: std.heap.MemoryPool(MyStruct) = .empty;
defer pool.deinit(allocator);
// Allocate objects (very fast) // Allocate objects (very fast)
const obj1 = try pool.create(); const obj1 = try pool.create(allocator);
const obj2 = try pool.create(); const obj2 = try pool.create(allocator);
// Free returns to pool for reuse (not to backing allocator) // Free returns to pool for reuse (not to backing allocator)
pool.destroy(obj1); pool.destroy(obj1);
// Reuses freed slot // 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 // Reset all - batch destroy without individual frees
_ = pool.reset(.retain_capacity); _ = pool.reset(allocator, .retain_capacity);
``` ```
**Options:** **Options:**
```zig ```zig
// Pre-allocate slots // 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 // 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) // Non-growable (fixed capacity)
var pool = try std.heap.MemoryPoolExtra(T, .{ .growable = false }).initPreheated(allocator, 50); var pool = try std.heap.memory_pool.Extra(T, .{ .growable = false }).initCapacity(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
``` ```
### StackFallbackAllocator ### StackFallbackAllocator
@ -363,18 +348,6 @@ const small = try allocator.alloc(u8, 100);
const large = try allocator.alloc(u8, 10000); 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 ### Wasm Allocator
Optimized for WebAssembly. Uses `@wasmMemoryGrow`: 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 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. - `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. - 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. - Keep allocator naming by memory contract (`gpa`, `arena`, `scratch`) and expose stored `io` through a small accessor only when callsites need it.

View File

@ -7,11 +7,9 @@ Zig 0.16 removed the managed array hash map aliases:
- `std.ArrayHashMap` removed. - `std.ArrayHashMap` removed.
- `std.AutoArrayHashMap` removed. - `std.AutoArrayHashMap` removed.
- `std.StringArrayHashMap` removed. - `std.StringArrayHashMap` removed.
- `std.AutoArrayHashMapUnmanaged` -> `std.array_hash_map.Auto` - The unmanaged root aliases still exist but are deprecated; prefer `std.array_hash_map.Auto`, `.String`, and `.Custom`.
- `std.StringArrayHashMapUnmanaged` -> `std.array_hash_map.String`
- `std.ArrayHashMapUnmanaged` -> `std.array_hash_map.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. 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 | | Type | Description |
|------|-------------| |------|-------------|
| `AutoArrayHashMap(K, V)` | Auto-hashing for common key types | | `std.array_hash_map.Auto(K, V)` | Auto-hashing for common key types |
| `ArrayHashMap(K, V, Ctx, store_hash)` | Custom hash/equal context | | `std.array_hash_map.Custom(K, V, Ctx, store_hash)` | Custom hash/equal context |
| `StringArrayHashMap(V)` | String keys | | `std.array_hash_map.String(V)` | String keys |
| `ArrayHashMapUnmanaged(...)` | No stored allocator |
## Basic Usage ## Basic Usage
```zig ```zig
const std = @import("std"); const std = @import("std");
var map = std.AutoArrayHashMap(u32, []const u8).init(allocator); var map: std.array_hash_map.Auto(u32, []const u8) = .empty;
defer map.deinit(); defer map.deinit(allocator);
// Insert // Insert
try map.put(1, "one"); try map.put(allocator, 1, "one");
try map.put(2, "two"); try map.put(allocator, 2, "two");
try map.put(3, "three"); try map.put(allocator, 3, "three");
// Lookup // Lookup
if (map.get(2)) |value| { if (map.get(2)) |value| {
@ -58,9 +55,9 @@ if (map.contains(1)) {
## Insertion Order Preserved ## Insertion Order Preserved
```zig ```zig
try map.put(10, "ten"); try map.put(allocator, 10, "ten");
try map.put(5, "five"); try map.put(allocator, 5, "five");
try map.put(15, "fifteen"); try map.put(allocator, 15, "fifteen");
// Iteration is in insertion order: 10, 5, 15 // Iteration is in insertion order: 10, 5, 15
var it = map.iterator(); var it = map.iterator();
@ -101,13 +98,13 @@ if (map.fetchSwapRemove(key)) |kv| {
```zig ```zig
// Get existing or insert new // Get existing or insert new
const result = try map.getOrPut(key); const result = try map.getOrPut(allocator, key);
if (!result.found_existing) { if (!result.found_existing) {
result.value_ptr.* = "new_value"; result.value_ptr.* = "new_value";
} }
// Get or put with default 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 ## Index-Based Operations
@ -125,24 +122,24 @@ if (map.getIndex(key)) |idx| {
## Capacity Management ## Capacity Management
```zig ```zig
try map.ensureTotalCapacity(100); try map.ensureTotalCapacity(allocator, 100);
try map.ensureUnusedCapacity(10); try map.ensureUnusedCapacity(allocator, 10);
const cap = map.capacity(); const cap = map.capacity();
const len = map.count(); const len = map.count();
map.clearRetainingCapacity(); map.clearRetainingCapacity();
map.clearAndFree(); map.clearAndFree(allocator);
``` ```
## String Keys ## String Keys
```zig ```zig
var map = std.StringArrayHashMap(i32).init(allocator); var map: std.array_hash_map.String(i32) = .empty;
defer map.deinit(); defer map.deinit(allocator);
try map.put("apple", 1); try map.put(allocator, "apple", 1);
try map.put("banana", 2); try map.put(allocator, "banana", 2);
// Keys are stored by reference, not copied // Keys are stored by reference, not copied
// Make sure string lifetime exceeds map usage // 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, []const u8,
i32, i32,
CaseInsensitiveContext, CaseInsensitiveContext,
true, // store_hash for better performance true, // store_hash for better performance
).initContext(allocator, .{}); ) = .empty;
defer map.deinit(); defer map.deinit(allocator);
try map.put("Hello", 1); try map.putContext(allocator, "Hello", 1, .{});
_ = map.get("HELLO"); // finds it! _ = map.getContext("HELLO", .{}); // finds it!
``` ```
## Complete Example: Word Counter ## Complete Example: Word Counter
@ -185,13 +182,14 @@ pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init; var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit(); defer _ = gpa.deinit();
var counts = std.StringArrayHashMap(u32).init(gpa.allocator()); const allocator = gpa.allocator();
defer counts.deinit(); var counts: std.array_hash_map.String(u32) = .empty;
defer counts.deinit(allocator);
const words = [_][]const u8{ "apple", "banana", "apple", "cherry", "banana", "apple" }; const words = [_][]const u8{ "apple", "banana", "apple", "cherry", "banana", "apple" };
for (words) |word| { for (words) |word| {
const result = try counts.getOrPut(word); const result = try counts.getOrPut(allocator, word);
if (result.found_existing) { if (result.found_existing) {
result.value_ptr.* += 1; result.value_ptr.* += 1;
} else { } else {
@ -221,13 +219,13 @@ pub fn main() !void {
| orderedRemove | N/A | O(n) | | orderedRemove | N/A | O(n) |
| Iteration order | Undefined | Insertion order | | Iteration order | Undefined | Insertion order |
| Key/value arrays | No | Yes | | Key/value arrays | No | Yes |
| Memory layout | Scattered | Contiguous | | Sequential iteration storage | Not exposed as direct key/value arrays | Direct key/value arrays |
## Notes ## Notes
- Iteration order equals insertion order - Iteration order equals insertion order
- `swapRemove` is O(1) but changes order - `swapRemove` is O(1) but changes order
- `orderedRemove` preserves order but is O(n) - `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) - 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.

View File

@ -7,17 +7,19 @@ Dynamic array (vector) that grows as needed.
## Initialization ## Initialization
```zig ```zig
// CRITICAL: Use .empty, not .{} // Use the supported explicit empty initializer.
var list: std.ArrayList(u32) = .empty; var list: std.ArrayList(u32) = .empty;
defer list.deinit(allocator); defer list.deinit(allocator);
// With pre-allocated capacity // 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) // From existing slice (takes ownership)
var list = std.ArrayList(u32).fromOwnedSlice(existing_slice); 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 buffer: [8]i32 = undefined;
var stack = std.ArrayList(i32).initBuffer(&buffer); var stack = std.ArrayList(i32).initBuffer(&buffer);
``` ```
@ -36,7 +38,8 @@ list.appendSliceAssumeCapacity(&[_]u32{1, 2, 3});
// Access items // Access items
const items = list.items; // []T slice const items = list.items; // []T slice
const first = list.items[0]; 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 const popped = list.pop(); // returns ?T, removes last
// Insert at index // Insert at index
@ -44,8 +47,8 @@ try list.insert(allocator, 2, value);
try list.insertSlice(allocator, 2, slice); try list.insertSlice(allocator, 2, slice);
// Remove // Remove
const removed = list.orderedRemove(index); // O(n), preserves order const removed_ordered = list.orderedRemove(index); // O(n), preserves order
const removed = list.swapRemove(index); // O(1), doesn't preserve order const removed_swapped = list.swapRemove(index); // O(1), changes order
``` ```
## Capacity Management ## Capacity Management
@ -57,7 +60,8 @@ try list.ensureUnusedCapacity(allocator, 10);
// Ensure total capacity is at least N // Ensure total capacity is at least N
try list.ensureTotalCapacity(allocator, 100); 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); list.shrinkAndFree(allocator, list.items.len);
// Clear // Clear
@ -74,6 +78,7 @@ defer allocator.free(owned);
// Get null-terminated slice // Get null-terminated slice
const z_str = try list.toOwnedSliceSentinel(allocator, 0); const z_str = try list.toOwnedSliceSentinel(allocator, 0);
defer allocator.free(z_str);
``` ```
## Iteration ## Iteration
@ -123,14 +128,18 @@ When inserting into multiple containers or when partial mutation would corrupt s
```zig ```zig
// BAD - partial failure leaves invalid state // 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 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 // GOOD - reserve first, then mutate
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 {
// Phase 1: Reserve (fallible, but no mutation) 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 list.ensureUnusedCapacity(gpa, 1);
try map.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) // Phase 3: Mutate (infallible)
list.appendAssumeCapacity(value); list.appendAssumeCapacity(value);
map.getOrPutAssumeCapacity(value).value_ptr.* = list.items.len; map.putAssumeCapacityNoClobber(value, index);
} }
``` ```
**Key methods:** **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) - `appendAssumeCapacity(item)` - Append without allocation (cannot fail, asserts capacity)
- `appendSliceAssumeCapacity(items)` - Append slice without allocation - `appendSliceAssumeCapacity(items)` - Append slice without allocation
@ -160,6 +170,8 @@ var arr = std.BoundedArray(u8, 64){};
// NEW // NEW
var buffer: [64]u8 = undefined; var buffer: [64]u8 = undefined;
var arr = std.ArrayList(u8).initBuffer(&buffer); var arr = std.ArrayList(u8).initBuffer(&buffer);
// Note: Operations will panic if capacity exceeded // Bounded operations report capacity exhaustion.
try arr.appendBounded(value); // returns error.OutOfMemory if full try arr.appendBounded(value); // error.OutOfMemory if full
// Assume-capacity operations assert; allocator-taking operations are illegal
// for an initBuffer list.
``` ```

View File

@ -1,6 +1,9 @@
# std.ascii # 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 ## Character Classification
@ -16,9 +19,11 @@ ascii.isHex('F') // A-F, a-f, 0-9
ascii.isUpper('A') // A-Z ascii.isUpper('A') // A-Z
ascii.isLower('a') // a-z ascii.isLower('a') // a-z
ascii.isWhitespace(' ') // space, \t, \n, \r, \v, \f 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.isControl('\n') // control characters (0x00-0x1F, 0x7F)
ascii.isAscii(c) // c < 128 ascii.isAscii(c) // c < 128
ascii.isGraphical('!') // printable ASCII excluding space
ascii.isPunctuation('!') // ASCII punctuation
``` ```
## Case Conversion ## Case Conversion
@ -29,16 +34,17 @@ ascii.toUpper('a') // 'A'
ascii.toLower('A') // 'a' ascii.toLower('A') // 'a'
// Strings - to buffer // Strings - to buffer
var buf: [100]u8 = undefined; var lower_buf: [100]u8 = undefined;
const lower = ascii.lowerString(&buf, "HeLLo"); // "hello" var upper_buf: [100]u8 = undefined;
const upper = ascii.upperString(&buf, "HeLLo"); // "HELLO" const lower = ascii.lowerString(&lower_buf, "HeLLo"); // "hello"
const upper = ascii.upperString(&upper_buf, "HeLLo"); // "HELLO"
// Strings - allocating // Strings - allocating
const lower = try ascii.allocLowerString(allocator, "HeLLo"); const allocated_lower = try ascii.allocLowerString(allocator, "HeLLo");
defer allocator.free(lower); // "hello" defer allocator.free(allocated_lower); // "hello"
const upper = try ascii.allocUpperString(allocator, "HeLLo"); const allocated_upper = try ascii.allocUpperString(allocator, "HeLLo");
defer allocator.free(upper); // "HELLO" defer allocator.free(allocated_upper); // "HELLO"
``` ```
## Case-Insensitive Comparison ## Case-Insensitive Comparison
@ -52,7 +58,7 @@ ascii.startsWithIgnoreCase("Hello World", "hello") // true
ascii.endsWithIgnoreCase("Hello World", "WORLD") // true ascii.endsWithIgnoreCase("Hello World", "WORLD") // true
// Search // Search
ascii.indexOfIgnoreCase("Hello World", "world") // ?usize = 6 ascii.findIgnoreCase("Hello World", "world") // ?usize = 6
// Lexicographical order // Lexicographical order
ascii.orderIgnoreCase("abc", "ABC") // .eq ascii.orderIgnoreCase("abc", "ABC") // .eq
@ -130,6 +136,7 @@ fn isAsciiString(s: []const u8) bool {
```zig ```zig
// Use ascii.lowerString to normalize keys // Use ascii.lowerString to normalize keys
var buf: [64]u8 = undefined; var buf: [64]u8 = undefined;
if (user_input.len > buf.len) return error.InputTooLong;
const normalized = ascii.lowerString(&buf, user_input); const normalized = ascii.lowerString(&buf, user_input);
if (map.get(normalized)) |value| { if (map.get(normalized)) |value| {
// found // found
@ -140,5 +147,7 @@ if (map.get(normalized)) |value| {
- All functions handle bytes > 127 gracefully (return `false` for classification) - All functions handle bytes > 127 gracefully (return `false` for classification)
- Functions use `u8` not `u7` for convenience - 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 - `lowerString`/`upperString` assert output buffer is large enough
- Normalize stored keys and lookup keys with the same convention

View File

@ -19,8 +19,9 @@ Lock-free atomics do not need `std.Io`. Blocking synchronization should use `std
```zig ```zig
std.atomic.Value(T) // Atomic wrapper for T (integers, enums, floats, bools, pointers) 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.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 ## 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 - **x86/x86_64**: `pause` instruction
- **AArch64**: `isb` instruction - **AArch64**: `isb` instruction
- **ARM**: `yield` instruction (v6k+) - **ARM**: feature-dependent yield/spin hint
- **RISC-V**: `pause` (Zihintpause extension) - **RISC-V**: `pause` (Zihintpause extension)
- **Others**: No-op - **Some unsupported targets**: No-op
## Cache Line Size ## 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 ```zig
const cache_line = std.atomic.cache_line; // 64, 128, etc. 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) - x86_64, AArch64: 128 bytes (big cores)
- ARM, MIPS: 32 bytes - ARM, MIPS: 32 bytes
- Most others: 64 bytes - Most others: 64 bytes
@ -300,18 +301,20 @@ fn Stack(comptime T: type) type {
```zig ```zig
var initialized = std.atomic.Value(bool).init(false); 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; var global_resource: ?*Resource = null;
fn getResource() *Resource { fn getResource(io: std.Io) *Resource {
// Fast path: already initialized // Fast path: already initialized
if (initialized.load(.acquire)) { if (initialized.load(.acquire)) {
return global_resource.?; return global_resource.?;
} }
// Slow path: initialize with lock // Slow path: initialize with lock
init_mutex.lock(); // This variant deliberately makes initialization uncancelable. Use
defer init_mutex.unlock(); // `try init_mutex.lock(io)` in a cancelable function instead.
init_mutex.lockUncancelable(io);
defer init_mutex.unlock(io);
if (!initialized.load(.acquire)) { if (!initialized.load(.acquire)) {
global_resource = initializeResource(); global_resource = initializeResource();
@ -412,5 +415,5 @@ const Barrier = struct {
## See Also ## See Also
- **[std.Thread](std-thread.md)** - Higher-level synchronization (Mutex, RwLock, Condition, Semaphore) - **[std.Io synchronization](std-io.md)** - Blocking `Mutex`, `RwLock`, `Condition`, `Semaphore`, `Event`, and futex methods
- **[std.Thread.Futex](std-thread.md)** - OS-level blocking primitives - **[std.Thread](std-thread.md)** - Thread creation, joining, and thread-local facilities

View File

@ -10,7 +10,7 @@ For examples that write encoded output to files/stdout, use `std.Io.Writer` and
| Codec | Use Case | | 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 | | `standard_no_pad` | Standard Base64 without padding |
| `url_safe` | URL-safe Base64 with `=` padding | | `url_safe` | URL-safe Base64 with `=` padding |
| `url_safe_no_pad` | URL-safe Base64 without padding (JWT, URLs) | | `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"); const decoder = base64.standard.decoderWithIgnore(" \n");
var buf: [100]u8 = undefined; 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_len = try decoder.decode(buf[0..max_size], encoded);
const decoded = buf[0..decoded_len]; const decoded = buf[0..decoded_len];
// "Hello, World!" // "Hello, World!"
@ -71,6 +71,8 @@ const decoded = buf[0..decoded_len];
## Streaming Encoding ## Streaming Encoding
This fragment assumes a caller-provided `io: std.Io` and `data: []const u8`:
```zig ```zig
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var writer = std.Io.File.stdout().writer(io, &buf); 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 ### Encode binary data for JSON/URLs
```zig ```zig
fn encodeForUrl(data: []const u8, buf: []u8) []const u8 { 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); 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) ### Handle multi-line Base64 (PEM format)
```zig ```zig
fn decodePem(pem_data: []const u8, buf: []u8) ![]u8 { fn decodePemBody(base64_body: []const u8, buf: []u8) ![]u8 {
// Skip header/footer, decode with newline ignoring // 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 decoder = std.base64.standard.decoderWithIgnore("\n\r");
const max = try decoder.calcSizeUpperBound(pem_data.len); const max = decoder.calcSizeUpperBound(base64_body.len);
const len = try decoder.decode(buf[0..max], pem_data); const len = try decoder.decode(buf[0..max], base64_body);
return buf[0..len]; 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 - URL-safe uses `-` and `_` which are safe in URLs
- Padding (`=`) makes length divisible by 4 - Padding (`=`) makes length divisible by 4
- `calcSizeForSlice` gives exact size; `calcSizeUpperBound` gives max (ignores padding) - `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

View File

@ -13,9 +13,9 @@ Densely stored sets of integers with efficient set operations (union, intersecti
| Type | Size | Allocation | | 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) | | `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) | | `DynamicBitSet` | Runtime | Allocator (managed) |
| `DynamicBitSetUnmanaged` | Runtime | Allocator (unmanaged) | | `DynamicBitSetUnmanaged` | Runtime | Allocator (unmanaged) |
@ -27,7 +27,7 @@ const std = @import("std");
// StaticBitSet auto-selects best implementation // StaticBitSet auto-selects best implementation
const Flags = std.StaticBitSet(64); const Flags = std.StaticBitSet(64);
var flags = Flags.initEmpty(); var flags: Flags = .empty;
flags.set(5); flags.set(5);
flags.set(10); flags.set(10);
@ -52,7 +52,7 @@ bits.set(100);
// Resize dynamically // Resize dynamically
try bits.resize(2000, false); // false = new bits are 0 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 // Clone
var copy = try bits.clone(allocator); var copy = try bits.clone(allocator);
@ -62,8 +62,8 @@ defer copy.deinit();
## Set Operations ## Set Operations
```zig ```zig
var a = Flags.initEmpty(); var a: Flags = .empty;
var b = Flags.initEmpty(); var b: Flags = .empty;
a.set(1); a.set(2); a.set(1); a.set(2);
b.set(2); b.set(3); b.set(2); b.set(3);
@ -99,7 +99,7 @@ if (a.supersetOf(b)) {
## Iteration ## Iteration
```zig ```zig
var flags = Flags.initEmpty(); var flags: Flags = .empty;
flags.set(1); flags.set(5); flags.set(10); flags.set(1); flags.set(5); flags.set(10);
// Iterate set bits (ascending order by default) // Iterate set bits (ascending order by default)
@ -135,7 +135,7 @@ if (flags.findLastSet()) |index| {
// index of highest set bit // 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| { if (flags.toggleFirstSet()) |index| {
// returns index and unsets the bit // returns index and unsets the bit
} }
@ -171,7 +171,7 @@ const Permission = enum(u8) {
admin = 4, admin = 4,
}; };
const Permissions = std.StaticBitSet(8); const Permissions = std.StaticBitSet(@typeInfo(Permission).@"enum".fields.len);
fn hasPermission(perms: Permissions, p: Permission) bool { fn hasPermission(perms: Permissions, p: Permission) bool {
return perms.isSet(@intFromEnum(p)); return perms.isSet(@intFromEnum(p));
@ -186,14 +186,14 @@ fn revoke(perms: *Permissions, p: Permission) void {
} }
pub fn main() void { pub fn main() void {
var user_perms = Permissions.initEmpty(); var user_perms: Permissions = .empty;
grant(&user_perms, .read); grant(&user_perms, .read);
grant(&user_perms, .write); grant(&user_perms, .write);
var admin_perms = Permissions.initFull(); const admin_perms: Permissions = .full;
// Check if user has all admin permissions // 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) // 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 - `StaticBitSet` is zero-allocation, copyable by value
- `DynamicBitSet` requires allocation, call `deinit()` - `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 - Iteration order is index order, not insertion order
- Use `std.enums.EnumSet` for enum-based bit flags - Use `std.enums.EnumSet` for enum-based bit flags

View File

@ -17,7 +17,8 @@ const std = @import("std");
var map = std.BufMap.init(allocator); var map = std.BufMap.init(allocator);
defer map.deinit(); // frees all stored strings 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("HOME", "/Users/alice");
try map.put("PATH", "/usr/bin"); try map.put("PATH", "/usr/bin");
@ -26,10 +27,8 @@ if (map.get("HOME")) |home| {
std.debug.print("home: {s}\n", .{home}); std.debug.print("home: {s}\n", .{home});
} }
// Get pointer (invalidated on resize) // Replace through the public ownership-aware operation.
if (map.getPtr("PATH")) |path_ptr| { try map.put("PATH", "/new/path");
path_ptr.* = try map.copy("/new/path"); // update in place
}
// Remove (frees both key and value) // Remove (frees both key and value)
map.remove("PATH"); map.remove("PATH");
@ -44,8 +43,12 @@ const n = map.count();
// putMove takes ownership instead of copying // putMove takes ownership instead of copying
const key = try allocator.dupe(u8, "MY_KEY"); const key = try allocator.dupe(u8, "MY_KEY");
const value = try allocator.dupe(u8, "my_value"); const value = try allocator.dupe(u8, "my_value");
try map.putMove(key, value); map.putMove(key, value) catch |err| {
// Don't free key/value - map owns them now allocator.free(key);
allocator.free(value);
return err;
};
// On success, the map owns both buffers.
``` ```
## BufMap: Iteration ## BufMap: Iteration
@ -169,8 +172,13 @@ pub fn main() !void {
## Notes ## Notes
- All strings are copied on insert/put, freed on remove/deinit - A new insertion copies both strings. Replacing an existing key retains its
- Use `putMove` to transfer ownership instead of copying stored key and replaces the owned value.
- `get()` returns borrowed slice - don't store long-term - `putMove` transfers ownership only on success; on error the caller retains it.
- Iteration order is not insertion order (hash map) - 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` - For non-owning string maps, use `std.StringHashMap`

View File

@ -457,7 +457,7 @@ const zlib = b.dependency("zlib", .{
exe.root_module.addImport("zlib", zlib.module("zlib")); exe.root_module.addImport("zlib", zlib.module("zlib"));
// Get artifact from dependency // Get artifact from dependency
exe.linkLibrary(zlib.artifact("z")); exe.root_module.linkLibrary(zlib.artifact("z"));
// Get path from dependency // Get path from dependency
const include_path = zlib.path("include"); const include_path = zlib.path("include");
@ -495,8 +495,8 @@ const dawn_dep = switch (target.result.os.tag) {
if (dawn_dep) |dep| { if (dawn_dep) |dep| {
// Dependency is available, use normally // Dependency is available, use normally
exe.addLibraryPath(dep.path("lib")); exe.root_module.addLibraryPath(dep.path("lib"));
exe.linkSystemLibrary("dawn"); exe.root_module.linkSystemLibrary("dawn", .{});
} }
``` ```
@ -542,7 +542,7 @@ run_step.dependOn(&run_cmd.step);
const cmd = b.addSystemCommand(&.{ "git", "describe", "--tags" }); const cmd = b.addSystemCommand(&.{ "git", "describe", "--tags" });
// Capture output // Capture output
const version = cmd.captureStdOut(); const version = cmd.captureStdOut(.{});
// Use output as file // Use output as file
const version_file = b.addInstallFile(version, "version.txt"); const version_file = b.addInstallFile(version, "version.txt");
@ -705,7 +705,7 @@ const config_h = b.addConfigHeader(.{
.VERSION_STRING = "1.0.0", .VERSION_STRING = "1.0.0",
}); });
exe.addConfigHeader(config_h); exe.root_module.addConfigHeader(config_h);
``` ```
### Code Generation with Zig Tool ### Code Generation with Zig Tool
@ -729,9 +729,8 @@ exe.root_module.addAnonymousImport("schema", .{
## C/C++ Integration ## C/C++ Integration
> **Note:** Compile-level methods like `exe.addCSourceFiles()`, `exe.linkSystemLibrary()`, > **Note:** Zig 0.16 configures compilation and linking on the artifact's `root_module`.
> `exe.addIncludePath()`, `exe.linkLibC()` are **deprecated** (to be removed after 0.15.0). > Older Compile-level methods are no longer the API shown here.
> Use `exe.root_module.*` equivalents shown below.
### Adding C Sources ### Adding C Sources
```zig ```zig
@ -762,11 +761,11 @@ exe.root_module.linkSystemLibrary("pthread", .{});
exe.root_module.linkSystemLibrary("ssl", .{}); exe.root_module.linkSystemLibrary("ssl", .{});
// Static library file // Static library file
exe.addObjectFile(b.path("lib/libfoo.a")); exe.root_module.addObjectFile(b.path("lib/libfoo.a"));
// Library search path // Library search path
exe.addLibraryPath(b.path("lib")); exe.root_module.addLibraryPath(b.path("lib"));
exe.addRPath(b.path("lib")); exe.root_module.addRPath(b.path("lib"));
// Link libc (set via createModule options or directly) // Link libc (set via createModule options or directly)
exe.root_module.link_libc = true; 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"); exe.root_module.root_source_file = b.path("src/main.zig");
// As include path // As include path
exe.addIncludePath(dep.path("include")); exe.root_module.addIncludePath(dep.path("include"));
// Install // Install
b.installFile(generated_file, "share/output.txt"); 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 ```zig
const LazyPath = union(enum) { const LazyPath = union(enum) {
// Path relative to build root (most common) // 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) // 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) // Absolute or CWD-relative path (use sparingly)
cwd_relative: []const u8, cwd_relative: []const u8,
// Path inside a dependency package // 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" const parent = base.dirname(); // "src/modules"
// Concatenate subpath // 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 // 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 ## Build Allocation
@ -1123,13 +1128,5 @@ fmt_step.dependOn(&fmt.step);
``` ```
### Clean Step ### Clean Step
```zig
const clean_step = b.step("clean", "Clean build artifacts");
clean_step.dependOn(&b.addRemoveDirTree(b.path("zig-out")).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.
// 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);
}
```

View File

@ -31,13 +31,13 @@ Use `std.c` when:
Prefer higher-level alternatives when available: Prefer higher-level alternatives when available:
```zig ```zig
// High-level (recommended) // 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 // Checked OS-level wrappers live under std.posix on supported targets. Their
const fd = try std.posix.open("data.txt", .{}, 0); // signatures and flag types are target-aware and report Zig errors.
// C-level (direct libc, lowest level) // C-level std.c bindings are raw libc calls: check the return code and errno.
const fd = std.c.open("data.txt", .{}, 0); const fd = std.c.open("data.txt", flags, mode);
``` ```
## Fundamental Types ## Fundamental Types
@ -94,21 +94,11 @@ c.pid_t // Process ID (platform-specific)
```zig ```zig
const c = std.c; 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; const stat: c.Stat = undefined;
// Fields (vary by platform): _ = stat;
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
``` ```
### I/O Vectors ### I/O Vectors
@ -273,10 +263,10 @@ c.sockaddr // Generic socket address
.family // Address family (sa_family_t) .family // Address family (sa_family_t)
.data // Address data .data // Address data
c.sockaddr_in // IPv4 address (from std.posix) c.sockaddr.in // IPv4 address, when exported for the selected target
c.sockaddr_in6 // IPv6 address c.sockaddr.in6 // IPv6 address, when exported for the selected target
c.sockaddr_un // Unix domain socket c.sockaddr.un // Unix domain socket, on applicable targets
c.sockaddr_storage // Large enough for any address c.sockaddr.storage // Generic storage, on applicable targets
c.socklen_t // Socket address length type c.socklen_t // Socket address length type
c.sa_family_t // Address family type c.sa_family_t // Address family type
@ -307,10 +297,10 @@ c.SOCK // Socket types
.CLOEXEC // Set close-on-exec .CLOEXEC // Set close-on-exec
.NONBLOCK // Non-blocking .NONBLOCK // Non-blocking
c.SOL // Socket level for options c.SOL // Target-specific socket option levels
.SOCKET // Socket-level options .SOCKET // Widely available socket-level option value
.IP, .IPV6 // IP-level options // Other members vary by target. Protocol numbers such as IP/TCP/UDP are
.TCP, .UDP // Protocol-level options // not a portable generic `SOL` member set.
c.SO // Socket options (SOL_SOCKET level) c.SO // Socket options (SOL_SOCKET level)
.REUSEADDR, .REUSEPORT .REUSEADDR, .REUSEPORT
@ -455,8 +445,7 @@ c.MAP // mmap flags
.SHARED // Share changes .SHARED // Share changes
.PRIVATE // Private copy-on-write .PRIVATE // Private copy-on-write
.FIXED // Use exact address .FIXED // Use exact address
.ANONYMOUS // No file backing (Linux/BSD) .ANONYMOUS // No file backing on targets that export this spelling
.ANON // Alias for ANONYMOUS
.NORESERVE // Don't reserve swap .NORESERVE // Don't reserve swap
.STACK // Stack mapping .STACK // Stack mapping
// Platform-specific flags // Platform-specific flags
@ -714,6 +703,8 @@ c.port_event // Port event structure
## Libc Function Bindings ## 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 ### File Operations
```zig ```zig
c.close // Close file descriptor c.close // Close file descriptor
@ -792,49 +783,47 @@ c.getcontext // Get current context (some platforms)
### darwin (macOS/iOS) ### darwin (macOS/iOS)
```zig ```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 // Mach types and functions
darwin.mach_port_t c.mach_port_t
darwin.mach_task_self() c.mach_task_self()
darwin.mach_msg() c.mach_msg()
darwin.mach_host_self() c.mach_host_self()
darwin.mach_timebase_info() c.mach_timebase_info()
darwin.mach_absolute_time() c.mach_absolute_time()
// Exception handling // Exception handling
darwin.EXC, darwin.EXCEPTION c.EXC, c.EXCEPTION
darwin.task_set_exception_ports() c.task_set_exception_ports()
darwin.task_get_exception_ports() c.task_get_exception_ports()
// Thread state // Thread state
darwin.thread_state c.thread_state
darwin.thread_get_state() c.thread_get_state()
darwin.thread_set_state() c.thread_set_state()
// VM operations // VM operations
darwin.mach_vm_read() c.mach_vm_read()
darwin.mach_vm_write() c.mach_vm_write()
darwin.mach_vm_protect() c.mach_vm_protect()
darwin.mach_vm_region() c.mach_vm_region()
// Dispatch/GCD semaphores // Public Dispatch/GCD aliases (only names exported by this namespace)
darwin.dispatch_semaphore_create() c.dispatch
darwin.dispatch_semaphore_wait()
darwin.dispatch_semaphore_signal()
// Unfair locks // Unfair locks
darwin.os_unfair_lock c.os_unfair_lock
darwin.os_unfair_lock_lock() c.os_unfair_lock_lock()
darwin.os_unfair_lock_unlock() c.os_unfair_lock_unlock()
// Process spawning // Process spawning
darwin.posix_spawn() c.posix_spawn()
darwin.posix_spawn_file_actions_* c.posix_spawn_file_actions_*
// File copy // File copy
darwin.fcopyfile() c.fcopyfile()
darwin.COPYFILE c.COPYFILE
``` ```
### freebsd ### 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 { pub fn wrapper(fd: std.posix.fd_t, buf: []u8) !usize {
const result = c_function(fd, buf.ptr, buf.len); const result = c_function(fd, buf.ptr, buf.len);
if (result < 0) { 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 std.posix.unexpectedErrno(err);
} }
return @intCast(result); return @intCast(result);
@ -963,7 +952,7 @@ fn platformSpecificCall() void {
}, },
.macos, .ios => { .macos, .ios => {
// Darwin uses different types // Darwin uses different types
const port = std.c.darwin.mach_port_t; const port = std.c.mach_port_t;
}, },
.windows => { .windows => {
// Windows uses HANDLE // Windows uses HANDLE

View File

@ -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 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 ## Table of Contents
- [Module Structure](#module-structure) - [Module Structure](#module-structure)
@ -80,13 +80,15 @@ var output: std.Io.Writer.Allocating = .init(allocator);
defer output.deinit(); defer output.deinit();
var buffer: [flate.max_window_len]u8 = undefined; var buffer: [flate.max_window_len]u8 = undefined;
var compress: flate.Compress = .init(&output.writer, &buffer, .{ var compress: flate.Compress = try .init(
.level = .default, &output.writer,
.container = .gzip, &buffer,
}); .gzip,
.default,
);
try compress.writer.writeAll(data); try compress.writer.writeAll(data);
try compress.end(); try compress.finish();
const compressed = output.written(); const compressed = output.written();
``` ```
@ -94,7 +96,10 @@ const compressed = output.written();
### Compression Levels ### Compression Levels
```zig ```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_4, // Fastest
level_5, level_5,
level_6, // Default level_6, // Default
@ -102,7 +107,7 @@ const Level = enum {
level_8, level_8,
level_9, // Best compression level_9, // Best compression
fast, // Alias for level_4 fastest, // Alias for level_1
default, // Alias for level_6 default, // Alias for level_6
best, // Alias for level_9 best, // Alias for level_9
}; };
@ -110,12 +115,7 @@ const Level = enum {
### Huffman-Only Compression ### Huffman-Only Compression
For faster compression without LZ77 match searching: `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.
```zig
const HuffmanEncoder = flate.HuffmanEncoder;
// Used internally for Huffman-only encoding (bigger output, faster compression)
```
## Zstandard ## Zstandard
@ -171,38 +171,38 @@ LZMA decompression with streaming reader interface.
```zig ```zig
const lzma = std.compress.lzma; 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(); defer decompress.deinit();
var buf: [4096]u8 = undefined; _ = try decompress.reader.streamRemaining(&output.writer);
while (true) {
const n = try decompress.read(&buf);
if (n == 0) break;
// Process buf[0..n]
}
``` ```
### With Options ### With Options
```zig ```zig
var decompress = try lzma.decompressWithOptions(allocator, reader, .{ var decompress: lzma.Decompress = try .initOptions(
.memlimit = 128 * 1024 * 1024, // 128 MB memory limit &reader,
}); allocator,
decoder_buffer,
.{ .allow_incomplete = false },
128 * 1024 * 1024,
);
``` ```
### Decompress Type ### Decompress Type
```zig ```zig
pub fn Decompress(comptime ReaderType: type) type { // Decompress embeds `reader: std.Io.Reader` and owns the caller-supplied
return struct { // buffer after init. `deinit` frees that buffer unless `takeBuffer` first
pub const Reader = std.io.GenericReader(*Self, Error, read); // reclaims it. `initParams` and `initOptions` are the construction paths.
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;
};
}
``` ```
## LZMA2 ## LZMA2
@ -214,11 +214,12 @@ LZMA2 decompression (improved LZMA with better streaming support).
```zig ```zig
const lzma2 = std.compress.lzma2; const lzma2 = std.compress.lzma2;
var output = std.ArrayList(u8).empty; var input: std.Io.Reader = .fixed(compressed_data);
defer output.deinit(allocator); var output: std.Io.Writer.Allocating = .init(allocator);
defer output.deinit();
var stream = std.io.fixedBufferStream(compressed_data); var decode = try lzma2.Decode.init(allocator);
try lzma2.decompress(allocator, stream.reader(), output.writer(allocator)); defer decode.deinit(allocator);
_ = try decode.decompress(&input, &output);
``` ```
## XZ ## XZ
@ -230,20 +231,17 @@ XZ format decompression (LZMA2 in a container with checksums).
```zig ```zig
const xz = std.compress.xz; 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(); defer decompress.deinit();
var buf: [4096]u8 = undefined; _ = try decompress.reader.streamRemaining(&output.writer);
while (true) {
const n = try decompress.read(&buf);
if (n == 0) break;
// Process buf[0..n]
}
``` ```
### Check Types ### 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 ```zig
pub const Check = enum(u4) { pub const Check = enum(u4) {
@ -312,23 +310,24 @@ fn decompressZstd(allocator: Allocator, compressed: []const u8) ![]u8 {
```zig ```zig
fn decompressToFile( fn decompressToFile(
io: std.Io,
input_path: []const u8, input_path: []const u8,
output_path: []const u8, output_path: []const u8,
container: std.compress.flate.Container, container: std.compress.flate.Container,
) !void { ) !void {
const flate = std.compress.flate; const flate = std.compress.flate;
const input_file = try std.fs.cwd().openFile(input_path, .{}); const input_file = try std.Io.Dir.cwd().openFile(io, input_path, .{});
defer input_file.close(); defer input_file.close(io);
const output_file = try std.fs.cwd().createFile(output_path, .{}); const output_file = try std.Io.Dir.cwd().createFile(io, output_path, .{});
defer output_file.close(); defer output_file.close(io);
var input_buf: [4096]u8 = undefined; 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_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, &.{}); var decompress: flate.Decompress = .init(&input_reader.interface, container, &.{});
_ = try decompress.reader.streamRemaining(&output_writer.interface); _ = try decompress.reader.streamRemaining(&output_writer.interface);
@ -437,7 +436,7 @@ pub const Error = error{
**DEFLATE (flate)**: **DEFLATE (flate)**:
- Decompression: gzip, zlib, raw deflate - 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 - Streaming with history buffer
**Zstandard (zstd)**: **Zstandard (zstd)**:
@ -453,5 +452,5 @@ pub const Error = error{
**XZ**: **XZ**:
- Decompression only - Decompression only
- CRC32/CRC64/SHA256 integrity checks - CRC32/CRC64/SHA256 check IDs are parsed; full block-check verification is incomplete
- Multiple block support - Multiple block support

View File

@ -114,9 +114,11 @@ Blake3.hash("data", &digest, .{});
var keyed: [Blake3.digest_length]u8 = undefined; var keyed: [Blake3.digest_length]u8 = undefined;
Blake3.hash("data", &keyed, .{ .key = key }); Blake3.hash("data", &keyed, .{ .key = key });
// Key derivation // Key derivation uses the dedicated KDF mode, not keyed-hash options.
var derived: [32]u8 = undefined; 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 ### Blake2
@ -149,7 +151,8 @@ Aes256Gcm.encrypt(&ciphertext, &tag, plaintext, associated_data, nonce, key);
// Decryption // Decryption
var decrypted: [ciphertext.len]u8 = undefined; var decrypted: [ciphertext.len]u8 = undefined;
try Aes256Gcm.decrypt(&decrypted, &ciphertext, tag, associated_data, nonce, key); 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: Key constants:
@ -231,13 +234,13 @@ const hash = SipHash.hash(key, data);
const Ed25519 = std.crypto.sign.Ed25519; const Ed25519 = std.crypto.sign.Ed25519;
// Generate key pair // Generate key pair
const kp = Ed25519.KeyPair.generate(); const kp = Ed25519.KeyPair.generate(io);
// Sign message // Sign message
const sig = kp.sign(message, null); const sig = kp.sign(message, null);
// Verify signature // Verify signature
try kp.public_key.verify(sig, message); try sig.verify(message, kp.public_key);
// Returns error.SignatureVerificationFailed on failure // Returns error.SignatureVerificationFailed on failure
// Incremental signing (large messages) // Incremental signing (large messages)
@ -258,7 +261,7 @@ Key lengths:
const EcdsaP256Sha256 = std.crypto.sign.ecdsa.EcdsaP256Sha256; const EcdsaP256Sha256 = std.crypto.sign.ecdsa.EcdsaP256Sha256;
// Generate key pair // Generate key pair
const kp = EcdsaP256Sha256.KeyPair.generate(); const kp = EcdsaP256Sha256.KeyPair.generate(io);
// Sign // Sign
const sig = try kp.sign(message, null); const sig = try kp.sign(message, null);
@ -277,8 +280,8 @@ Available: `EcdsaP256Sha256`, `EcdsaP256Sha3_256`, `EcdsaP384Sha384`, `EcdsaP384
const X25519 = std.crypto.dh.X25519; const X25519 = std.crypto.dh.X25519;
// Generate key pairs for Alice and Bob // Generate key pairs for Alice and Bob
const alice = X25519.KeyPair.generate(); const alice = X25519.KeyPair.generate(io);
const bob = X25519.KeyPair.generate(); const bob = X25519.KeyPair.generate(io);
// Compute shared secret // Compute shared secret
const alice_shared = try X25519.scalarmult(alice.secret_key, bob.public_key); 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) ### ML-KEM (Post-Quantum)
```zig ```zig
const MlKem768 = std.crypto.kem.ml_kem.MlKem768; const MLKem768 = std.crypto.kem.ml_kem.MLKem768;
// Key generation // Key generation
const kp = MlKem768.KeyPair.generate(); const kp = MLKem768.KeyPair.generate(io);
// Encapsulation (sender) // Encapsulation (sender)
const encaps = kp.public_key.encaps(null); const encaps = kp.public_key.encaps(io);
const shared_secret = encaps.shared_secret; const shared_secret = encaps.shared_secret;
const ciphertext = encaps.ciphertext; const ciphertext = encaps.ciphertext;
@ -308,7 +311,7 @@ const decaps_secret = try kp.secret_key.decaps(ciphertext);
// shared_secret == decaps_secret // shared_secret == decaps_secret
``` ```
Available: `MlKem512`, `MlKem768`, `MlKem1024` Available: `MLKem512`, `MLKem768`, `MLKem1024`
## Key Derivation ## Key Derivation
@ -354,18 +357,23 @@ try argon2.kdf(
.p = 4, // parallelism .p = 4, // parallelism
}, },
.argon2id, // mode: argon2i, argon2d, or argon2id .argon2id, // mode: argon2i, argon2d, or argon2id
io,
); );
// Use preset parameters // 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) // PHC string format (for storage)
var buf: [128]u8 = undefined; 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$..." // Returns: "$argon2id$v=19$m=65536,t=3,p=4$..."
// Verify PHC-encoded hash // Verify PHC-encoded hash
try argon2.strVerify(encoded, password, null); try argon2.strVerify(encoded, password, .{ .allocator = allocator }, io);
``` ```
Parameter presets: Parameter presets:
@ -400,11 +408,14 @@ try scrypt.kdf(allocator, &hash, password, salt, scrypt.Params.interactive);
const bcrypt = std.crypto.pwhash.bcrypt; const bcrypt = std.crypto.pwhash.bcrypt;
// Hash password // Hash password
var hash: [bcrypt.hash_length]u8 = undefined; var hash: [128]u8 = undefined;
try bcrypt.strHash(password, .{ .rounds = 10 }, &hash); const hash_str = try bcrypt.strHash(password, .{
.params = .owasp,
.encoding = .phc,
}, &hash, io);
// Verify // Verify
try bcrypt.strVerify(hash_str, password); try bcrypt.strVerify(hash_str, password, .{ .silently_truncate_password = false });
``` ```
### PBKDF2 ### PBKDF2
@ -414,21 +425,21 @@ const pbkdf2 = std.crypto.pwhash.pbkdf2;
const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256; const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
var key: [32]u8 = undefined; var key: [32]u8 = undefined;
pbkdf2(HmacSha256, &key, password, salt, 100000); // 100k iterations try pbkdf2(&key, password, salt, 100000, HmacSha256); // 100k iterations
``` ```
## Secure Random ## 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 ```zig
const random = std.crypto.random;
// Random bytes // Random bytes
var key: [32]u8 = undefined; 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 n = random.int(u64);
const bounded = random.intRangeLessThan(u32, 0, 100); // [0, 100) const bounded = random.intRangeLessThan(u32, 0, 100); // [0, 100)
@ -535,10 +546,10 @@ Aes256Gcm.encrypt(&ct, &tag, pt, ad, nonce, key);
```zig ```zig
// For symmetric keys // For symmetric keys
var key: [32]u8 = undefined; var key: [32]u8 = undefined;
std.crypto.random.bytes(&key); io.random(&key);
// For asymmetric keys // For asymmetric keys
const kp = std.crypto.sign.Ed25519.KeyPair.generate(); const kp = std.crypto.sign.Ed25519.KeyPair.generate(io);
``` ```
### Nonce Management ### Nonce Management
@ -553,7 +564,7 @@ counter += 1;
// Option 2: Random (safe with XChaCha's 24-byte nonce) // Option 2: Random (safe with XChaCha's 24-byte nonce)
const XChaCha = std.crypto.aead.chacha_poly.XChaCha20Poly1305; const XChaCha = std.crypto.aead.chacha_poly.XChaCha20Poly1305;
var nonce: [XChaCha.nonce_length]u8 = undefined; var nonce: [XChaCha.nonce_length]u8 = undefined;
std.crypto.random.bytes(&nonce); io.random(&nonce);
``` ```
### Secure Password Storage ### Secure Password Storage
@ -563,11 +574,15 @@ const argon2 = std.crypto.pwhash.argon2;
// Registration: hash and store // Registration: hash and store
var buf: [128]u8 = undefined; 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 // Store hash_str in database
// Login: verify // Login: verify
argon2.strVerify(stored_hash, password, null) catch |err| { argon2.strVerify(stored_hash, password, .{ .allocator = allocator }, io) catch |err| {
if (err == error.PasswordVerificationFailed) { if (err == error.PasswordVerificationFailed) {
// Invalid password // Invalid password
} }

View File

@ -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 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 ## Quick Reference
@ -33,7 +33,7 @@ std.debug.print("loading...", .{});
## Format Specifiers ## 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 ### 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" std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a"
// Named (with struct) // 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 // Runtime width/precision
std.debug.print("{d:[width]}\n", .{ .width = 5, 42 }); std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = 5 });
std.debug.print("{d:.[precision]}\n", .{ .precision = 2, 3.14159 }); std.debug.print("{[value]d:.[precision]}\n", .{ .value = 3.14159, .precision = 2 });
``` ```
### Escape Braces ### Escape Braces
@ -177,47 +177,32 @@ Panic prints message + stack trace to stderr, then aborts.
```zig ```zig
// Print current stack trace to stderr // Print current stack trace to stderr
std.debug.dumpCurrentStackTrace(null); std.debug.dumpCurrentStackTrace(.{});
// Skip frames until this address // Skip frames until this address
std.debug.dumpCurrentStackTrace(@returnAddress()); std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
``` ```
### Dump to Writer ### Dump to Writer
```zig ```zig
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var stderr = std.Io.File.stderr().writer(io, &buf); var locked = try io.lockStderr(&buf, null);
try std.debug.dumpCurrentStackTraceToWriter(null, &stderr.interface); defer io.unlockStderr();
try std.debug.writeCurrentStackTrace(.{}, locked.terminal());
``` ```
### Capture Stack Trace ### Capture Stack Trace
```zig ```zig
var addrs: [32]usize = undefined; var addrs: [32]usize = undefined;
var trace: std.builtin.StackTrace = .{ const trace = std.debug.captureCurrentStackTrace(.{}, &addrs);
.instruction_addresses = &addrs,
.index = 0,
};
std.debug.captureStackTrace(@returnAddress(), &trace);
// Later: print captured trace // Later: print captured trace
std.debug.dumpStackTrace(trace); std.debug.dumpStackTrace(&trace);
``` ```
### StackIterator `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.
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});
}
```
## Hex Dump ## Hex Dump
@ -229,13 +214,12 @@ std.debug.dumpHex(data);
// Output: // Output:
// 7fff5fbff8a0 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 00 01 02 Hello, World!... // 7fff5fbff8a0 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 00 01 02 Hello, World!...
// Dump to writer // Fallible dump to an existing terminal abstraction
var buf: [256]u8 = undefined; try std.debug.dumpHexFallible(terminal, data);
var aw: std.io.Writer.Allocating = .init(allocator);
defer aw.deinit();
try std.debug.dumpHexFallible(&aw.writer, .no_color, 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: Output format:
- Address (lowercase hex) - Address (lowercase hex)
- 16 bytes per line (uppercase hex) - 16 bytes per line (uppercase hex)
@ -282,7 +266,7 @@ if (MyTrace.enabled) {
std.debug.print("trace: {}", .{trace}); 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 ## SafetyLock
@ -304,8 +288,8 @@ fn checkNotLocked() void {
} }
``` ```
- In Debug/ReleaseSafe: actively tracks lock state - `SafetyLock` follows runtime-safety mode: active in Debug and ReleaseSafe, and a no-op in ReleaseFast and ReleaseSmall.
- In ReleaseFast/ReleaseSmall: all methods are no-ops - `Trace` has the separate Debug-only default described above.
## Source Location ## Source Location
@ -327,7 +311,7 @@ const unknown = SourceLocation.invalid;
```zig ```zig
const Symbol = std.debug.Symbol; const Symbol = std.debug.Symbol;
// Symbol with resolved source location // Resolved fields are optional because debug information may be incomplete.
const sym: Symbol = .{ const sym: Symbol = .{
.name = "myFunction", .name = "myFunction",
.compile_unit_name = "main.zig", .compile_unit_name = "main.zig",
@ -335,7 +319,7 @@ const sym: Symbol = .{
}; };
// Unknown symbol // Unknown symbol
const unknown: Symbol = .{}; // name = "???", compile_unit_name = "???" const unknown: Symbol = .unknown; // all three fields are null
``` ```
## Segfault Handling ## 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. **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: 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`.
```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);
```
## Valgrind Detection ## Valgrind Detection
@ -389,28 +359,22 @@ if (std.debug.inValgrind()) {
// Get debug info for current executable // Get debug info for current executable
const info = try std.debug.getSelfDebugInfo(); const info = try std.debug.getSelfDebugInfo();
// Get symbol at address // Public methods are target-specific through SelfInfo. A broadly available
const symbol = try info.getSymbolAtAddress(allocator, address); // operation is resolving the owning module name; returned storage is owned by
defer if (symbol.source_location) |sl| allocator.free(sl.file_name); // SelfInfo rather than by the caller.
const module_name = try info.getModuleName(io, address);
std.debug.print("{s}:{d}: {s}\n", .{ std.debug.print("module: {s}\n", .{module_name});
symbol.source_location.?.file_name,
symbol.source_location.?.line,
symbol.name,
});
``` ```
## Constants ## Constants
```zig ```zig
// Whether runtime safety checks are enabled // 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 // Whether platform can produce stack traces
std.debug.sys_can_stack_trace // false on WASM, MIPS, etc. std.debug.sys_can_stack_trace // false on WASM, MIPS, etc.
// Whether platform has ucontext_t
std.debug.have_ucontext
``` ```
## Submodules ## Submodules
@ -420,8 +384,8 @@ std.debug.have_ucontext
| `std.debug.Dwarf` | DWARF debug info parser | | `std.debug.Dwarf` | DWARF debug info parser |
| `std.debug.Pdb` | Windows PDB debug info parser | | `std.debug.Pdb` | Windows PDB debug info parser |
| `std.debug.SelfInfo` | Debug info for current executable | | `std.debug.SelfInfo` | Debug info for current executable |
| `std.debug.MemoryAccessor` | Safe memory access for unwinding |
| `std.debug.Coverage` | Code coverage support | | `std.debug.Coverage` | Code coverage support |
| `std.debug.cpu_context` | Target-specific native CPU context definitions |
## FullPanic ## FullPanic
@ -447,24 +411,22 @@ fn myPanicFn(msg: []const u8, ret_addr: ?usize) noreturn {
For multi-line debug output without interleaving: For multi-line debug output without interleaving:
```zig ```zig
// Lock stderr and clear any progress indicators // Lock stderr and clear any progress indicators. The returned object exposes
std.debug.lockStdErr(); // a file writer and terminal; unlock performs the final flush automatically.
defer std.debug.unlockStdErr(); var lock_buf: [256]u8 = undefined;
const locked = std.debug.lockStderr(&lock_buf);
defer std.debug.unlockStderr();
// Safe to write multiple lines // Safe to write multiple lines
var buf: [256]u8 = undefined; try locked.file_writer.interface.writeAll("Line 1\n");
var stderr = std.Io.File.stderr().writer(io, &buf); try locked.file_writer.interface.writeAll("Line 2\n");
try stderr.interface.writeAll("Line 1\n");
try stderr.interface.writeAll("Line 2\n");
try stderr.interface.flush();
``` ```
Or with a writer: The matching function is spelled `unlockStderr`:
```zig ```zig
var buf: [256]u8 = undefined; var buf: [256]u8 = undefined;
const writer = std.debug.lockStderrWriter(&buf); const locked = std.debug.lockStderr(&buf);
defer std.debug.unlockStderrWriter(); defer std.debug.unlockStderr();
try locked.file_writer.interface.print("Complex output: {}\n", .{value});
try writer.print("Complex output: {}\n", .{value});
``` ```

View File

@ -13,8 +13,8 @@ const Color = enum { red, green, blue, yellow };
const ColorSet = std.enums.EnumSet(Color); const ColorSet = std.enums.EnumSet(Color);
// Initialize // Initialize
var colors = ColorSet.initEmpty(); var colors: ColorSet = .empty;
var all = ColorSet.initFull(); const all: ColorSet = .full;
// Struct-style init // Struct-style init
var primary = ColorSet.init(.{ var primary = ColorSet.init(.{
@ -106,7 +106,7 @@ if (map.get(.red)) |value| {
} }
// Get with default // Get with default
const value = map.getOrDefault(.blue, 0); const value = map.get(.blue) orelse 0;
// Get pointer // Get pointer
if (map.getPtr(.red)) |ptr| { if (map.getPtr(.red)) |ptr| {
@ -132,10 +132,10 @@ while (it.next()) |entry| {
std.debug.print("{}: {}\n", .{ entry.key, entry.value.* }); std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
} }
// Iterate keys only // Iterate keys only by ignoring each entry's value pointer
var key_it = map.keyIterator(); var key_it = map.iterator();
while (key_it.next()) |key| { while (key_it.next()) |entry| {
std.debug.print("{}\n", .{key}); std.debug.print("{}\n", .{entry.key});
} }
``` ```
@ -171,8 +171,11 @@ for (std.enums.values(Color)) |color| {
std.debug.print("{}: {}\n", .{ color, rgb.get(color) }); std.debug.print("{}: {}\n", .{ color, rgb.get(color) });
} }
// Direct slice access // Public sequential access uses the iterator.
const slice = rgb.values; // [3]u32 var rgb_it = rgb.iterator();
while (rgb_it.next()) |entry| {
std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
}
``` ```
## EnumIndexer ## EnumIndexer
@ -191,7 +194,7 @@ const count = Indexer.count; // 3
```zig ```zig
// Get all values as slice // 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) // Safe tag name (works with non-exhaustive)
const name = std.enums.tagName(Color, .red); // "red" or null 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 { pub fn main() void {
const admin = User{ const admin = User{
.name = "admin", .name = "admin",
.perms = Permissions.initFull(), .perms = .full,
}; };
const reader = User{ const reader = User{
@ -293,8 +296,8 @@ pub fn main() void {
## Notes ## Notes
- `EnumSet`: Bit-backed, use for presence tracking - `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 - `EnumArray`: Dense, all values always present
- All are fixed-size, zero-allocation, copyable by value - All are fixed-size, zero-allocation, copyable by value
- Use `std.StaticBitSet` for non-enum integer sets - 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.

View File

@ -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.format` is replaced by `std.Io.Writer.print`.
- `std.fmt.Formatter` is renamed to `std.fmt.Alt`. - `std.fmt.Formatter` is renamed to `std.fmt.Alt`.
- `std.fmt.FormatOptions` is renamed to `std.fmt.Options`. - `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}`. - The `{D}` duration specifier was removed; format `std.Io.Duration` with `{f}`.
```zig ```zig
@ -38,13 +38,13 @@ pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
## Format String Syntax ## Format String Syntax
Full syntax: `{[arg]:[fill][alignment][width][.precision][specifier]}` Full syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}`
### Components ### Components
| Component | Description | Example | | 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` | | `fill` | Padding character | `{:0>5}` uses `0` |
| `alignment` | `<` left, `^` center, `>` right | `{:<10}` | | `alignment` | `<` left, `^` center, `>` right | `{:<10}` |
| `width` | Minimum field width | `{:10}` | | `width` | Minimum field width | `{:10}` |
@ -63,14 +63,14 @@ std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a"
### Named Arguments ### Named Arguments
```zig ```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 ### Runtime Width/Precision
```zig ```zig
std.debug.print("{d:[width]}\n", .{ .width = @as(usize, 8), 42 }); std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = @as(usize, 8) });
std.debug.print("{d:.[prec]}\n", .{ .prec = @as(usize, 2), 3.14159 }); std.debug.print("{[value]d:.[prec]}\n", .{ .value = 3.14159, .prec = @as(usize, 2) });
``` ```
### Escape Braces ### Escape Braces
@ -81,7 +81,9 @@ std.debug.print("{{literal}}\n", .{}); // "{literal}"
## Format Specifiers ## 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 | | 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 f = try std.fmt.parseInt(i32, "0o17", 0); // 15 (octal)
const g = try std.fmt.parseInt(i32, "42", 0); // 42 (decimal) 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 h = try std.fmt.parseInt(u32, "1_000_000", 10); // 1000000
const i = try std.fmt.parseInt(u32, "0xff_ff", 0); // 65535 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:** **Errors:**
- `error.NoSpaceLeft` - Buffer too small - `error.NoSpaceLeft` - Buffer too small
### bufPrintZ ### bufPrintSentinel
Format into buffer with null terminator. Format into buffer with null terminator.
```zig ```zig
var buf: [256]u8 = undefined; 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) // result is [:0]u8 = "Hello world!" (null-terminated)
``` ```
### count ### count
Count characters needed for format (without allocating). Count output bytes needed for the format (without allocating).
```zig ```zig
const len = std.fmt.count("Value: {d}, Name: {s}", .{ 42, "test" }); const len = std.fmt.count("Value: {d}, Name: {s}", .{ 42, "test" });

View File

@ -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. 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 ## Core Types
```zig ```zig
@ -41,7 +45,10 @@ const file = try std.Io.Dir.cwd().createFile(io, "out.txt", .{});
defer file.close(io); 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 ## Reading Files
@ -68,6 +75,9 @@ defer allocator.free(bytes);
``` ```
The limit uses `std.Io.Limit`. Hitting the limit returns `error.StreamTooLong`. 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 ### Read To End From Existing File
@ -130,14 +140,16 @@ if (try stdin_reader.interface.takeDelimiter('\n')) |line| {
## Directories ## Directories
```zig ```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); defer dir.close(io);
try std.Io.Dir.cwd().createDir(io, "new-dir", .default_dir); try std.Io.Dir.cwd().createDir(io, "new-dir", .default_dir);
try std.Io.Dir.cwd().createDirPath(io, "path/to/nested"); 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 ## 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. `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 ```zig
const cwd_path = try std.process.currentPathAlloc(io, allocator); const cwd_path = try std.process.currentPathAlloc(io, allocator);
@ -202,7 +217,10 @@ defer allocator.free(rel);
## Atomic Files ## 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 ## Absolute Operations

View File

@ -68,7 +68,7 @@ const h7 = hash.CityHash64.hash("hello world");
### Streaming/Incremental Hashing ### 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 ```zig
const std = @import("std"); const std = @import("std");
@ -101,7 +101,7 @@ const h3 = fnv.final();
## Auto-Hashing (Generic Types) ## 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 ```zig
const std = @import("std"); const std = @import("std");
@ -117,7 +117,7 @@ fn hashPoint(p: Point) u64 {
return hasher.final(); return hasher.final();
} }
// Works with any hashable type // Works with types accepted by autoHash; slices/pointers require deliberate handling.
fn hashAny(value: anytype) u64 { fn hashAny(value: anytype) u64 {
var hasher = std.hash.Wyhash.init(0); var hasher = std.hash.Wyhash.init(0);
std.hash.autoHash(&hasher, value); std.hash.autoHash(&hasher, value);
@ -141,7 +141,7 @@ const Strategy = std.hash.Strategy;
var hasher = std.hash.Wyhash.init(0); var hasher = std.hash.Wyhash.init(0);
const data: []const u8 = "hello"; 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); std.hash.autoHashStrat(&hasher, data, .Shallow);
// Deep: follow pointer, hash contents (one level) // Deep: follow pointer, hash contents (one level)
@ -153,7 +153,7 @@ std.hash.autoHashStrat(&hasher, data, .DeepRecursive);
| Strategy | Behavior | | 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 | | `.Deep` | Follow pointer one level, hash contents |
| `.DeepRecursive` | Follow all pointers, hash all contents | | `.DeepRecursive` | Follow all pointers, hash all contents |
@ -240,14 +240,14 @@ const Adler32 = std.hash.Adler32;
const checksum = Adler32.hash("data"); const checksum = Adler32.hash("data");
// Streaming // Streaming
var adler = Adler32.init(); var adler: Adler32 = .{};
adler.update("data"); adler.update("data");
const result = adler.final(); const result = adler.adler;
``` ```
## Using with HashMap ## 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 ```zig
const std = @import("std"); const std = @import("std");
@ -409,8 +409,13 @@ const std = @import("std");
// Requires 128-bit key // Requires 128-bit key
const key: [16]u8 = .{0} ** 16; const key: [16]u8 = .{0} ** 16;
const h64 = std.hash.SipHash64(2, 4).hash(&key, "data"); var sip64 = std.hash.SipHash64(2, 4).init(&key);
const h128 = std.hash.SipHash128(2, 4).hash(&key, "data"); 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) // Default parameters (2-4 rounds)
const SipHash = std.hash.SipHash64(2, 4); const SipHash = std.hash.SipHash64(2, 4);
@ -440,17 +445,19 @@ fn hashPair(comptime T: type, a: T, b: T) u64 {
### File Checksum ### File Checksum
```zig ```zig
fn checksumFile(path: []const u8) !u32 { fn checksumFile(io: std.Io, path: []const u8) !u32 {
const file = try std.fs.cwd().openFile(path, .{}); const file = try std.Io.Dir.cwd().openFile(io, path, .{});
defer file.close(); defer file.close(io);
var crc = std.hash.Crc32.init(); var crc: std.hash.Crc32 = .init();
var buf: [4096]u8 = undefined; var reader_buf: [4096]u8 = undefined;
var chunk: [4096]u8 = undefined;
var reader = file.reader(io, &reader_buf);
while (true) { while (true) {
const n = try file.read(&buf); const n = try reader.interface.readSliceShort(&chunk);
if (n == 0) break; if (n == 0) break;
crc.update(buf[0..n]); crc.update(chunk[0..n]);
} }
return crc.final(); return crc.final();
@ -460,15 +467,13 @@ fn checksumFile(path: []const u8) !u32 {
### Bloom Filter Hash ### Bloom Filter Hash
```zig ```zig
fn bloomHashes(data: []const u8, k: usize) []u64 { fn bloomHashes(data: []const u8, hashes: []u64) void {
var hashes: [16]u64 = undefined;
const h1 = std.hash.Wyhash.hash(0, data); const h1 = std.hash.Wyhash.hash(0, data);
const h2 = std.hash.Wyhash.hash(h1, data); const h2 = std.hash.Wyhash.hash(h1, data);
for (0..k) |i| { for (hashes, 0..) |*hash, i| {
hashes[i] = h1 +% @as(u64, i) *% h2; hash.* = h1 +% @as(u64, i) *% h2;
} }
return hashes[0..k];
} }
``` ```

View File

@ -13,9 +13,10 @@ std.AutoHashMapUnmanaged(KeyType, ValueType) // no stored allocator
std.StringHashMap(ValueType) std.StringHashMap(ValueType)
std.StringHashMapUnmanaged(ValueType) std.StringHashMapUnmanaged(ValueType)
// ArrayHashMap - preserves insertion order, fast iteration // ArrayHashMap - Zig 0.16 unmanaged ordered maps
std.ArrayHashMap(K, V, Context, store_hash) std.array_hash_map.Auto(K, V)
std.StringArrayHashMap(V) std.array_hash_map.String(V)
std.array_hash_map.Custom(K, V, Context, store_hash)
``` ```
## AutoHashMap Usage ## AutoHashMap Usage
@ -54,7 +55,7 @@ const n = map.count();
## Unmanaged Variant ## Unmanaged Variant
```zig ```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; var map: std.AutoHashMapUnmanaged(u32, []const u8) = .empty;
defer map.deinit(allocator); defer map.deinit(allocator);
@ -98,12 +99,16 @@ while (iter.next()) |entry| {
} }
// Keys only // Keys only
for (map.keys()) |key| { } var keys = map.keyIterator();
while (keys.next()) |key_ptr| { _ = key_ptr.*; }
// Values only // 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 ## Capacity
```zig ```zig
@ -138,11 +143,11 @@ var map = std.HashMap(MyKey, Value, Context, 80).initContext(allocator, context)
Preserves insertion order, supports indexed access: Preserves insertion order, supports indexed access:
```zig ```zig
var map = std.StringArrayHashMap(i32).init(allocator); var map: std.array_hash_map.String(i32) = .empty;
defer map.deinit(); defer map.deinit(allocator);
try map.put("b", 2); try map.put(allocator, "b", 2);
try map.put("a", 1); try map.put(allocator, "a", 1);
// Iterate in insertion order: "b", "a" // Iterate in insertion order: "b", "a"
for (map.keys(), map.values()) |k, v| { } for (map.keys(), map.values()) |k, v| { }
@ -152,10 +157,10 @@ const key = map.keys()[0]; // "b"
const val = map.values()[0]; // 2 const val = map.values()[0]; // 2
// Swap remove (O(1) but changes order) // Swap remove (O(1) but changes order)
map.swapRemove("b"); _ = map.swapRemove("b");
// Ordered remove (O(n) but preserves order) // Ordered remove (O(n) but preserves order)
map.orderedRemove("a"); _ = map.orderedRemove("a");
``` ```
## Common Patterns ## Common Patterns
@ -174,8 +179,15 @@ for (words) |word| {
// Cache with owned keys // Cache with owned keys
var cache = std.StringHashMap(Data).init(allocator); 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: // When inserting, dupe the key if needed:
const key_copy = try allocator.dupe(u8, external_key); const key_copy = try allocator.dupe(u8, external_key);
errdefer allocator.free(key_copy); errdefer allocator.free(key_copy);
try cache.put(key_copy, data); 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.

View File

@ -14,7 +14,7 @@ var client: std.http.Client = .{
defer client.deinit(); 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 ## Table of Contents
- [HTTP Client](#http-client) - [HTTP Client](#http-client)
@ -30,12 +30,13 @@ Older examples below may still show 0.15 client construction. Add `.io = io` and
```zig ```zig
const std = @import("std"); const std = @import("std");
pub fn main() !void { pub fn main(init: std.process.Init) !void {
var gpa: std.heap.DebugAllocator(.{}) = .init; var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit(); defer _ = gpa.deinit();
const allocator = gpa.allocator(); 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(); defer client.deinit();
// Simple GET - response body discarded // Simple GET - response body discarded
@ -49,7 +50,7 @@ pub fn main() !void {
### Fetch with Response Body ### Fetch with Response Body
```zig ```zig
var client: std.http.Client = .{ .allocator = allocator }; var client: std.http.Client = .{ .allocator = allocator, .io = io };
defer client.deinit(); defer client.deinit();
// Create writer to capture response // Create writer to capture response
@ -84,7 +85,7 @@ const result = try client.fetch(.{
For more control over the request lifecycle: For more control over the request lifecycle:
```zig ```zig
var client: std.http.Client = .{ .allocator = allocator }; var client: std.http.Client = .{ .allocator = allocator, .io = io };
defer client.deinit(); defer client.deinit();
const uri = try std.Uri.parse("https://api.example.com/resource"); const uri = try std.Uri.parse("https://api.example.com/resource");
@ -135,7 +136,13 @@ defer req.deinit();
const body = "request body content"; const body = "request body content";
try req.sendBodyComplete(@constCast(body)); 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 }; req.transfer_encoding = .{ .content_length = body.len };
var body_writer_buf: [1024]u8 = undefined; var body_writer_buf: [1024]u8 = undefined;
var bw = try req.sendBody(&body_writer_buf); 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: Connections are automatically pooled and reused:
```zig ```zig
var client: std.http.Client = .{ .allocator = allocator }; var client: std.http.Client = .{ .allocator = allocator, .io = io };
defer client.deinit(); defer client.deinit();
// Configure pool size (default 32) // Configure pool size (default 32)
@ -226,20 +233,25 @@ client.write_buffer_size = 2048; // default 1024
for (0..10) |_| { for (0..10) |_| {
var req = try client.request(.GET, uri, .{ .keep_alive = true }); var req = try client.request(.GET, uri, .{ .keep_alive = true });
defer req.deinit(); 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 ### Proxy Configuration
```zig ```zig
var client: std.http.Client = .{ .allocator = allocator }; var client: std.http.Client = .{ .allocator = allocator, .io = io };
defer client.deinit(); defer client.deinit();
// Load from environment (HTTP_PROXY, HTTPS_PROXY, etc.) // Load from environment (HTTP_PROXY, HTTPS_PROXY, etc.)
var arena = std.heap.ArenaAllocator.init(allocator); var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit(); 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: // Or configure manually:
var proxy: std.http.Proxy = .{ var proxy: std.http.Proxy = .{
@ -255,15 +267,15 @@ client.http_proxy = &proxy;
### TLS Configuration ### TLS Configuration
```zig ```zig
var client: std.http.Client = .{ .allocator = allocator }; var client: std.http.Client = .{ .allocator = allocator, .io = io };
defer client.deinit(); defer client.deinit();
// TLS is enabled by default for https:// // TLS is enabled by default for https://
// Configure TLS buffer size (affects memory usage) // Configure TLS buffer size (affects memory usage)
client.tls_buffer_size = std.crypto.tls.Client.min_buffer_len; client.tls_buffer_size = std.crypto.tls.Client.min_buffer_len;
// Force certificate rescan on next HTTPS request // Force time/root-certificate freshness to be reconsidered on the next HTTPS request.
client.next_https_rescan_certs = true; client.now = null;
// Disable TLS at compile time via std.options.http_disable_tls // Disable TLS at compile time via std.options.http_disable_tls
``` ```
@ -274,25 +286,26 @@ client.next_https_rescan_certs = true;
```zig ```zig
const std = @import("std"); const std = @import("std");
const net = std.net; const net = std.Io.net;
const http = std.http; const http = std.http;
pub fn main() !void { pub fn main(init: std.process.Init) !void {
const address = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080); const io = init.io;
var tcp_server = try address.listen(.{}); const address = try net.IpAddress.parseIp4("127.0.0.1", 8080);
defer tcp_server.deinit(); var tcp_server = try address.listen(io, .{});
defer tcp_server.deinit(io);
while (true) { while (true) {
const conn = try tcp_server.accept(); const conn = try tcp_server.accept(io);
defer conn.stream.close(); defer conn.close(io);
var read_buf: [8192]u8 = undefined; var read_buf: [8192]u8 = undefined;
var write_buf: [4096]u8 = undefined; var write_buf: [4096]u8 = undefined;
var reader = conn.stream.reader(&read_buf); var reader = conn.reader(io, &read_buf);
var writer = conn.stream.writer(&write_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| { const request = server.receiveHead() catch |err| {
std.debug.print("Failed to receive: {}\n", .{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, GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH,
pub fn requestHasBody(m: Method) bool; // POST, PUT, 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 safe(m: Method) bool; // GET, HEAD, OPTIONS, TRACE
pub fn idempotent(m: Method) bool; // GET, HEAD, PUT, DELETE, OPTIONS, TRACE pub fn idempotent(m: Method) bool; // GET, HEAD, PUT, DELETE, OPTIONS, TRACE
pub fn cacheable(m: Method) bool; // GET, HEAD pub fn cacheable(m: Method) bool; // GET, HEAD
@ -569,8 +582,8 @@ const Header = struct {
### JSON API Client ### JSON API Client
```zig ```zig
fn fetchJson(comptime T: type, allocator: Allocator, url: []const u8) !T { fn fetchJson(comptime T: type, io: std.Io, allocator: Allocator, url: []const u8) !std.json.Parsed(T) {
var client: std.http.Client = .{ .allocator = allocator }; var client: std.http.Client = .{ .allocator = allocator, .io = io };
defer client.deinit(); defer client.deinit();
var body_buf: [65536]u8 = undefined; 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; if (result.status != .ok) return error.HttpError;
const parsed = try std.json.parseFromSlice(T, allocator, body_writer.buffered(), .{}); // alloc_always prevents returned strings from borrowing body_buf. The
return parsed.value; // 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 ### POST JSON Data
```zig ```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, .{}); const json = try std.json.stringifyAlloc(allocator, data, .{});
defer allocator.free(json); defer allocator.free(json);
var client: std.http.Client = .{ .allocator = allocator }; var client: std.http.Client = .{ .allocator = allocator, .io = io };
defer client.deinit(); defer client.deinit();
const result = try client.fetch(.{ const result = try client.fetch(.{
@ -617,8 +633,8 @@ fn postJson(allocator: Allocator, url: []const u8, data: anytype) !void {
### Download File ### Download File
```zig ```zig
fn downloadFile(allocator: Allocator, url: []const u8, path: []const u8) !void { fn downloadFile(io: std.Io, allocator: Allocator, url: []const u8, path: []const u8) !void {
var client: std.http.Client = .{ .allocator = allocator }; var client: std.http.Client = .{ .allocator = allocator, .io = io };
defer client.deinit(); defer client.deinit();
const uri = try std.Uri.parse(url); 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; if (response.head.status != .ok) return error.HttpError;
const file = try std.fs.cwd().createFile(path, .{}); const file = try std.Io.Dir.cwd().createFile(io, path, .{});
defer file.close(); defer file.close(io);
var file_buf: [4096]u8 = undefined; 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; var reader_buf: [4096]u8 = undefined;
const body_reader = response.reader(&reader_buf); const body_reader = response.reader(&reader_buf);

View File

@ -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 ### File Reader
@ -148,6 +148,8 @@ const header = try reader.takeStruct(Header, .little);
const leb = try reader.takeLeb128(u64); 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 ## File Integration
Use `std.Io.Dir` and `std.Io.File`. Use `std.Io.Dir` and `std.Io.File`.
@ -206,11 +208,14 @@ Use `io.randomSecure` for fresh secure entropy with error reporting.
## Time ## Time
The release notes map: Timestamp reads now require an explicit clock choice:
- `std.time.Instant` -> `std.Io.Timestamp` ```zig
- `std.time.Timer` -> `std.Io.Timestamp` const now = std.Io.Timestamp.now(io, .real);
- `std.time.timestamp` -> `std.Io.Timestamp.now` 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. 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(...)` - `io.async(...)`
- `std.Io.Group` - `std.Io.Group`
- `std.Io.Select` - `std.Io.Select(U)` where `U` is the tagged union of possible results
- `std.Io.Batch` - `std.Io.Batch`
- `std.Io.Queue(T)` - `std.Io.Queue(Elem)`, initialized with caller-provided typed element storage
Cancelation guidance: Cancelation guidance:
@ -250,8 +255,8 @@ Blocking sync moved to `std.Io` equivalents:
| `std.Thread.Semaphore` | `std.Io.Semaphore` | | `std.Thread.Semaphore` | `std.Io.Semaphore` |
| `std.Thread.RwLock` | `std.Io.RwLock` | | `std.Thread.RwLock` | `std.Io.RwLock` |
| `std.Thread.ResetEvent` | `std.Io.Event` | | `std.Thread.ResetEvent` | `std.Io.Event` |
| `std.Thread.WaitGroup` | `std.Io.Group` | | `std.Thread.WaitGroup` | Conceptually `std.Io.Group`; submit tasks, then await or cancel the group |
| `std.Thread.Futex` | `std.Io.Futex` | | `std.Thread.Futex` | `io.futexWait*` / `io.futexWake`; waits have cancelable and uncancelable forms |
```zig ```zig
try mutex.lock(io); try mutex.lock(io);

View File

@ -128,7 +128,7 @@ defer allocator.free(json);
// To writer // To writer
var buf: [4096]u8 = undefined; 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 std.json.Stringify.value(config, .{}, &writer.interface);
try writer.interface.flush(); try writer.interface.flush();
``` ```
@ -184,22 +184,23 @@ pub const Value = union(enum) {
float: f64, float: f64,
number_string: []const u8, // unparsed number number_string: []const u8, // unparsed number
string: []const u8, string: []const u8,
array: Array, // std.ArrayList(Value) array: Array, // std.array_list.Managed(Value)
object: ObjectMap, // StringArrayHashMap(Value) object: ObjectMap, // std.array_hash_map.String(Value)
}; };
``` ```
### Building Values Manually ### Building Values Manually
```zig ```zig
var obj = std.json.ObjectMap.init(allocator); var obj: std.json.ObjectMap = .empty;
try obj.put("name", .{ .string = "test" }); defer obj.deinit(allocator);
try obj.put("count", .{ .integer = 42 }); try obj.put(allocator, "name", .{ .string = "test" });
try obj.put(allocator, "count", .{ .integer = 42 });
var arr = std.json.Array.init(allocator); var arr = std.json.Array.init(allocator);
try arr.append(.{ .integer = 1 }); try arr.append(.{ .integer = 1 });
try arr.append(.{ .integer = 2 }); 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 }; const value = std.json.Value{ .object = obj };
``` ```
@ -283,7 +284,7 @@ const Point = struct {
Build JSON incrementally: Build JSON incrementally:
```zig ```zig
var out: std.io.Writer.Allocating = .init(allocator); var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit(); defer out.deinit();
var jw: std.json.Stringify = .{ var jw: std.json.Stringify = .{
@ -345,14 +346,16 @@ const Config = struct {
debug: bool = false, debug: bool = false,
}; };
fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config { fn loadConfig(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !Config {
const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
error.FileNotFound => return Config{}, // defaults error.FileNotFound => return Config{}, // defaults
else => return err, 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); defer allocator.free(content);
const parsed = try std.json.parseFromSlice(Config, allocator, 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(); 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{ return Config{
.host = try allocator.dupe(u8, parsed.value.host), .host = try allocator.dupe(u8, parsed.value.host),
.port = parsed.value.port, .port = parsed.value.port,

View File

@ -5,7 +5,7 @@ Intrusive linked lists for O(1) insertion/removal. Nodes are embedded in user st
## When to Use ## When to Use
- O(1) insertion/removal anywhere in list - 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 - Preallocated/arena-allocated nodes
- No allocation on insert (nodes already exist) - No allocation on insert (nodes already exist)
@ -27,12 +27,13 @@ var list: std.DoublyLinkedList = .{};
var a: Item = .{ .data = 1 }; var a: Item = .{ .data = 1 };
var b: Item = .{ .data = 2 }; var b: Item = .{ .data = 2 };
var c: Item = .{ .data = 3 }; var c: Item = .{ .data = 3 };
var d: Item = .{ .data = 4 };
// Insert // Insert
list.append(&a.node); // add to end list.append(&a.node); // add to end
list.prepend(&b.node); // add to start list.prepend(&b.node); // add to start
list.insertAfter(&a.node, &c.node); // insert c after a 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 // Remove
list.remove(&a.node); // O(1) remove specific node list.remove(&a.node); // O(1) remove specific node
@ -46,20 +47,26 @@ if (list.first) |node| {
} }
// Traverse forward // Traverse forward
var it = list.first; {
while (it) |node| : (it = node.next) { var it = list.first;
const item: *Item = @fieldParentPtr("node", node); while (it) |node| : (it = node.next) {
// use item.data const item: *Item = @fieldParentPtr("node", node);
// use item.data
}
} }
// Traverse backward // Traverse backward
var it = list.last; {
while (it) |node| : (it = node.prev) { var it = list.last;
const item: *Item = @fieldParentPtr("node", node); while (it) |node| : (it = node.prev) {
// use item.data const item: *Item = @fieldParentPtr("node", node);
// use item.data
}
} }
// Concatenate (moves all from list2 to end of list1) // Concatenate (moves all from list2 to end of list1)
var list1: std.DoublyLinkedList = .{};
var list2: std.DoublyLinkedList = .{};
list1.concatByMoving(&list2); list1.concatByMoving(&list2);
// Length (O(n) - consider tracking separately) // Length (O(n) - consider tracking separately)
@ -85,10 +92,11 @@ var b: Item = .{ .data = 2 };
list.prepend(&a.node); // add to front list.prepend(&a.node); // add to front
a.node.insertAfter(&b.node); // insert b after a a.node.insertAfter(&b.node); // insert b after a
// Remove // Remove b after a, then reinsert it so the remaining operations are valid.
const first = list.popFirst(); // remove and return first _ = a.node.removeNext(); // removes and returns b
_ = a.node.removeNext(); // remove node after a list.prepend(&b.node);
list.remove(&b.node); // O(n) - must find predecessor list.remove(&a.node); // O(n) - must find predecessor
const first = list.popFirst(); // removes and returns b
// Traverse (forward only) // Traverse (forward only)
var it = list.first; var it = list.first;
@ -122,9 +130,11 @@ node.insertAfter(new_node)
node.removeNext() // ?*Node - removes and returns next node.removeNext() // ?*Node - removes and returns next
node.findLast() // *Node node.findLast() // *Node
node.countChildren() // usize 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 ## Common Pattern: LRU Cache
```zig ```zig

View File

@ -34,10 +34,12 @@ pub fn main() void {
| Level | Build Mode Default | Purpose | | Level | Build Mode Default | Purpose |
|-------|-------------------|---------| |-------|-------------------|---------|
| `.err` | Always shown | Something went wrong | | `.err` | Enabled by the default configuration | Something went wrong |
| `.warn` | Always shown | Uncertain if wrong, worth investigating | | `.warn` | Enabled by the default configuration | Uncertain if wrong, worth investigating |
| `.info` | Debug + Release | General program state | | `.info` | Enabled in common default modes | General program state |
| `.debug` | Debug only | Messages only useful for debugging | | `.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: Default level by build mode:
- **Debug**: `.debug` (all messages) - **Debug**: `.debug` (all messages)
@ -122,14 +124,10 @@ fn myLogFn(
const level_txt = comptime level.asText(); const level_txt = comptime level.asText();
const prefix = "[" ++ level_txt ++ "] (" ++ scope_prefix ++ "): "; 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 buf: [64]u8 = undefined;
var stderr = std.Io.File.stderr().writer(io, &buf); const locked = std.debug.lockStderr(&buf);
stderr.interface.print(prefix ++ format ++ "\n", args) catch return; defer std.debug.unlockStderr(); // flushes the returned writer
stderr.interface.flush() catch return; locked.file_writer.interface.print(prefix ++ format ++ "\n", args) catch return;
} }
``` ```
@ -148,7 +146,7 @@ fn process() void {
} }
// For default scope // For default scope
if (std.log.defaultLogEnabled(.debug)) { if (std.log.logEnabled(.debug, .default)) {
std.log.debug("Debug message", .{}); std.log.debug("Debug message", .{});
} }
} }
@ -177,9 +175,15 @@ fn myLogFn(
comptime format: []const u8, comptime format: []const u8,
args: anytype, args: anytype,
) void { ) void {
// Add timestamp, then forward to default // Emit the timestamp and message under one stderr lock so concurrent
std.debug.print("[{d}] ", .{applicationTimestampNow().toNanoseconds()}); // records cannot split the prefix from the message.
std.log.defaultLog(level, scope, format, args); 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, args: anytype,
) void { ) void {
const io = applicationIo(); const io = applicationIo();
const file = std.Io.Dir.cwd().openFile(io, "app.log", .{ .mode = .write_only }) catch return; // Returns a long-lived handle opened during startup with an explicit
defer file.close(io); // 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 buf: [256]u8 = undefined;
var writer = file.writer(io, &buf); var writer = file.writer(io, &buf);
@ -257,3 +263,5 @@ fn fileLogFn(
w.flush() catch return; 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.

View File

@ -108,7 +108,7 @@ const log10_int_val = std.math.log10_int(1000); // 3
```zig ```zig
// Powers // Powers
const pow_val = std.math.pow(f64, 2.0, 3.0); // 2^3 = 8.0 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 // Roots
const sqrt_val = std.math.sqrt(@as(f64, 16.0)); // 4.0 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 // Shift with overflow check
const shifted = std.math.shlExact(u8, 1, 8) catch |err| { const shifted = std.math.shlExact(u8, 2, 7) catch |err| {
return err; // Overflow: 1 << 8 doesn't fit in u8 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 ## Division Functions
```zig ```zig
@ -362,13 +364,17 @@ defer b.deinit();
// Arithmetic // Arithmetic
try a.add(&a, &b); try a.add(&a, &b);
try a.mul(&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 // Comparison
const ord = a.order(b); // .lt, .eq, or .gt const ord = a.order(b); // .lt, .eq, or .gt
// Convert to primitive (if fits) // 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 // Value doesn't fit in i128
return err; return err;
}; };
@ -386,8 +392,9 @@ try c.setString(10, "123456789012345678901234567890");
const gcd_val = std.math.gcd(@as(u32, 48), @as(u32, 18)); // 6 const gcd_val = std.math.gcd(@as(u32, 48), @as(u32, 18)); // 6
// Least common multiple // Least common multiple
const lcm_val = try std.math.lcm(@as(u32, 4), @as(u32, 6)); // 12 const lcm_val = std.math.lcm(@as(u32, 4), @as(u32, 6)); // 12
// Returns error.Overflow if result doesn't fit // lcm is not an error union; intermediate multiplication follows the selected
// integer overflow/safety behavior.
``` ```
## Gamma Functions ## Gamma Functions
@ -402,9 +409,9 @@ const lg = std.math.lgamma(f64, 100.0);
## Notes ## 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){...})` - 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.) - Hardware instructions used when available (`@sin`, `@cos`, `@sqrt`, etc.)
- `approxEqAbs` for values near zero, `approxEqRel` for larger values - `approxEqAbs` for values near zero, `approxEqRel` for larger values
- Complex number operations available in `std.math.complex` - Complex number operations available in `std.math.complex`

View File

@ -22,7 +22,9 @@ std.mem.cutPrefix(u8, path, "assets/")
std.mem.cutSuffix(u8, file_name, ".zig") 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 ## Slice Comparison & Search
@ -101,10 +103,12 @@ defer allocator.free(joined); // "a, b, c"
// Join with null terminator // Join with null terminator
const joinedZ = try std.mem.joinZ(allocator, "/", &.{ "path", "to", "file" }); const joinedZ = try std.mem.joinZ(allocator, "/", &.{ "path", "to", "file" });
defer allocator.free(joinedZ);
// [:0]u8 = "path/to/file" // [:0]u8 = "path/to/file"
// Concatenate without separator // Concatenate without separator
const concatted = try std.mem.concat(allocator, u8, &.{ "hello", " ", "world" }); const concatted = try std.mem.concat(allocator, u8, &.{ "hello", " ", "world" });
defer allocator.free(concatted);
// "hello world" // "hello world"
``` ```
@ -125,7 +129,8 @@ std.mem.trim(u8, "\n\thello\n\t", " \t\n")
// In-place replace (returns count) // In-place replace (returns count)
var buf: [100]u8 = undefined; var buf: [100]u8 = undefined;
const count = std.mem.replace(u8, "hello", "l", "L", &buf); 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 // Allocate new slice
const result = try std.mem.replaceOwned(u8, allocator, "hello", "l", "L"); 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) const byte_copy = std.mem.toBytes(val); // [4]u8 (copy)
// Bytes to value // Bytes to value
const bytes = [_]u8{ 0xEF, 0xBE, 0xAD, 0xDE }; const word_bytes: [4]u8 align(@alignOf(u32)) = .{ 0xEF, 0xBE, 0xAD, 0xDE };
const ptr = std.mem.bytesAsValue(u32, &bytes); // *const u32 const ptr = std.mem.bytesAsValue(u32, &word_bytes); // native-memory view retaining input alignment
const val = std.mem.bytesToValue(u32, &bytes); // u32 (copy) const word = std.mem.bytesToValue(u32, &word_bytes); // u32 copy
// Slice conversions // Slice conversions
const u16_slice = [_]u16{ 0x0102, 0x0304 }; const u16_slice = [_]u16{ 0x0102, 0x0304 };
const u8_slice = std.mem.sliceAsBytes(&u16_slice); // []const u8 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 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 ## Alignment
```zig ```zig
@ -182,8 +189,9 @@ std.mem.isValidAlign(3) // false
const ptr: [*]u8 = @ptrFromInt(0x123); const ptr: [*]u8 = @ptrFromInt(0x123);
const aligned = std.mem.alignPointer(ptr, 0x100); // ?[*]u8 = 0x200 const aligned = std.mem.alignPointer(ptr, 0x100); // ?[*]u8 = 0x200
// Find aligned slice within bytes // Find aligned mutable slice within bytes
const aligned_slice = std.mem.alignInBytes(bytes, 16); // ?[]align(16) u8 var storage: [128]u8 = undefined;
const aligned_slice = std.mem.alignInBytes(&storage, 16); // ?[]align(16) u8
``` ```
## Alignment Type ## Alignment Type
@ -208,14 +216,14 @@ const ok = align_val.check(0x100); // true if aligned
```zig ```zig
// To/from native endianness // To/from native endianness
const native = std.mem.littleToNative(u32, 0x12345678); const native_from_little = std.mem.littleToNative(u32, 0x12345678);
const native = std.mem.bigToNative(u32, 0x12345678); const native_from_big = std.mem.bigToNative(u32, 0x12345678);
const little = std.mem.nativeToLittle(u32, native_val); const little = std.mem.nativeToLittle(u32, native_val);
const big = std.mem.nativeToBig(u32, native_val); const big = std.mem.nativeToBig(u32, native_val);
// General conversion // General conversion
const val = std.mem.toNative(u32, x, .little); // from little to native const decoded = std.mem.toNative(u32, x, .little); // from little to native
const val = std.mem.nativeTo(u32, x, .big); // from native to big const encoded = std.mem.nativeTo(u32, x, .big); // from native to big
// Byte swap all fields in struct // Byte swap all fields in struct
std.mem.byteSwapAllFields(MyStruct, &my_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 ## Min/Max
```zig ```zig
@ -264,9 +274,9 @@ std.mem.min(i32, &slice) // 1
std.mem.max(i32, &slice) // 5 std.mem.max(i32, &slice) // 5
std.mem.minMax(i32, &slice) // .{ 1, 5 } std.mem.minMax(i32, &slice) // .{ 1, 5 }
std.mem.indexOfMin(i32, &slice) // 1 std.mem.findMin(i32, &slice) // 1
std.mem.indexOfMax(i32, &slice) // 4 std.mem.findMax(i32, &slice) // 4
std.mem.indexOfMinMax(i32, &slice) // .{ 1, 4 } std.mem.findMinMax(i32, &slice) // .{ 1, 4 }
``` ```
## Reverse & Rotate ## 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); const slice = std.mem.span(c_string);
// Index of first difference // Index of first difference
std.mem.indexOfDiff(u8, "hello", "helps") // ?usize = 3 std.mem.findDiff(u8, "hello", "helps") // ?usize = 3
// Collapse repeated elements // Collapse repeated elements
var data = "aabbcc".*; var data = "aabbcc".*;

View File

@ -7,7 +7,7 @@ Comptime type introspection and manipulation utilities. Essential for generic pr
| Function | Purpose | | Function | Purpose |
|----------|---------| |----------|---------|
| `stringToEnum(T, str)` | Convert string to enum variant | | `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 | | `fieldNames(T)` | Get field names as string slice |
| `fieldInfo(T, field)` | Get info for specific field | | `fieldInfo(T, field)` | Get info for specific field |
| `fieldIndex(T, name)` | Get field index by name | | `fieldIndex(T, name)` | Get field index by name |
@ -71,6 +71,8 @@ const MyError = error{ NotFound, Timeout };
const error_fields = std.meta.fields(MyError); 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 ### Get Field Names
```zig ```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. For tagged unions, returns the existing tag type if compatible.
### DeclEnum - Generate Enum from Declarations ### DeclEnum - Generate Enum from Declarations
@ -170,8 +174,8 @@ const ApiMethod = std.meta.DeclEnum(Api);
### Int/Float Type Construction ### Int/Float Type Construction
```zig ```zig
const U24 = std.meta.Int(.unsigned, 24); // u24 const U24 = @Int(.unsigned, 24); // u24
const I7 = std.meta.Int(.signed, 7); // i7 const I7 = @Int(.signed, 7); // i7
const F32 = std.meta.Float(32); // f32 const F32 = std.meta.Float(32); // f32
const F16 = std.meta.Float(16); // f16 const F16 = std.meta.Float(16); // f16
``` ```
@ -180,7 +184,7 @@ const F16 = std.meta.Float(16); // f16
```zig ```zig
// From type array // 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 } // Equivalent to: struct { u32, f32, bool }
// From function signature // From function signature
@ -213,9 +217,9 @@ std.meta.Elem(?[*]u8) // u8 (through optional)
### Sentinel ### Sentinel
```zig ```zig
std.meta.sentinel([:0]u8) // @as(u8, 0) const slice_sentinel = std.meta.sentinel([:0]u8).?; // @as(u8, 0)
std.meta.sentinel([*:0]u8) // @as(u8, 0) const ptr_sentinel = std.meta.sentinel([*:0]u8).?; // @as(u8, 0)
std.meta.sentinel([5: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([]u8) // null
std.meta.sentinel([5]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. **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). **Not supported:** untagged unions (compile error).
## Type Queries ## Type Queries
@ -333,13 +339,17 @@ const decls = std.meta.declarations(S);
## Error Handling ## Error Handling
```zig ```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); 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 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 ## TrailerFlags
@ -349,11 +359,12 @@ Memory-efficient optional field storage using bit flags:
```zig ```zig
const std = @import("std"); const std = @import("std");
const Flags = std.meta.TrailerFlags(struct { const Trailer = struct {
name: []const u8, name: []const u8,
age: u32, age: u32,
email: []const u8, email: []const u8,
}); };
const Flags = std.meta.TrailerFlags(Trailer);
// Initialize with some fields active // Initialize with some fields active
var flags = Flags.init(.{ var flags = Flags.init(.{
@ -364,7 +375,7 @@ var flags = Flags.init(.{
// Allocate only needed space // Allocate only needed space
const size = flags.sizeInBytes(); 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); defer allocator.free(data);
// Set values // Set values

View File

@ -1,6 +1,6 @@
# std.MultiArrayList # 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 ## When to Use
@ -54,7 +54,7 @@ const n = list.len;
When accessing multiple fields, use `slice()` to compute pointers once: When accessing multiple fields, use `slice()` to compute pointers once:
```zig ```zig
const slices = list.slice(); var slices = list.slice();
// Now access fields without recomputing offsets // Now access fields without recomputing offsets
for (slices.items(.id), slices.items(.score)) |id, score| { for (slices.items(.id), slices.items(.score)) |id, score| {
@ -75,8 +75,9 @@ list.swapRemove(index);
// O(n) but preserves order // O(n) but preserves order
list.orderedRemove(index); list.orderedRemove(index);
// Remove multiple indices (must be sorted ascending) // Remove multiple in-bounds indices from the pre-removal list. They must be
list.orderedRemoveMany(&.{ 1, 5, 7, 9 }); // sorted ascending; duplicates are allowed and count as one removed element.
list.orderedRemoveMany(&.{ 0, 1 });
``` ```
## Tagged Union Support ## Tagged Union Support
@ -96,7 +97,7 @@ try values.append(allocator, .{ .float = 3.14 });
// Access tags and data separately // Access tags and data separately
const tags = values.items(.tags); // []meta.Tag(Value) 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 // Reconstruct full union
const full = values.get(0); // Value{ .int = 42 } const full = values.get(0); // Value{ .int = 42 }
@ -132,6 +133,11 @@ list.clearAndFree(allocator);
## Clone and Transfer ## Clone and Transfer
```zig ```zig
const copy = try list.clone(allocator); var copy = try list.clone(allocator);
const owned_slice = list.toOwnedSlice(); // empties list, caller owns 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.

View File

@ -1,12 +1,12 @@
# std.Io.net Reference (Zig 0.16.0) # 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 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 ```zig
var client: std.http.Client = .{ var client: std.http.Client = .{
@ -17,626 +17,550 @@ defer client.deinit();
``` ```
## Table of Contents ## Table of Contents
- [TCP Client](#tcp-client)
- [TCP Server](#tcp-server) - [API Map](#api-map)
- [Address Types](#address-types) - [TCP Clients](#tcp-clients)
- [TCP Servers](#tcp-servers)
- [IP Address Types](#ip-address-types)
- [Stream I/O](#stream-io) - [Stream I/O](#stream-io)
- [DNS Resolution](#dns-resolution) - [DNS and Host Names](#dns-and-host-names)
- [Unix Sockets](#unix-sockets) - [Unix-Domain Sockets](#unix-domain-sockets)
- [Sockets and Datagram APIs](#sockets-and-datagram-apis)
- [Common Patterns](#common-patterns) - [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 ```zig
const std = @import("std"); const std = @import("std");
const net = std.net; const net = std.Io.net;
pub fn main() !void { pub fn main(init: std.process.Init) !void {
var gpa: std.heap.DebugAllocator(.{}) = .init; const io = init.io;
defer _ = gpa.deinit(); const host: net.HostName = try .init("example.com");
const allocator = gpa.allocator();
// Connect to host:port (handles DNS resolution) const stream = try host.connect(io, 80, .{
const stream = try net.tcpConnectToHost(allocator, "example.com", 80); .mode = .stream,
defer stream.close(); .protocol = .tcp,
});
defer stream.close(io);
// Create buffered reader/writer
var read_buf: [4096]u8 = undefined; var read_buf: [4096]u8 = undefined;
var write_buf: [1024]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); try writer.interface.writeAll(
var writer = stream.writer(&write_buf); "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n",
);
// Write request
try writer.interface.writeAll("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
try writer.interface.flush(); try writer.interface.flush();
// Read response while (reader.interface.take(4096)) |chunk| {
while (reader.interface().take(4096)) |chunk| {
std.debug.print("{s}", .{chunk}); std.debug.print("{s}", .{chunk});
} else |err| switch (err) { } else |err| switch (err) {
error.EndOfStream => {}, 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 ```zig
// Parse and connect to IP address directly (no DNS) const address = try net.IpAddress.parseIp4("192.168.1.1", 8080);
const address = try net.Address.parseIp4("192.168.1.1", 8080); const stream = try address.connect(io, .{
const stream = try net.tcpConnectToAddress(address); .mode = .stream,
defer stream.close(); .protocol = .tcp,
});
defer stream.close(io);
``` ```
### Connect with IPv6 ### IPv6 and Scoped IPv6
```zig ```zig
// IPv6 address // Pure parsing: no interface-name scope lookup.
const addr6 = try net.Address.parseIp6("::1", 8080); const loopback = try net.IpAddress.parseIp6("::1", 8080);
const stream = try net.tcpConnectToAddress(addr6); const stream = try loopback.connect(io, .{ .mode = .stream, .protocol = .tcp });
defer stream.close(); defer stream.close(io);
// IPv6 with scope ID (link-local) // Resolving `%eth0` / `%eno1` requires Io because the interface name must be
const link_local = try net.Address.resolveIp6("fe80::1%eth0", 8080); // 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 ### Basic Server
```zig ```zig
const std = @import("std"); const std = @import("std");
const net = std.net; const net = std.Io.net;
pub fn main() !void { fn serve(io: std.Io) !void {
// Create address to listen on const address: net.IpAddress = .{ .ip4 = .unspecified(8080) };
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080); var server = try address.listen(io, .{
// Start listening
var server = try address.listen(.{
.reuse_address = true, .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) { while (true) {
const conn = try server.accept(); const client = try server.accept(io);
defer conn.stream.close(); defer client.close(io);
try handleClient(io, client);
// Handle connection
try handleClient(conn.stream, conn.address);
} }
} }
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 read_buf: [4096]u8 = undefined;
var write_buf: [1024]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); const request_line = reader.interface.takeDelimiter('\n') catch |err| switch (err) {
var writer = stream.writer(&write_buf); error.ReadFailed => return reader.err.?,
error.StreamTooLong => return error.RequestLineTooLong,
// Read request
const request = reader.interface().takeDelimiter('\n') catch |err| switch (err) {
error.EndOfStream => return,
else => return err,
} orelse return; } 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.writeAll("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello");
try writer.interface.flush(); 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 ### Listen Options
```zig ```zig
const server = try address.listen(.{ const server = try address.listen(io, .{
// Allow address reuse (SO_REUSEADDR + SO_REUSEPORT on POSIX) .kernel_backlog = 128,
.reuse_address = true, .reuse_address = true,
.mode = .stream,
// Connection backlog (default 128) .protocol = .tcp,
.kernel_backlog = 256,
// Non-blocking accept (O_NONBLOCK)
.force_nonblocking = false,
}); });
``` ```
### 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 ```zig
// Listen on port 0 to let OS assign an available port const address: net.IpAddress = .{ .ip4 = .loopback(0) };
const address = net.Address.initIp4(.{ 127, 0, 0, 1 }, 0); var server = try address.listen(io, .{});
var server = try address.listen(.{}); defer server.deinit(io);
defer server.deinit();
// Get the assigned port const assigned_port = server.socket.address.getPort();
const port = server.listen_address.getPort();
std.debug.print("Listening on port {d}\n", .{port});
``` ```
## 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 ```zig
pub const Address = extern union { pub const IpAddress = union(enum) {
any: posix.sockaddr, ip4: Ip4Address,
in: Ip4Address, ip6: Ip6Address,
in6: Ip6Address,
un: posix.sockaddr.un, // Unix socket (if supported)
}; };
``` ```
### 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 ```zig
// IPv4 from bytes const loopback4: net.IpAddress = .{ .ip4 = .loopback(8080) };
const addr4 = net.Address.initIp4(.{ 127, 0, 0, 1 }, 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 explicit4: net.IpAddress = .{ .ip4 = .{
const addr6 = net.Address.initIp6( .bytes = .{ 127, 0, 0, 1 },
.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, // ::1 .port = 8080,
8080, // port } };
0, // flowinfo
0, // scope_id
);
// Unix socket
const unix = try net.Address.initUnix("/tmp/my.sock");
``` ```
### Parsing Addresses `Ip6Address` also has `flow: u32 = 0` and `interface: net.Interface = .none` fields.
### Parsing
```zig ```zig
// Parse IPv4 const addr4 = try net.IpAddress.parseIp4("192.168.1.1", 8080);
const addr4 = try net.Address.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 // Address plus optional port. IPv6 must be bracketed.
const addr6 = try net.Address.parseIp6("2001:db8::1", 8080); 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) // Handles an IPv6 interface-name scope and therefore requires Io.
const addr = try net.Address.parseIp("::1", 8080); const scoped = try net.IpAddress.resolve(io, "fe80::1%eth0", 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);
``` ```
### 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 ```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 same = address.eql(&other_address);
const port = addr.getPort(); // 8080
addr.setPort(9090);
// Get socket length for syscalls var buf: [128]u8 = undefined;
const socklen = addr.getOsSockLen();
// Compare addresses
if (addr.eql(other_addr)) {
// addresses match
}
// Format for printing
var buf: [64]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
try addr.format(&writer); try address.format(&writer); // omits an IPv6 interface-name scope
const formatted = writer.buffered(); // "127.0.0.1:8080" 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 ```zig
const Ip4Address = extern struct { pub const Ip4Address.ParseError = error{
sa: posix.sockaddr.in, Overflow,
InvalidEnd,
pub fn parse(buf: []const u8, port: u16) !Ip4Address; InvalidCharacter,
pub fn init(addr: [4]u8, port: u16) Ip4Address; Incomplete,
pub fn getPort(self: Ip4Address) u16; NonCanonical,
pub fn setPort(self: *Ip4Address, port: u16) void;
pub fn format(self: Ip4Address, w: *std.Io.Writer) !void;
}; };
``` ```
### Ip6Address For example, leading-zero forms such as `01.2.3.4` are non-canonical.
```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;
};
```
## Stream I/O ## Stream I/O
### Stream Type ### Stream Shape and Lifecycle
```zig ```text
pub const Stream = struct { pub const Stream = struct {
handle: Handle, // fd on POSIX, SOCKET on Windows socket: net.Socket,
pub fn close(s: Stream) void; pub fn close(stream: *const Stream, io: std.Io) void;
pub fn reader(stream: Stream, buffer: []u8) Reader; pub fn shutdown(stream: *const Stream, io: std.Io, how: net.ShutdownHow) !void;
pub fn writer(stream: Stream, buffer: []u8) Writer; 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 ```zig
const stream = try net.tcpConnectToHost(allocator, "example.com", 80); var read_buf: [4096]u8 = undefined;
defer stream.close(); 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) { const data = r.take(100) catch |err| switch (err) {
error.EndOfStream => &.{}, error.EndOfStream => return,
error.ReadFailed => return reader.getError().?, error.ReadFailed => return reader.err.?,
}; };
// Read until delimiter
const line = r.takeDelimiter('\n') catch |err| switch (err) { const line = r.takeDelimiter('\n') catch |err| switch (err) {
error.EndOfStream => null, error.ReadFailed => return reader.err.?,
error.StreamTooLong => return error.LineTooLong, error.StreamTooLong => return error.LineTooLong,
error.ReadFailed => return reader.getError().?,
} orelse return; } orelse return;
// Discard bytes _ = data;
_ = try r.discard(.limited(100)); _ = line;
// Stream to writer
_ = try r.streamRemaining(&output_writer);
``` ```
### 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 ```zig
var buf: [1024]u8 = undefined; var write_buf: [1024]u8 = undefined;
var writer = stream.writer(&buf); var writer = stream.writer(io, &write_buf);
const w = &writer.interface; const w = &writer.interface;
// Write bytes
try w.writeAll("Hello, World!"); try w.writeAll("Hello, World!");
// Formatted output
try w.print("Count: {d}\n", .{42}); try w.print("Count: {d}\n", .{42});
// MUST flush before close
try w.flush(); 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 ```zig
var reader = stream.reader(&buf); try stream.shutdown(io, .send); // no more application writes
const r = reader.interface(); // Continue reading until EndOfStream if the protocol expects a response.
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,
}
},
};
``` ```
## DNS Resolution `ShutdownHow` is `.recv`, `.send`, or `.both`.
### Get Address List ## DNS and Host Names
### Validation
```zig ```zig
const std = @import("std"); const host = try net.HostName.init("example.com");
const net = std.net; try net.HostName.validate("api.example.com");
pub fn main() !void { const same = host.eql(try .init("EXAMPLE.COM")); // DNS names compare case-insensitively
var gpa: std.heap.DebugAllocator(.{}) = .init; const child = host.sameParentDomain(try .init("www.example.com"));
defer _ = gpa.deinit(); ```
const allocator = gpa.allocator();
// Resolve hostname to addresses `HostName` retains a borrowed byte slice. Labels and total length are validated; the maximum is `net.HostName.max_len`.
const list = try net.getAddressList(allocator, "example.com", 80);
defer list.deinit();
// Canonical name (if available) ### Queue-Based Lookup
if (list.canon_name) |name| {
std.debug.print("Canonical name: {s}\n", .{name});
}
// Iterate addresses ```zig
for (list.addrs) |addr| { const host: net.HostName = try .init("example.com");
var buf: [64]u8 = undefined; var result_storage: [16]net.HostName.LookupResult = undefined;
var w: std.Io.Writer = .fixed(&buf); var results: std.Io.Queue(net.HostName.LookupResult) = .init(&result_storage);
try addr.format(&w);
std.debug.print("Address: {s}\n", .{w.buffered()}); 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 ```zig
// Tries each resolved address until one connects const host: net.HostName = try .init("example.com");
const stream = net.tcpConnectToHost(allocator, "example.com", 80) catch |err| switch (err) { const stream = host.connect(io, 443, .{
error.ConnectionRefused => return error.ServerDown, .mode = .stream,
error.UnknownHostName => return error.DnsError, .protocol = .tcp,
error.TemporaryNameServerFailure => return error.DnsError, }) catch |err| switch (err) {
error.UnknownHostName, error.NoAddressReturned => return error.DnsFailure,
error.ConnectionRefused => return error.ServerUnavailable,
else => return err, 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 ```zig
if (net.has_unix_sockets) { 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 ```zig
const stream = try net.connectUnixSocket("/var/run/app.sock"); const socket_path = "/tmp/my.sock";
defer stream.close(); 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; const address = try net.UnixAddress.init(socket_path);
var reader = stream.reader(&buf); var server = try address.listen(io, .{ .kernel_backlog = 128 });
var writer = stream.writer(&buf); defer server.deinit(io);
// ... 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 {};
while (true) { while (true) {
const conn = try server.accept(); const client = try server.accept(io);
defer conn.stream.close(); defer client.close(io);
// handle connection... // 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 ## Common Patterns
### Echo Server ### Echo One Connection
```zig ```zig
const std = @import("std"); fn echoConnection(io: std.Io, stream: net.Stream) !void {
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();
var read_buf: [4096]u8 = undefined; 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; _ = reader.interface.streamRemaining(&writer.interface) catch |err| switch (err) {
defer response.deinit(allocator); error.ReadFailed => return reader.err.?,
error.WriteFailed => return writer.err.?,
while (true) { };
const chunk = reader.interface().take(4096) catch |err| switch (err) { try writer.interface.flush();
error.EndOfStream => break,
error.ReadFailed => return reader.getError().?,
};
try response.appendSlice(allocator, chunk);
}
return response.toOwnedSlice(allocator);
} }
``` ```
### 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 ```zig
const std = @import("std"); fn isValidIpAddress(text: []const u8) bool {
const net = std.net; _ = net.IpAddress.parse(text, 0) catch return false;
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;
return true; return true;
} }
fn isValidHostname(hostname: []const u8) bool { fn isValidHostName(text: []const u8) bool {
return net.isValidHostName(hostname); 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 ```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 { const Pool = struct {
connections: std.ArrayList(Connection), const Entry = struct { stream: net.Stream, in_use: bool };
allocator: Allocator,
pub fn acquire(self: *Pool, address: net.Address) !net.Stream { entries: std.ArrayList(Entry) = .empty,
// Find free connection allocator: std.mem.Allocator,
for (self.connections.items) |*conn| { io: std.Io,
if (!conn.in_use) {
conn.in_use = true; fn acquire(self: *Pool, address: *const net.IpAddress) !usize {
return conn.stream; 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); const stream = try address.connect(self.io, .{ .mode = .stream, .protocol = .tcp });
try self.connections.append(self.allocator, .{ errdefer stream.close(self.io);
.stream = stream, try self.entries.append(self.allocator, .{ .stream = stream, .in_use = true });
.in_use = true, return self.entries.items.len - 1;
});
return stream;
} }
pub fn release(self: *Pool, stream: net.Stream) void { fn get(self: *Pool, index: usize) *net.Stream {
for (self.connections.items) |*conn| { return &self.entries.items[index].stream;
if (conn.stream.handle == stream.handle) {
conn.in_use = false;
return;
}
}
} }
pub fn deinit(self: *Pool) void { fn release(self: *Pool, index: usize) void {
for (self.connections.items) |conn| { self.entries.items[index].in_use = false;
conn.stream.close(); }
}
self.connections.deinit(self.allocator); 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 ```zig
pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError; error.ConnectionRefused
error.ConnectionResetByPeer
pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError; error.HostUnreachable
// Includes: ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, etc. 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 ```zig
pub const GetAddressListError = error{ net.HostName.LookupError // UnknownHostName, NameServerFailure,
TemporaryNameServerFailure, // NoAddressReturned, configuration/DNS record errors, ...
NameServerFailure,
AddressFamilyNotSupported, net.HostName.ConnectError // LookupError || net.IpAddress.ConnectError
UnknownHostName,
HostLacksNetworkAddresses,
// ... and others
};
``` ```
### Address Parse Errors The old `GetAddressListError` and `TcpConnectToHostError` aliases are not Zig 0.16 APIs.
```zig ### Stream Error Translation
pub const IPv4ParseError = error{
Overflow,
InvalidEnd,
InvalidCharacter,
Incomplete,
NonCanonical, // e.g., leading zeros like "01.02.03.04"
};
pub const IPv6ParseError = error{ `Stream.Reader` and `Stream.Writer` deliberately adapt network errors to the generic `std.Io.Reader`/`std.Io.Writer` interfaces:
Overflow,
InvalidEnd, - On `error.ReadFailed`, inspect `reader.err`.
InvalidCharacter, - On `error.WriteFailed`, inspect `writer.err`.
Incomplete, - `EndOfStream` is the normal generic-reader signal for an orderly peer close.
InvalidIpv4Mapping, - Close, shutdown, accept, connect, lookup, bind, and listen all take the explicit `io` used to create or operate the resource.
};
```

View File

@ -23,13 +23,9 @@ std.os.wasi // WebAssembly System Interface
std.os.plan9 // Plan 9 system calls std.os.plan9 // Plan 9 system calls
std.os.uefi // UEFI firmware interface std.os.uefi // UEFI firmware interface
std.os.emscripten // Emscripten runtime 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 ## Platform Submodules
@ -37,13 +33,14 @@ std.os.argv // Command line arguments (POSIX only)
```zig ```zig
// High-level (recommended for most code) // 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) // POSIX-level (cross-platform low-level)
const fd = try std.posix.open("data.txt", .{}, 0); const fd = try std.posix.open("data.txt", .{}, 0);
// OS-specific (platform-specific features) // 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 ## Linux-Specific APIs
@ -55,7 +52,7 @@ const linux = std.os.linux;
// Raw syscall interface // Raw syscall interface
const result = linux.syscall3(.write, fd, @intFromPtr(buf.ptr), buf.len); const result = linux.syscall3(.write, fd, @intFromPtr(buf.ptr), buf.len);
if (linux.E.init(result) != .SUCCESS) { if (linux.errno(result) != .SUCCESS) {
// handle error // handle error
} }
@ -74,7 +71,7 @@ _ = linux.chroot(path);
const linux = std.os.linux; const linux = std.os.linux;
// mmap with typed flags // mmap with typed flags
const addr = linux.mmap( const result = linux.mmap(
null, null,
length, length,
linux.PROT.READ | linux.PROT.WRITE, linux.PROT.READ | linux.PROT.WRITE,
@ -82,9 +79,10 @@ const addr = linux.mmap(
-1, -1,
0, 0,
); );
if (addr == linux.MAP_FAILED) { const addr: [*]u8 = switch (linux.errno(result)) {
// handle error .SUCCESS => @ptrFromInt(result),
} else => |err| return std.posix.unexpectedErrno(err),
};
// Remap // Remap
_ = linux.mremap(old_addr, old_size, new_size, .{ .MAYMOVE = true }, null); _ = linux.mremap(old_addr, old_size, new_size, .{ .MAYMOVE = true }, null);
@ -121,7 +119,7 @@ const linux = std.os.linux;
// Wait on futex // Wait on futex
_ = linux.futex( _ = linux.futex(
&futex_word, &futex_word,
.{ .op = .WAIT, .PRIVATE = true }, .{ .cmd = .WAIT, .private = true },
expected_value, expected_value,
.{ .timeout = &timeout }, .{ .timeout = &timeout },
null, null,
@ -131,7 +129,7 @@ _ = linux.futex(
// Wake waiters // Wake waiters
_ = linux.futex( _ = linux.futex(
&futex_word, &futex_word,
.{ .op = .WAKE, .PRIVATE = true }, .{ .cmd = .WAKE, .private = true },
num_to_wake, num_to_wake,
.{ .val2 = 0 }, .{ .val2 = 0 },
null, null,
@ -150,7 +148,10 @@ var act: linux.Sigaction = .{
.mask = linux.empty_sigset, .mask = linux.empty_sigset,
.flags = .{}, .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 // Kill process
_ = linux.kill(pid, linux.SIG.TERM); _ = linux.kill(pid, linux.SIG.TERM);
@ -173,7 +174,11 @@ _ = linux.epoll_ctl(epfd, .ADD, client_fd, &event);
// Wait for events // Wait for events
var events: [64]linux.epoll_event = undefined; 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| { for (events[0..n]) |ev| {
// handle event // handle event
} }
@ -195,19 +200,13 @@ const platform = linux.getauxval(std.elf.AT_PLATFORM);
### File Operations ### File Operations
```zig ```zig
const windows = std.os.windows; // Prefer the portable std.Io layer even in Windows-only programs.
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
// Open file with NT API defer file.close(io);
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);
``` ```
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 ### Process Information
```zig ```zig
@ -225,26 +224,13 @@ const err = windows.GetLastError();
### Pipes ### Pipes
```zig 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.
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);
```
### Submodules ### Submodules
```zig ```zig
windows.kernel32 // kernel32.dll functions windows.kernel32 // kernel32.dll functions
windows.ntdll // ntdll.dll functions (NT native API) windows.ntdll // ntdll.dll functions (NT native API)
windows.advapi32 // advapi32.dll (security, registry)
windows.ws2_32 // Winsock 2 networking windows.ws2_32 // Winsock 2 networking
windows.crypt32 // Cryptographic functions windows.crypt32 // Cryptographic functions
windows.nls // National Language Support windows.nls // National Language Support
@ -386,21 +372,21 @@ const submitted = try ring.submit();
_ = try ring.submit_and_wait(1); _ = try ring.submit_and_wait(1);
// Process completions // Process completions
while (ring.cq_ready() > 0) { var cqes: [32]std.os.linux.io_uring_cqe = undefined;
const cqe = ring.peek_cqe() orelse break; const ready = try ring.copy_cqes(&cqes, 1);
for (cqes[0..ready]) |cqe| {
const user_data = cqe.user_data; const user_data = cqe.user_data;
const result = cqe.res; // bytes transferred or -errno const result = cqe.res; // bytes transferred or -errno
if (result < 0) { 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 // 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 ### Common Operations
```zig ```zig
@ -411,8 +397,8 @@ sqe.prep_readv(fd, iovecs, offset);
sqe.prep_writev(fd, iovecs, offset); sqe.prep_writev(fd, iovecs, offset);
// Fixed buffers (pre-registered, zero-copy) // Fixed buffers (pre-registered, zero-copy)
sqe.prep_read_fixed(fd, buf, offset, buf_index); sqe.prep_read_fixed(fd, registered_iovec, offset, buf_index);
sqe.prep_write_fixed(fd, data, offset, buf_index); sqe.prep_write_fixed(fd, registered_iovec, offset, buf_index);
// Network // Network
sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0); sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0);
@ -463,7 +449,7 @@ defer ring.unregister_buffers() catch {};
// Use registered buffer // Use registered buffer
const sqe = try ring.get_sqe(); 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 ### File Descriptor Registration
@ -482,51 +468,14 @@ sqe.flags |= std.os.linux.IOSQE_FIXED_FILE;
## Common Functions ## 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 - Use `std.Io.Dir` and `std.Io.File` for portable path, access, and metadata operations.
var buf: [std.fs.max_path_bytes]u8 = undefined; - Use `std.posix` for POSIX-like file-descriptor operations.
const path = try std.os.getFdPath(fd, &buf); - Use `std.os.windows`, `std.os.wasi`, or another exported target module when the behavior is intentionally ABI-specific.
std.debug.print("Path: {s}\n", .{path}); - 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.
// 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
```
## Common Patterns ## Common Patterns
@ -562,11 +511,10 @@ fn eventLoop(ring: *std.os.linux.IoUring) !void {
// Submit pending and wait for completions // Submit pending and wait for completions
_ = try ring.submit_and_wait(1); _ = try ring.submit_and_wait(1);
// Process all available completions // Copying also advances the completion queue.
while (ring.cq_ready() > 0) { var cqes: [64]std.os.linux.io_uring_cqe = undefined;
const cqe = ring.peek_cqe() orelse break; const count = try ring.copy_cqes(&cqes, 1);
defer ring.cq_advance(1); for (cqes[0..count]) |cqe| {
const ctx = @as(*Context, @ptrFromInt(cqe.user_data)); const ctx = @as(*Context, @ptrFromInt(cqe.user_data));
try ctx.handle_completion(cqe.res); try ctx.handle_completion(cqe.res);
} }
@ -582,7 +530,7 @@ const linux = std.os.linux;
fn readSyscall(fd: i32, buf: []u8) !usize { fn readSyscall(fd: i32, buf: []u8) !usize {
const result = linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len); const result = linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len);
switch (linux.E.init(result)) { switch (linux.errno(result)) {
.SUCCESS => return result, .SUCCESS => return result,
.INTR => return error.Interrupted, .INTR => return error.Interrupted,
.AGAIN => return error.WouldBlock, .AGAIN => return error.WouldBlock,
@ -601,28 +549,17 @@ fn readSyscall(fd: i32, buf: []u8) !usize {
```zig ```zig
const windows = std.os.windows; const windows = std.os.windows;
fn windowsOperation() !void { fn translateLastError() !void {
const result = windows.kernel32.SomeFunction(...); // Call this immediately after a Win32 API reports failure; another Win32
if (result == windows.FALSE) { // call may overwrite the thread's last-error value.
switch (windows.GetLastError()) { switch (windows.GetLastError()) {
.ERROR_FILE_NOT_FOUND => return error.FileNotFound, .FILE_NOT_FOUND => return error.FileNotFound,
.ERROR_ACCESS_DENIED => return error.AccessDenied, .ACCESS_DENIED => return error.AccessDenied,
else => |e| return windows.unexpectedError(e), else => |e| return windows.unexpectedError(e),
}
} }
} }
``` ```
### Cross-Platform File Descriptor Path ### Retaining a Portable File Path
```zig 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.
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);
}
```

View File

@ -7,12 +7,12 @@ Zig 0.16 changed priority dequeues to align with unmanaged containers:
- Initialize with `.empty`. - Initialize with `.empty`.
- `add` -> `push`. - `add` -> `push`.
- `addSlice` -> `pushSlice`. - `addSlice` -> `pushSlice`.
- `addUnchecked` -> `pushUnchecked`. - The old unchecked insertion helper has no public `pushUnchecked` replacement.
- `removeMin` / `removeMinOrNull` -> `popMin`. - `removeMin` / `removeMinOrNull` -> `popMin`.
- `removeMax` / `removeMaxOrNull` -> `popMax`. - `removeMax` / `removeMaxOrNull` -> `popMax`.
- `removeIndex` -> `popIndex`. - `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. 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); const PDQ = std.PriorityDequeue(u32, void, compare);
var dequeue = PDQ.init(allocator, {}); var dequeue = PDQ.initContext({});
defer dequeue.deinit(); defer dequeue.deinit(allocator);
``` ```
## Basic Operations ## Basic Operations
```zig ```zig
// Add elements // Add elements
try dequeue.add(54); try dequeue.push(allocator, 54);
try dequeue.add(12); try dequeue.push(allocator, 12);
try dequeue.add(7); try dequeue.push(allocator, 7);
// Add multiple // Add multiple
try dequeue.addSlice(&[_]u32{ 1, 2, 3 }); try dequeue.pushSlice(allocator, &[_]u32{ 1, 2, 3 });
// Peek at min/max (doesn't remove) // Peek at min/max (doesn't remove)
if (dequeue.peekMin()) |min| { if (dequeue.peekMin()) |min| {
@ -59,12 +59,8 @@ if (dequeue.peekMax()) |max| {
} }
// Remove min/max // Remove min/max
const min = dequeue.removeMin(); // asserts non-empty const maybe_min = dequeue.popMin(); // ?T; null if empty
const max = dequeue.removeMax(); // asserts non-empty const maybe_max = dequeue.popMax(); // ?T; null if empty
// Safe removal (returns null if empty)
const maybe_min = dequeue.removeMinOrNull();
const maybe_max = dequeue.removeMaxOrNull();
// Size // Size
const n = dequeue.count(); const n = dequeue.count();
@ -76,21 +72,22 @@ const cap = dequeue.capacity();
```zig ```zig
// Take ownership of slice, heapify in place // Take ownership of slice, heapify in place
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 }); var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
var dequeue = PDQ.fromOwnedSlice(allocator, items, {}); var dequeue = PDQ.fromOwnedSlice(items, {});
defer dequeue.deinit(); defer dequeue.deinit(allocator);
``` ```
## Update Priority ## Update Priority
```zig ```zig
try dequeue.update(old_value, new_value); 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 ## Remove by Index
```zig ```zig
const removed = dequeue.removeIndex(index); const removed = dequeue.popIndex(index); // asserts in bounds; heap index is not priority rank
``` ```
## Iteration ## Iteration
@ -107,9 +104,9 @@ it.reset();
## Capacity Management ## Capacity Management
```zig ```zig
try dequeue.ensureTotalCapacity(100); try dequeue.ensureTotalCapacity(allocator, 100);
try dequeue.ensureUnusedCapacity(10); try dequeue.ensureUnusedCapacity(allocator, 10);
dequeue.shrinkAndFree(new_capacity); dequeue.shrinkAndFree(allocator, new_capacity);
``` ```
## Context-Based Comparator ## 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 IndexPDQ = std.PriorityDequeue(usize, []const u32, compareByScore);
const scores = [_]u32{ 50, 30, 80, 20 }; 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 ## Complete Example: Bounded Range Tracker
@ -140,15 +138,16 @@ pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init; var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit(); defer _ = gpa.deinit();
var tracker = RangePDQ.init(gpa.allocator(), {}); const allocator = gpa.allocator();
defer tracker.deinit(); var tracker = RangePDQ.initContext({});
defer tracker.deinit(allocator);
// Add values // Add values
try tracker.add(10); try tracker.push(allocator, 10);
try tracker.add(5); try tracker.push(allocator, 5);
try tracker.add(20); try tracker.push(allocator, 20);
try tracker.add(3); try tracker.push(allocator, 3);
try tracker.add(15); try tracker.push(allocator, 15);
// Get range without removing // Get range without removing
const min = tracker.peekMin().?; // 3 const min = tracker.peekMin().?; // 3
@ -158,8 +157,8 @@ pub fn main() !void {
std.debug.print("Range: {} to {} = {}\n", .{ min, max, range }); std.debug.print("Range: {} to {} = {}\n", .{ min, max, range });
// Pop from both ends // Pop from both ends
_ = tracker.removeMin(); // removes 3 _ = tracker.popMin(); // removes 3
_ = tracker.removeMax(); // removes 20 _ = tracker.popMax(); // removes 20
// New range is 5 to 15 // New range is 5 to 15
} }
@ -177,7 +176,7 @@ pub fn main() !void {
## Notes ## Notes
- Both `removeMin()` and `removeMax()` are O(log n) - Both `popMin()` and `popMax()` are O(log n)
- `peekMin()` is O(1), `peekMax()` is O(1) after first 2 elements - Both nullable peeks are O(1), including empty and one-element deques
- Iterator order is heap array order, not priority order - Iterator order is heap array order, not priority order
- Use when you need efficient access to both extremes - Use when you need efficient access to both extremes

View File

@ -8,12 +8,12 @@ Zig 0.16 changed priority queues to align with unmanaged containers:
- Empty queues can use `.empty`. - Empty queues can use `.empty`.
- `init` -> `initContext` when context is needed. - `init` -> `initContext` when context is needed.
- `add` -> `push`. - `add` -> `push`.
- `addUnchecked` -> `pushUnchecked`. - The old unchecked insertion helper has no public `pushUnchecked` replacement; reserve and use supported public operations.
- `addSlice` -> `pushSlice`. - `addSlice` -> `pushSlice`.
- `remove` / `removeOrNull` -> `pop`. - `remove` / `removeOrNull` -> `pop`.
- `removeIndex` -> `popIndex`. - `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. 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); const PQ = std.PriorityQueue(u32, void, lessThan);
var queue = PQ.init(allocator, {}); var queue = PQ.initContext({});
defer queue.deinit(); defer queue.deinit(allocator);
``` ```
## Max-Heap ## Max-Heap
@ -56,12 +56,12 @@ const MaxPQ = std.PriorityQueue(u32, void, greaterThan);
```zig ```zig
// Add elements // Add elements
try queue.add(54); try queue.push(allocator, 54);
try queue.add(12); try queue.push(allocator, 12);
try queue.add(7); try queue.push(allocator, 7);
// Add multiple // Add multiple
try queue.addSlice(&[_]u32{ 1, 2, 3 }); try queue.pushSlice(allocator, &[_]u32{ 1, 2, 3 });
// Peek at highest priority (doesn't remove) // Peek at highest priority (doesn't remove)
if (queue.peek()) |top| { if (queue.peek()) |top| {
@ -69,8 +69,7 @@ if (queue.peek()) |top| {
} }
// Remove highest priority // Remove highest priority
const top = queue.remove(); // asserts non-empty const maybe_top = queue.pop(); // ?T; null when empty
const maybe = queue.removeOrNull(); // returns ?T
// Size // Size
const n = queue.count(); const n = queue.count();
@ -82,8 +81,8 @@ const cap = queue.capacity();
```zig ```zig
// Take ownership of slice, heapify in place // Take ownership of slice, heapify in place
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 }); var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
var queue = PQ.fromOwnedSlice(allocator, items, {}); var queue = PQ.fromOwnedSlice(items, {});
defer queue.deinit(); defer queue.deinit(allocator);
// Now queue is a valid heap // Now queue is a valid heap
``` ```
@ -92,14 +91,15 @@ defer queue.deinit();
```zig ```zig
// Change priority of existing element // Change priority of existing element
try queue.update(old_value, new_value); 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 ## Remove by Index
```zig ```zig
// Remove element at specific position (not priority order) // 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) ## Iteration (Non-Priority Order)
@ -113,14 +113,16 @@ while (it.next()) |elem| {
it.reset(); // restart iteration it.reset(); // restart iteration
``` ```
Any queue mutation invalidates the iterator.
## Capacity Management ## Capacity Management
```zig ```zig
try queue.ensureTotalCapacity(100); try queue.ensureTotalCapacity(allocator, 100);
try queue.ensureUnusedCapacity(10); try queue.ensureUnusedCapacity(allocator, 10);
queue.shrinkAndFree(new_capacity); queue.shrinkAndFree(allocator, new_capacity);
queue.clearRetainingCapacity(); queue.clearRetainingCapacity();
queue.clearAndFree(); queue.clearAndFree(allocator);
``` ```
## Context-Based Comparator ## 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 IndexPQ = std.PriorityQueue(usize, []const u32, compareByScore);
const scores = [_]u32{ 50, 30, 80, 20 }; const scores = [_]u32{ 50, 30, 80, 20 };
var queue = IndexPQ.init(allocator, &scores); var queue = IndexPQ.initContext(scores[0..]);
defer queue.deinit(); defer queue.deinit(allocator);
try queue.add(0); // score 50 try queue.push(allocator, 0); // score 50
try queue.add(1); // score 30 try queue.push(allocator, 1); // score 30
try queue.add(2); // score 80 try queue.push(allocator, 2); // score 80
try queue.add(3); // score 20 try queue.push(allocator, 3); // score 20
// Removes index 3 (score 20 is smallest) // Removes index 3 (score 20 is smallest)
const best = queue.remove(); // 3 const best = queue.pop().?; // 3
``` ```
## Complete Example: Task Scheduler ## Complete Example: Task Scheduler
@ -167,14 +169,15 @@ pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init; var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit(); defer _ = gpa.deinit();
var tasks = TaskQueue.init(gpa.allocator(), {}); const allocator = gpa.allocator();
defer tasks.deinit(); var tasks = TaskQueue.initContext({});
defer tasks.deinit(allocator);
try tasks.add(.{ .name = "low priority", .priority = 100 }); try tasks.push(allocator, .{ .name = "low priority", .priority = 100 });
try tasks.add(.{ .name = "urgent", .priority = 1 }); try tasks.push(allocator, .{ .name = "urgent", .priority = 1 });
try tasks.add(.{ .name = "medium", .priority = 50 }); try tasks.push(allocator, .{ .name = "medium", .priority = 50 });
while (tasks.removeOrNull()) |task| { while (tasks.pop()) |task| {
std.debug.print("Processing: {s}\n", .{task.name}); std.debug.print("Processing: {s}\n", .{task.name});
} }
// Output: // Output:
@ -187,6 +190,6 @@ pub fn main() !void {
## Notes ## Notes
- Heap property: parent has higher priority than children - 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) - 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

View File

@ -78,6 +78,10 @@ const cwd = try std.process.currentPathAlloc(io, gpa);
defer gpa.free(cwd); 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. Do not add new `std.process.getCwd*` callsites.
## Run and Capture Output ## Run and Capture Output
@ -113,6 +117,10 @@ Important options:
- `disable_aslr` - `disable_aslr`
- `timeout` - `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 ## Spawn Child Process
Use `std.process.spawn(io, options)`. `std.process.Child.init` is not the 0.16 pattern. 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.unlockMemory`
- `std.process.lockMemoryAll` - `std.process.lockMemoryAll`
- `std.process.unlockMemoryAll` - `std.process.unlockMemoryAll`
- `std.process.MemoryProtection`
- `std.process.protectMemory`
Use them only for explicit platform/security needs. Use them only for explicit platform/security needs.

View File

@ -6,7 +6,7 @@ Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/releas
## Zig 0.16 Entropy Rule ## 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 ```zig
var seed: [32]u8 = undefined; 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. 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 ## Quick Reference
@ -37,10 +37,8 @@ Old examples below may mention `std.crypto.random`; use the `std.Io` patterns ab
``` ```
Need crypto security? Need crypto security?
├─ Yes → ChaCha (DefaultCsprng) or Ascon |- Yes -> use `io.random`/`io.randomSecure`, or seed ChaCha/Ascon securely
└─ No → Need speed? `- No -> use Xoshiro256 (DefaultPrng), Sfc64, RomuTrio, or Pcg
├─ Yes → Xoshiro256 (DefaultPrng), Sfc64, or RomuTrio
└─ No → Pcg (smaller state), Xoroshiro128
``` ```
| PRNG | State | Output | Use Case | | PRNG | State | Output | Use Case |
@ -50,8 +48,8 @@ Need crypto security?
| `Pcg` | 128-bit | 32-bit | Compact, statistically excellent | | `Pcg` | 128-bit | 32-bit | Compact, statistically excellent |
| `Sfc64` | 256-bit | 64-bit | Very fast | | `Sfc64` | 256-bit | 64-bit | Very fast |
| `RomuTrio` | 192-bit | 64-bit | Fast, small code size | | `RomuTrio` | 192-bit | 64-bit | Fast, small code size |
| `Isaac64` | 8KB | 64-bit | Cryptographic-ish (prefer ChaCha) | | `Isaac64` | 8KB | 64-bit | Non-default PRNG; do not select it as the documented CSPRNG path |
| `ChaCha` | 512-bit | stream | CSPRNG, forward secure | | `ChaCha` | Internal state | stream | CSPRNG, forward secure |
| `Ascon` | 320-bit | stream | CSPRNG, lightweight | | `Ascon` | 320-bit | stream | CSPRNG, lightweight |
## Basic Usage ## Basic Usage
@ -79,14 +77,13 @@ pub fn main() void {
```zig ```zig
const std = @import("std"); const std = @import("std");
pub fn main() void { pub fn main(init: std.process.Init) void {
// Use std.crypto.random for system entropy const io = init.io;
const secure = std.crypto.random;
var key: [32]u8 = undefined; 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 ```zig
var seed: u64 = undefined; 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); var prng = std.Random.DefaultPrng.init(seed);
``` ```
@ -115,7 +112,7 @@ prng.jump();
```zig ```zig
// Requires 32-byte secret seed // Requires 32-byte secret seed
var secret_seed: [std.Random.ChaCha.secret_seed_length]u8 = undefined; 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); var csprng = std.Random.ChaCha.init(secret_seed);
const random = csprng.random(); const random = csprng.random();
@ -257,10 +254,13 @@ const int_weights = [_]u32{ 5, 3, 2 };
const int_choice = random.weightedIndex(u32, &int_weights); 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 ### Random Element from Slice
```zig ```zig
fn randomElement(comptime T: type, random: std.Random, slice: []const T) T { 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); const index = random.uintLessThan(usize, slice.len);
return slice[index]; return slice[index];
} }
@ -272,10 +272,11 @@ const color = randomElement([]const u8, random, &colors);
### Random Sample (Without Replacement) ### Random Sample (Without Replacement)
```zig ```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 // Fisher-Yates partial shuffle
var indices: [source.len]usize = undefined; for (indices[0..source.len], 0..) |*idx, i| idx.* = i;
for (&indices, 0..) |*idx, i| idx.* = i;
for (dest, 0..) |*d, i| { for (dest, 0..) |*d, i| {
const j = random.intRangeLessThan(usize, i, source.len); const j = random.intRangeLessThan(usize, i, source.len);
@ -304,10 +305,10 @@ std.debug.assert(prng1.random().int(u64) == prng2.random().int(u64));
```zig ```zig
threadlocal var tls_prng: ?std.Random.DefaultPrng = null; threadlocal var tls_prng: ?std.Random.DefaultPrng = null;
fn getThreadRandom() std.Random { fn getThreadRandom(io: std.Io) std.Random {
if (tls_prng == null) { if (tls_prng == null) {
var seed: u64 = undefined; 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); tls_prng = std.Random.DefaultPrng.init(seed);
} }
return tls_prng.?.random(); return tls_prng.?.random();
@ -318,6 +319,7 @@ fn getThreadRandom() std.Random {
```zig ```zig
fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator) ![]std.Random.Xoshiro256 { 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); const prngs = try allocator.alloc(std.Random.Xoshiro256, n);
prngs[0] = std.Random.Xoshiro256.init(base_seed); 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 ### Monte Carlo Simulation
```zig ```zig
@ -357,7 +361,8 @@ fn generatePassword(random: std.Random, buf: []u8) void {
// Usage // Usage
var password: [16]u8 = undefined; 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 ### Gaussian Random with Box-Muller
@ -406,7 +411,7 @@ const MyPrng = struct {
- `DefaultPrng` is `Xoshiro256` - fast, high quality, not cryptographic - `DefaultPrng` is `Xoshiro256` - fast, high quality, not cryptographic
- `DefaultCsprng` is `ChaCha` - cryptographically secure with forward secrecy - `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) - `uintLessThan`/`intRangeLessThan` may reject values (not constant-time)
- Use biased variants (`*Biased`) for timing-sensitive applications - Use biased variants (`*Biased`) for timing-sensitive applications
- `jump()` on Xoshiro256 advances 2^128 steps for parallel streams - `jump()` on Xoshiro256 advances 2^128 steps for parallel streams

View File

@ -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 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 ## When to Use
@ -15,11 +15,11 @@ A dynamic list where element pointers remain stable across growth. Unlike ArrayL
## Trade-offs ## 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) - O(log n) random access (vs O(1) for ArrayList)
- Higher per-element overhead - Higher per-element overhead
## Initialization ## Legacy Initialization
```zig ```zig
// Without preallocation // Without preallocation
@ -32,7 +32,7 @@ var list = std.SegmentedList(i32, 16){};
defer list.deinit(allocator); defer list.deinit(allocator);
``` ```
## Basic Operations ## Legacy Basic Operations
```zig ```zig
// Append (pointer remains valid forever) // Append (pointer remains valid forever)
@ -84,7 +84,7 @@ if (it.peek()) |ptr| {
it.set(50); it.set(50);
``` ```
## Capacity Management ## Legacy Capacity Management
```zig ```zig
// Grow capacity // 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 ```zig
const Object = struct { const Object = struct {

View File

@ -171,7 +171,7 @@ const any_true = @reduce(.Or, mask); // true
### suggestVectorLength ### 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 ```zig
const std = @import("std"); const std = @import("std");
@ -187,7 +187,7 @@ Returns `null` if scalars are recommended (no SIMD benefit).
### suggestVectorLengthForCpu ### 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 ```zig
const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2; 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:** **Architecture support:**
- **x86**: SSE (128-bit), AVX2 (256-bit), AVX-512 (512-bit) - **x86**: SSE (128-bit), AVX2 (256-bit), AVX-512 (512-bit)
- **ARM**: NEON (128-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) - **RISC-V**: V extension (32-bit to 65536-bit via zvl* features)
- **WebAssembly**: simd128 (128-bit) - **WebAssembly**: simd128 (128-bit)
- **PowerPC**: AltiVec (128-bit) - **PowerPC**: AltiVec (128-bit)
@ -289,7 +289,7 @@ const result = std.simd.deinterlace(2, interleaved);
### extract - Get Subvector ### extract - Get Subvector
Extract a contiguous slice of elements: Extract a contiguous slice of elements. `first` and `count` are comptime-known:
```zig ```zig
const vec: @Vector(8, u32) = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; 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 ### shiftElementsLeft / shiftElementsRight
Shift elements, filling with a value: Shift elements, filling with a value. The shift amount is comptime-known:
```zig ```zig
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 }; const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
@ -315,7 +315,7 @@ const right = std.simd.shiftElementsRight(vec, 2, 999);
### rotateElementsLeft / rotateElementsRight ### rotateElementsLeft / rotateElementsRight
Circular rotation (elements wrap around): Circular rotation (elements wrap around). The rotation amount is comptime-known:
```zig ```zig
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 }; const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
@ -339,7 +339,7 @@ const reversed = std.simd.reverseOrder(vec);
### mergeShift ### mergeShift
Combine two vectors and extract a shifted window: Combine two vectors and extract a shifted window. The shift amount is comptime-known:
```zig ```zig
const a: @Vector(4, u32) = .{ 1, 2, 3, 4 }; const a: @Vector(4, u32) = .{ 1, 2, 3, 4 };
@ -400,7 +400,7 @@ const count = std.simd.countElementsWithValue(vec, 4); // 3
### prefixScan ### 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 ```zig
const vec: @Vector(4, i32) = .{ 11, 23, 9, -21 }; const vec: @Vector(4, i32) = .{ 11, 23, 9, -21 };
@ -443,7 +443,7 @@ const rev = std.simd.prefixScan(.Add, -1, vec);
### prefixScanWithFunc ### 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 ```zig
fn myMax(a: @Vector(4, f32), b: @Vector(4, f32)) @Vector(4, f32) { 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 ## Performance Notes
- **Optimal vector size:** Use `suggestVectorLength` for portable code; don't hardcode lane counts. Powers of two (2-64) are most efficient - **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 - **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 - **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 - **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 - **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 - **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 - **MIPS limitation:** `interlace` and `prefixScan` don't work on MIPS architecture

View File

@ -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. Sorting algorithms and binary search utilities. All sorts are in-place and require no allocator.
## Quick Reference ## 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 | | `block` | Yes | O(n log n) | Default choice when stability matters |
| `pdq` | No | O(n log n) | Default choice when stability doesn't matter | | `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 | | `heap` | No | O(n log n) | Guaranteed worst-case, no recursion |
## Comparator Functions ## Comparator Functions
@ -33,7 +33,7 @@ const desc_i32 = std.sort.desc(i32); // descending
```zig ```zig
var items = [_]i32{ 5, 2, 8, 1, 9 }; 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)); std.sort.pdq(i32, &items, {}, std.sort.asc(i32));
// items = [1, 2, 5, 8, 9] // items = [1, 2, 5, 8, 9]
@ -188,7 +188,7 @@ var items = [_]i32{ 5, 2, 8, 1 };
const ctx = Context{ .items = &items }; const ctx = Context{ .items = &items };
// Sort a subrange using indices // 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] // items = [5, 1, 2, 8]
``` ```
@ -220,15 +220,19 @@ std.sort.block(Item, &items, {}, byPriority);
## Algorithm Selection ## Algorithm Selection
- **`pdq`** (Pattern-Defeating Quicksort): Best general-purpose unstable sort. Adapts to input patterns, falls back to heapsort for worst cases. - **`pdq`** (Pattern-Defeating Quicksort): 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. - **`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. - **`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. - **`heap`**: Guaranteed O(n log n) with O(1) memory. No recursion, predictable performance.
## Notes ## Notes
- All sorts are **in-place** with O(1) or O(log n) auxiliary memory - 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 - `asc`/`desc` helpers work with any type supporting `<` operator
- Binary search functions require the array to already be sorted - 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.

View File

@ -1,6 +1,6 @@
# std.StaticStringMap # 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 ## When to Use
@ -8,7 +8,7 @@ Compile-time optimized string lookup. Perfect hash for small, fixed sets of stri
- Command/option parsing - Command/option parsing
- Static configuration keys - Static configuration keys
- When string set is known at compile time - 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 ## Basic Usage
@ -52,7 +52,7 @@ if (reserved.has("break")) {
} }
``` ```
## Case-Insensitive Lookup ## ASCII Case-Insensitive Lookup
```zig ```zig
const commands = std.StaticStringMapWithEql( const commands = std.StaticStringMapWithEql(
@ -86,6 +86,8 @@ defer map.deinit(allocator);
_ = map.get("two"); // 2 _ = 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 ## Get Index
```zig ```zig

View File

@ -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 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 ## Table of Contents
- [Module Structure](#module-structure) - [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.Writer // Create tar archives
std.tar.Diagnostics // Collect errors during extraction std.tar.Diagnostics // Collect errors during extraction
std.tar.FileKind // .file, .directory, .sym_link std.tar.FileKind // .file, .directory, .sym_link
std.tar.PipeOptions // Options for pipeToFileSystem std.tar.ExtractOptions // Options for extract
std.tar.pipeToFileSystem() // Extract archive to directory std.tar.extract() // Extract archive to directory
``` ```
## Reading Tar Archives ## Reading Tar Archives
@ -36,8 +36,8 @@ const data = @embedFile("archive.tar");
var reader: std.Io.Reader = .fixed(data); var reader: std.Io.Reader = .fixed(data);
// Buffers must be provided by caller // Buffers must be provided by caller
var file_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.fs.max_path_bytes]u8 = undefined; var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var it: std.tar.Iterator = .init(&reader, .{ var it: std.tar.Iterator = .init(&reader, .{
.file_name_buffer = &file_name_buffer, .file_name_buffer = &file_name_buffer,
@ -70,16 +70,16 @@ pub const File = struct {
### Reading File Contents ### 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 ```zig
while (try it.next()) |file| { while (try it.next()) |file| {
if (file.kind == .file) { if (file.kind == .file) {
// Option 1: Stream to writer // Option 1: Stream to writer
var buf: [1024]u8 = undefined; var buf: [1024]u8 = undefined;
var output_file = try dir.createFile(file.name, .{}); var output_file = try dir.createFile(io, file.name, .{});
defer output_file.close(); defer output_file.close(io);
var file_writer = output_file.writer(&buf); var file_writer = output_file.writer(io, &buf);
try it.streamRemaining(file, &file_writer.interface); try it.streamRemaining(file, &file_writer.interface);
try file_writer.interface.flush(); try file_writer.interface.flush();
@ -104,7 +104,7 @@ pub const Options = struct {
## Extracting to Filesystem ## Extracting to Filesystem
### pipeToFileSystem ### extract
Extract entire archive to a directory: Extract entire archive to a directory:
@ -112,7 +112,7 @@ Extract entire archive to a directory:
const data = @embedFile("archive.tar"); const data = @embedFile("archive.tar");
var reader: std.Io.Reader = .fixed(data); 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 .strip_components = 1, // remove leading path component
.mode_mode = .executable_bit_only, .mode_mode = .executable_bit_only,
.exclude_empty_directories = false, .exclude_empty_directories = false,
@ -122,19 +122,19 @@ try std.tar.pipeToFileSystem(std.fs.cwd(), &reader, .{
### From File ### From File
```zig ```zig
const file = try std.fs.cwd().openFile("archive.tar", .{}); const file = try std.Io.Dir.cwd().openFile(io, "archive.tar", .{});
defer file.close(); defer file.close(io);
var buf: [4096]u8 = undefined; 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 ```zig
pub const PipeOptions = struct { pub const ExtractOptions = struct {
strip_components: u32 = 0, // directories to strip from paths strip_components: u32 = 0, // directories to strip from paths
mode_mode: ModeMode = .executable_bit_only, mode_mode: ModeMode = .executable_bit_only,
exclude_empty_directories: bool = false, exclude_empty_directories: bool = false,
@ -180,24 +180,26 @@ try w.writeLink("latest", "v1.0", .{});
const tar_bytes = output.written(); 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 ### Writing from File
```zig ```zig
var output_file = try std.fs.cwd().createFile("archive.tar", .{}); var output_file = try std.Io.Dir.cwd().createFile(io, "archive.tar", .{});
defer output_file.close(); defer output_file.close(io);
var buf: [4096]u8 = undefined; 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 }; var w: std.tar.Writer = .{ .underlying_writer = &file_writer.interface };
// Write file from disk // Write file from disk
var src_file = try std.fs.cwd().openFile("data.txt", .{}); var src_file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
defer src_file.close(); defer src_file.close(io);
var src_buf: [4096]u8 = undefined; var src_buf: [4096]u8 = undefined;
var src_reader = src_file.reader(&src_buf); var src_reader = src_file.reader(io, &src_buf);
const stat = try src_file.stat(); 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(); 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 pub fn writeFileStream(w: *Writer, sub_path: []const u8, size: u64, reader: *std.Io.Reader, options: Options) WriteFileStreamError!void
// Write file from file reader // 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 // Write symbolic link
pub fn writeLink(w: *Writer, sub_path: []const u8, link_name: []const u8, options: Options) Error!void 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 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 ```zig
pub const Options = struct { pub const Options = struct {
mode: u32 = 0, // POSIX mode (0 = default: 0o664 for files) 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 }; var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator };
defer diagnostics.deinit(); defer diagnostics.deinit();
std.tar.pipeToFileSystem(dir, &reader, .{ std.tar.extract(io, dir, &reader, .{
.diagnostics = &diagnostics, .diagnostics = &diagnostics,
}) catch |err| { }) catch |err| {
// Some errors are still fatal // 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 }); std.debug.print("Root dir: {s}, entries: {d}\n", .{ diagnostics.root_dir, diagnostics.entries });
``` ```
### Diagnostics.Error Types ### Diagnostics.Error Variants
```zig ```text
pub const Error = union(enum) { // 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 { unable_to_create_sym_link: struct {
code: anyerror, code: anyerror,
file_name: []const u8, file_name: []const u8,
@ -298,7 +306,7 @@ pub const Error = union(enum) {
}, },
unsupported_file_type: struct { unsupported_file_type: struct {
file_name: []const u8, file_name: []const u8,
file_type: Header.Kind, file_type: /* private archive header kind */,
}, },
components_outside_stripped_prefix: struct { components_outside_stripped_prefix: struct {
file_name: []const u8, file_name: []const u8,
@ -311,13 +319,13 @@ pub const Error = union(enum) {
### Extract and Process Archive ### Extract and Process Archive
```zig ```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 reader: std.Io.Reader = .fixed(tar_data);
var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator }; var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator };
defer diagnostics.deinit(); defer diagnostics.deinit();
try std.tar.pipeToFileSystem(dest, &reader, .{ try std.tar.extract(io, dest, &reader, .{
.strip_components = 1, .strip_components = 1,
.diagnostics = &diagnostics, .diagnostics = &diagnostics,
}); });
@ -337,8 +345,8 @@ fn listTar(allocator: Allocator, tar_data: []const u8) !void {
_ = allocator; _ = allocator;
var reader: std.Io.Reader = .fixed(tar_data); var reader: std.Io.Reader = .fixed(tar_data);
var file_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.fs.max_path_bytes]u8 = undefined; var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var it: std.tar.Iterator = .init(&reader, .{ var it: std.tar.Iterator = .init(&reader, .{
.file_name_buffer = &file_name_buffer, .file_name_buffer = &file_name_buffer,
@ -365,7 +373,7 @@ fn listTar(allocator: Allocator, tar_data: []const u8) !void {
### Create Archive from Directory ### Create Archive from Directory
```zig ```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); var output: std.Io.Writer.Allocating = .init(allocator);
errdefer output.deinit(); 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); var walker = try source_dir.walk(allocator);
defer walker.deinit(); defer walker.deinit();
while (try walker.next()) |entry| { while (try walker.next(io)) |entry| {
switch (entry.kind) { switch (entry.kind) {
.directory => try w.writeDir(entry.path, .{}), .directory => try w.writeDir(entry.path, .{}),
.file => { .file => {
var file = try entry.dir.openFile(entry.basename, .{}); var file = try entry.dir.openFile(io, entry.basename, .{});
defer file.close(); defer file.close(io);
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf); var file_reader = file.reader(io, &buf);
const stat = try file.stat(); const stat = try file.stat(io);
try w.writeFile(entry.path, &file_reader, stat.mtime); try w.writeFileTimestamp(entry.path, &file_reader, stat.mtime);
}, },
.sym_link => { .sym_link => {
var link_buf: [std.fs.max_path_bytes]u8 = undefined; var link_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
const target = try entry.dir.readLink(entry.basename, &link_buf); const target_len = try entry.dir.readLink(io, entry.basename, &link_buf);
try w.writeLink(entry.path, target, .{}); try w.writeLink(entry.path, link_buf[0..target_len], .{});
}, },
else => {}, // skip special files 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 { fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocator) !?[]u8 {
var reader: std.Io.Reader = .fixed(tar_data); var reader: std.Io.Reader = .fixed(tar_data);
var file_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.fs.max_path_bytes]u8 = undefined; var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var it: std.tar.Iterator = .init(&reader, .{ var it: std.tar.Iterator = .init(&reader, .{
.file_name_buffer = &file_name_buffer, .file_name_buffer = &file_name_buffer,
@ -427,10 +435,10 @@ fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocat
## Supported Features ## 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 **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 **Path handling**: Automatic prefix/name splitting, GNU extended headers for paths > 256 bytes

View File

@ -53,7 +53,7 @@ try testing.expectEqualSlices(u32, &[_]u32{1, 2, 3}, result_slice);
// Sentinel-terminated slice equality // Sentinel-terminated slice equality
try testing.expectEqualSentinel(u8, 0, expected_cstr, actual_cstr); 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); try testing.expectEqualDeep(expected_struct, actual_struct);
// Float comparison (absolute tolerance) // Float comparison (absolute tolerance)
@ -68,7 +68,7 @@ try testing.expectApproxEqRel(@as(f64, 100.0), result, 0.01);
```zig ```zig
const Point = struct { x: i32, y: i32 }; 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 p1 = Point{ .x = 1, .y = 2 };
const p2 = Point{ .x = 1, .y = 2 }; const p2 = Point{ .x = 1, .y = 2 };
try testing.expectEqual(p1, p2); // OK - structs compared field-by-field 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 }; const b = [_]u8{ 1, 2, 3 };
// testing.expectEqual(&a, &b); // FAILS - different pointers // 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(&a, &b); // OK - compares contents
try testing.expectEqualDeep("abc", "abc"); // OK 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 ## Error Assertions
```zig ```zig
@ -164,7 +166,7 @@ std.debug.print("Deallocations: {}\n", .{failing.deallocations});
## Exhaustive Allocation Failure Testing ## 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 ```zig
fn myFunction(allocator: std.mem.Allocator, size: usize) !void { 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... 2. Runs N more times, failing allocation 0, then 1, then 2...
3. Verifies `OutOfMemory` is returned and no memory leaked 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.MemoryLeakDetected` - allocation failed but memory wasn't freed
- `error.SwallowedOutOfMemoryError` - `OutOfMemory` was caught but not propagated - `error.SwallowedOutOfMemoryError` - `OutOfMemory` was caught but not propagated
- `error.NondeterministicMemoryUsage` - allocation count varies between runs - `error.NondeterministicMemoryUsage` - allocation count varies between runs
Other errors returned by the tested function are propagated unchanged.
## Temporary Directory ## Temporary Directory
Create an isolated temp directory for file system tests: Create an isolated temp directory for file system tests:
@ -205,12 +209,18 @@ test "file operations" {
defer tmp.cleanup(); defer tmp.cleanup();
// Write and read files // Write and read files
var file = try tmp.dir.createFile("test.txt", .{}); const io = std.testing.io;
defer file.close(); {
try file.writeAll("hello"); 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 // 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); defer std.testing.allocator.free(content);
try testing.expectEqualStrings("hello", content); try testing.expectEqualStrings("hello", content);
} }
@ -237,10 +247,9 @@ comptime {
std.testing.refAllDecls(@This()); std.testing.refAllDecls(@This());
} }
// Recursive version for nested types // refAllDecls visits the immediate declarations of the supplied type.
comptime { // Zig 0.16 has no std.testing.refAllDeclsRecursive helper; recurse through
std.testing.refAllDeclsRecursive(@This()); // selected nested types explicitly when that is part of the test's intent.
}
``` ```
## Skip Tests ## Skip Tests
@ -265,7 +274,7 @@ test "skip if feature unavailable" {
```zig ```zig
test "with logging" { 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}); std.debug.print("Debug info: {}\n", .{value});
} }
@ -294,8 +303,9 @@ test "fuzz parser" {
try std.testing.fuzz( try std.testing.fuzz(
{}, // context (passed to test function) {}, // context (passed to test function)
struct { struct {
fn testOne(_: void, input: []const u8) !void { fn testOne(_: void, smith: *std.testing.Smith) !void {
// This runs with many different inputs var storage: [4096]u8 = undefined;
const input = storage[0..smith.slice(&storage)];
_ = myParser.parse(input) catch |err| switch (err) { _ = myParser.parse(input) catch |err| switch (err) {
error.InvalidInput => return, // expected error.InvalidInput => return, // expected
else => return err, else => return err,
@ -376,6 +386,5 @@ test "arena for test allocations" {
zig build test # Run all tests zig build test # Run all tests
zig test src/lib.zig # Test single file zig test src/lib.zig # Test single file
zig test --test-filter "name" # Filter by name substring zig test --test-filter "name" # Filter by name substring
zig test -fsummary # Show test summary zig test --help # List options supported by this Zig version
zig test --verbose # Show debug output
``` ```

View File

@ -26,7 +26,6 @@ Useful thread utilities:
```zig ```zig
const id = std.Thread.getCurrentId(); const id = std.Thread.getCurrentId();
const cpu_count = std.Thread.getCpuCount() catch 1; const cpu_count = std.Thread.getCpuCount() catch 1;
std.Thread.sleep(10 * std.time.ns_per_ms);
std.Thread.yield() catch {}; std.Thread.yield() catch {};
``` ```
@ -49,7 +48,7 @@ Release-note migration map:
| `std.Thread.RwLock` | `std.Io.RwLock` | | `std.Thread.RwLock` | `std.Io.RwLock` |
| `std.Thread.ResetEvent` | `std.Io.Event` | | `std.Thread.ResetEvent` | `std.Io.Event` |
| `std.Thread.WaitGroup` | `std.Io.Group` | | `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`. 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 ```zig
mutex.lockUncancelable(io); mutex.lockUncancelable(io);
@ -186,7 +187,8 @@ Rules:
## Application Guidance ## Application Guidance
- Queues, allocators, registries, timers, and worker systems that use blocking sync should accept/store `std.Io`. - 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. - 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. - Prefer an application's existing scheduler for application work unless the standard `std.Io` task API is explicitly the better fit.

View File

@ -6,11 +6,9 @@ Zig 0.16 moves time APIs that depend on the runtime behind `std.Io`. Use `std.Io
## Migration Summary ## Migration Summary
Release-note map: Choose an explicit clock when migrating old time APIs. Use
`std.Io.Timestamp.now(io, .real)` for wall time and `.boot` or `.awake` for
- `std.time.Instant` -> `std.Io.Timestamp` elapsed-time measurements; `Timestamp` is not a one-for-one timer replacement.
- `std.time.Timer` -> `std.Io.Timestamp`
- `std.time.timestamp` -> `std.Io.Timestamp.now`
- `{D}` duration formatting -> format `std.Io.Duration` with `{f}` - `{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`. `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 ## 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 ```zig
try std.Io.Clock.Duration{ try std.Io.Clock.Duration{
@ -88,8 +87,6 @@ try std.Io.Clock.Duration{
}.sleep(io); }.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 ## Resolution
Clock resolution may fail or return zero for unsupported clocks. 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 }; 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 ## Application Guidance
- Put common wall-clock timestamp reads behind a shared application helper when consistent clock selection matters. - 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 the clock choice documented by usage (`.real` for wall time, monotonic clocks for elapsed time)?
- Is duration formatting using `{f}` with `std.Io.Duration`? - Is duration formatting using `{f}` with `std.Io.Duration`?
- Does the API receive or store `io` rather than constructing a fallback locally? - 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`?

View File

@ -24,6 +24,9 @@ var treap: MyTreap = .{};
Nodes are user-managed (intrusive design): 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 ```zig
var nodes: [100]MyTreap.Node = undefined; var nodes: [100]MyTreap.Node = undefined;
@ -55,7 +58,8 @@ if (entry.node) |node| {
// found, node.key == key // 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); var entry = treap.getEntryForExisting(node);
``` ```
@ -74,7 +78,7 @@ entry.set(null);
```zig ```zig
var entry = treap.getEntryForExisting(old_node); 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 ## Min/Max Access
@ -168,4 +172,5 @@ pub fn main() !void {
- No allocator needed (nodes are user-managed) - No allocator needed (nodes are user-managed)
- Balancing uses randomized priorities (xorshift PRNG) - Balancing uses randomized priorities (xorshift PRNG)
- `node.priority == 0` indicates node is not in treap - `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.

View File

@ -1,6 +1,6 @@
# std.Tz - TZif Timezone Database Parsing (Zig 0.16.0) # 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 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 ```zig
const std = @import("std"); const std = @import("std");
pub fn main() !void { fn load(io: std.Io, allocator: std.mem.Allocator) !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Open system timezone file // Open system timezone file
const file = try std.fs.openFileAbsolute("/usr/share/zoneinfo/America/New_York", .{}); const file = try std.Io.Dir.openFileAbsolute(io, "/usr/share/zoneinfo/America/New_York", .{});
defer file.close(); defer file.close(io);
var read_buf: [4096]u8 = undefined;
var file_reader = file.reader(io, &read_buf);
// Parse TZif data // 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(); defer tz.deinit();
// Access timezone information // Access timezone information
@ -47,9 +45,9 @@ const std = @import("std");
// Embed TZif file at compile time // Embed TZif file at compile time
const tokyo_tz = @embedFile("tz/asia_tokyo.tzif"); const tokyo_tz = @embedFile("tz/asia_tokyo.tzif");
pub fn main() !void { pub fn parseEmbedded(allocator: std.mem.Allocator) !void {
var stream = std.io.fixedBufferStream(tokyo_tz); var reader: std.Io.Reader = .fixed(tokyo_tz);
var tz = try std.Tz.parse(std.heap.page_allocator, stream.reader()); var tz = try std.Tz.parse(allocator, &reader);
defer tz.deinit(); defer tz.deinit();
// Use timezone data... // Use timezone data...
@ -66,7 +64,7 @@ pub const Tz = struct {
leapseconds: []const Leapsecond, leapseconds: []const Leapsecond,
footer: ?[]const u8, // POSIX TZ string for future dates 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 pub fn deinit(self: *Tz) void
}; };
``` ```
@ -115,7 +113,7 @@ pub const Leapsecond = struct {
```zig ```zig
fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 { fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 {
// Find the last transition before or at the given timestamp // 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| { for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) { if (t.ts <= unix_timestamp) {
@ -134,8 +132,9 @@ fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 {
return 0; return 0;
} }
// Usage // Usage for a timestamp supplied by the caller
const offset = getUtcOffset(&tz, std.time.timestamp()); const unix_timestamp: i64 = 1_700_000_000;
const offset = getUtcOffset(&tz, unix_timestamp);
const local_time = unix_timestamp + offset; const local_time = unix_timestamp + offset;
``` ```
@ -143,14 +142,16 @@ const local_time = unix_timestamp + offset;
```zig ```zig
fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool { fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool {
var active: ?*const std.Tz.Timetype = null;
for (tz.transitions) |t| { for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) { if (t.ts <= unix_timestamp) {
if (t.timetype.isDst()) return true; active = t.timetype;
} else { } else {
break; 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 ```zig
fn getTimezoneAbbrev(tz: *const std.Tz, unix_timestamp: i64) []const u8 { 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| { for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) { if (t.ts <= unix_timestamp) {
@ -194,14 +195,16 @@ fn printTransitions(tz: *const std.Tz) void {
} }
``` ```
## Parse Errors ## Selected Parse Errors
| Error | Cause | | Error | Cause |
|-------|-------| |-------|-------|
| `error.BadHeader` | Invalid TZif magic bytes (not "TZif") | | `error.BadHeader` | Invalid TZif magic bytes (not "TZif") |
| `error.BadVersion` | Unsupported TZif version (only 0, 2, 3 supported) | | `error.BadVersion` | Unsupported TZif version (only 0, 2, 3 supported) |
| `error.Malformed` | RFC 8536 validation failure | | `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 ## System Timezone Paths
@ -218,7 +221,7 @@ Common timezone identifiers:
## POSIX TZ Footer ## 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 ```zig
if (tz.footer) |posix_tz| { if (tz.footer) |posix_tz| {

View File

@ -49,14 +49,15 @@ while (it2.nextCodepointSlice()) |slice| {
// slice is []const u8: "h", "é", "l", "l", "o", " ", "世", "界" // slice is []const u8: "h", "é", "l", "l", "o", " ", "世", "界"
} }
// Peek ahead without advancing // Peek ahead without advancing a fresh iterator
const next3 = it.peek(3); // next 3 codepoints as UTF-8 bytes var peek_it = view.iterator();
const next3 = peek_it.peek(3); // next 3 codepoints as UTF-8 bytes
// Comptime-validated view // Comptime-validated view
const view = unicode.Utf8View.initComptime("hello"); const comptime_view = unicode.Utf8View.initComptime("hello");
// Unchecked (when you know it's valid) // 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 ## Encoding/Decoding Codepoints
@ -64,17 +65,17 @@ const view = unicode.Utf8View.initUnchecked(trusted_utf8);
```zig ```zig
// Encode codepoint to UTF-8 // Encode codepoint to UTF-8
var buf: [4]u8 = undefined; var buf: [4]u8 = undefined;
const len = try unicode.utf8Encode('é', &buf); // len = 2 const encoded_len = try unicode.utf8Encode('é', &buf); // len = 2
// buf[0..len] contains UTF-8 bytes // buf[0..encoded_len] contains UTF-8 bytes
// Comptime encoding (returns fixed-size array) // Comptime encoding (returns fixed-size array)
const bytes = unicode.utf8EncodeComptime('世'); // [3]u8 const bytes = unicode.utf8EncodeComptime('世'); // [3]u8
// Get UTF-8 sequence length for a codepoint // 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 // 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 ## UTF-8 ↔ UTF-16 Conversion
@ -109,23 +110,25 @@ defer allocator.free(utf8z);
// UTF-8 to UTF-16LE (caller provides buffer) // UTF-8 to UTF-16LE (caller provides buffer)
var utf16_buf: [128]u16 = undefined; var utf16_buf: [128]u16 = undefined;
const len = try unicode.utf8ToUtf16Le(&utf16_buf, "hello"); 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) // UTF-16LE to UTF-8 (caller provides buffer)
var utf8_buf: [256]u8 = undefined; var utf8_buf: [256]u8 = undefined;
const len = try unicode.utf16LeToUtf8(&utf8_buf, utf16_data); const utf8_len = try unicode.utf16LeToUtf8(&utf8_buf, utf16_data);
const utf8 = utf8_buf[0..len]; 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 ### ArrayList Conversion
```zig ```zig
var list = std.ArrayList(u16).empty; var list = std.array_list.Managed(u16).init(allocator);
defer list.deinit(allocator); defer list.deinit();
try unicode.utf8ToUtf16LeArrayList(&list, "hello"); try unicode.utf8ToUtf16LeArrayList(&list, "hello");
var list8 = std.ArrayList(u8).empty; var list8 = std.array_list.Managed(u8).init(allocator);
defer list8.deinit(allocator); defer list8.deinit();
try unicode.utf16LeToUtf8ArrayList(&list8, utf16_data); 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). WTF-8 is like UTF-8 but allows unpaired surrogates (for Windows compatibility).
```zig ```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 unicode.wtf8ValidateSlice(data) // bool
// WTF-8 iteration // WTF-8 iteration
@ -178,13 +182,17 @@ while (it.nextCodepoint()) |cp| {
// WTF-8 ↔ WTF-16 conversion // WTF-8 ↔ WTF-16 conversion
const wtf8 = try unicode.wtf16LeToWtf8Alloc(allocator, wtf16_data); const wtf8 = try unicode.wtf16LeToWtf8Alloc(allocator, wtf16_data);
defer allocator.free(wtf8);
const wtf16 = try unicode.wtf8ToWtf16LeAlloc(allocator, wtf8_data); const wtf16 = try unicode.wtf8ToWtf16LeAlloc(allocator, wtf8_data);
defer allocator.free(wtf16);
// Convert WTF-8 to UTF-8 (lossy - replaces surrogates with U+FFFD) // Convert WTF-8 to UTF-8 (lossy - replaces surrogates with U+FFFD)
const utf8 = try unicode.wtf8ToUtf8LossyAlloc(allocator, wtf8_data); const utf8 = try unicode.wtf8ToUtf8LossyAlloc(allocator, wtf8_data);
defer allocator.free(utf8);
// In-place lossy conversion // In-place is supported when input and output are exactly the same slice.
try unicode.wtf8ToUtf8Lossy(buffer, wtf8_data); // Otherwise output must be at least as long as input.
try unicode.wtf8ToUtf8Lossy(buffer, buffer);
``` ```
## Formatting ## 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 ```zig
fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 { fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
const view = try unicode.Utf8View.init(s); 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 | | Error | Meaning |
|-------|---------| |-------|---------|
@ -255,7 +265,7 @@ fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
| `Utf8InvalidStartByte` | Invalid first byte in sequence | | `Utf8InvalidStartByte` | Invalid first byte in sequence |
| `Utf8ExpectedContinuation` | Missing continuation byte | | `Utf8ExpectedContinuation` | Missing continuation byte |
| `Utf8OverlongEncoding` | Overlong encoding detected | | `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 | | `CodepointTooLarge` | Codepoint > 0x10FFFF |
## Notes ## Notes

View File

@ -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 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 ## Table of Contents
- [Parsing URIs](#parsing-uris) - [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", .{}); std.debug.print("Port not a valid u16\n", .{});
return err; return err;
}, },
error.InvalidHostName => {
std.debug.print("Invalid host name\n", .{});
return err;
},
}; };
``` ```
@ -81,8 +85,6 @@ const Uri = struct {
path: Component = Component.empty, path: Component = Component.empty,
query: ?Component = null, query: ?Component = null,
fragment: ?Component = null, fragment: ?Component = null,
pub const host_name_max = 255;
}; };
``` ```
@ -106,13 +108,14 @@ const Component = union(enum) {
### Getting Host ### Getting Host
```zig ```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) { const host = uri.getHost(&buffer) catch |err| switch (err) {
error.UriMissingHost => return error.NoHost, 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: With allocation:
```zig ```zig
@ -152,6 +155,8 @@ const raw = try component.toRaw(&buf); // "hello world"
const raw_alloc = try component.toRawMaybeAlloc(allocator); // "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 ## Formatting URIs
### Full URI ### Full URI
@ -236,7 +241,7 @@ const decoded = std.Uri.percentDecodeInPlace(&buffer);
// decoded == "hello world!" // decoded == "hello world!"
``` ```
### Decode Backwards (Safe for Aliasing) ### Decode Backwards (Conditionally Safe for Aliasing)
```zig ```zig
const input = "%48%65%6C%6C%6F"; const input = "%48%65%6C%6C%6F";
@ -245,6 +250,8 @@ const decoded = std.Uri.percentDecodeBackwards(&output, input);
// decoded == "Hello" // 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 ### Encode with Component
```zig ```zig
@ -264,7 +271,7 @@ var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
// Encode with custom character validation // 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 { fn isValid(c: u8) bool {
return std.ascii.isAlphanumeric(c); return std.ascii.isAlphanumeric(c);
} }
@ -338,103 +345,101 @@ const name = getQueryParam(uri, "name"); // "alice"
### Build URL with Query Parameters ### Build URL with Query Parameters
```zig ```zig
fn buildUrl(allocator: Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 { fn buildUrl(allocator: std.mem.Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 {
var result: std.ArrayList(u8) = .empty; var result: std.Io.Writer.Allocating = .init(allocator);
defer result.deinit(allocator); errdefer result.deinit();
try result.appendSlice(allocator, base); try result.writer.writeAll(base);
for (params, 0..) |param, i| { for (params, 0..) |param, i| {
try result.append(allocator, if (i == 0) '?' else '&'); try result.writer.writeByte(if (i == 0) '?' else '&');
try (std.Uri.Component{ .raw = param[0] }).formatEscaped(&result.writer);
// Encode key try result.writer.writeByte('=');
for (param[0]) |c| { try (std.Uri.Component{ .raw = param[1] }).formatEscaped(&result.writer);
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}));
}
}
} }
return result.toOwnedSlice(allocator); return result.toOwnedSlice();
} }
``` ```
### Normalize URI ### Serialize a Parsed URI
```zig ```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); const uri = try std.Uri.parse(uri_str);
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); 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); try uri.format(&writer);
return try allocator.dupe(u8, writer.buffered()); 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 ### Validate URI
```zig ```zig
fn isValidUri(str: []const u8) bool { fn isAcceptedByUriParser(str: []const u8) bool {
_ = std.Uri.parse(str) catch return false; _ = std.Uri.parse(str) catch return false;
return true; return true;
} }
fn isValidHttpUri(str: []const u8) bool { fn hasHttpSchemeAndHost(str: []const u8) bool {
const uri = std.Uri.parse(str) catch return false; 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 ### Join Path Segments
```zig ```zig
fn joinPath(allocator: Allocator, base_uri: std.Uri, segments: []const []const u8) !std.Uri { const OwnedUri = struct {
var path: std.ArrayList(u8) = .empty; value: std.Uri,
defer path.deinit(allocator); path_storage: []u8,
// Start with base path (remove trailing slash if any) fn deinit(self: *OwnedUri, allocator: std.mem.Allocator) void {
const base_path = base_uri.path.percent_encoded; allocator.free(self.path_storage);
if (base_path.len > 0 and base_path[base_path.len - 1] == '/') { self.* = undefined;
try path.appendSlice(allocator, base_path[0 .. base_path.len - 1]);
} else {
try path.appendSlice(allocator, base_path);
} }
};
// 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| { for (segments) |seg| {
try path.append(allocator, '/'); const current = path.written();
try path.appendSlice(allocator, seg); 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; var result = base_uri;
result.path = .{ .percent_encoded = try path.toOwnedSlice(allocator) }; result.path = .{ .percent_encoded = owned_path };
result.query = null; result.query = null;
result.fragment = 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 ### Extract Base URL
```zig ```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 buf: [1024]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
@ -461,6 +466,7 @@ pub const ParseError = error{
UnexpectedCharacter, // Invalid character in URI component UnexpectedCharacter, // Invalid character in URI component
InvalidFormat, // Malformed URI structure InvalidFormat, // Malformed URI structure
InvalidPort, // Port not a valid u16 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 ```zig
// getHost errors // getHost errors
error.UriMissingHost // URI has no host component error.UriMissingHost // URI has no host component
error.UriHostTooLong // Host exceeds host_name_max (255)
// toRaw errors // toRaw errors
error.NoSpaceLeft // Buffer too small for decoded string error.NoSpaceLeft // Buffer too small for decoded string

View File

@ -2,6 +2,8 @@
Utilities for parsing, tokenizing, and working with Zig source code. Used for tooling, linters, formatters, and custom analysis. 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 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. 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) { if (tree.errors.len > 0) {
for (tree.errors) |err| { for (tree.errors) |err| {
var buf: [256]u8 = undefined; var buf: [256]u8 = undefined;
var w: std.io.Writer = .fixed(&buf); var w: std.Io.Writer = .fixed(&buf);
try tree.renderError(err, &w); try tree.renderError(err, &w);
std.debug.print("Error: {s}\n", .{w.buffered()}); std.debug.print("Error: {s}\n", .{w.buffered()});
} }
@ -164,7 +166,7 @@ defer allocator.free(formatted);
// Or render to writer // Or render to writer
var buf: [8192]u8 = undefined; 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 tree.render(allocator, &writer.interface, .{});
try writer.interface.flush(); try writer.interface.flush();
``` ```
@ -195,8 +197,19 @@ const token_tag = tree.tokenTag(token_index);
for (tree.rootDecls()) |decl| { for (tree.rootDecls()) |decl| {
switch (tree.nodeTag(decl)) { switch (tree.nodeTag(decl)) {
.fn_decl => handleFunction(tree, decl), .fn_decl => handleFunction(tree, decl),
.global_var_decl, .simple_var_decl => handleVariable(tree, decl), .global_var_decl, .simple_var_decl, .local_var_decl, .aligned_var_decl => {
.container_decl, .container_decl_two => handleStruct(tree, 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 => {}, else => {},
} }
} }
@ -499,7 +512,7 @@ if (tree.errors.len > 0) {
// Format error message // Format error message
var buf: [512]u8 = undefined; var buf: [512]u8 = undefined;
var w: std.io.Writer = .fixed(&buf); var w: std.Io.Writer = .fixed(&buf);
try tree.renderError(err, &w); try tree.renderError(err, &w);
std.debug.print("{s}:{d}:{d}: error: {s}\n", .{ std.debug.print("{s}:{d}:{d}: error: {s}\n", .{
@ -530,7 +543,7 @@ var bundle = try wip_errors.toOwnedBundle("");
defer bundle.deinit(allocator); defer bundle.deinit(allocator);
// Render to stderr // Render to stderr
bundle.renderToStdErr(.{ .ttyconf = .no_color }); try bundle.renderToStderr(io, .{}, .off);
// Or iterate errors // Or iterate errors
for (bundle.getMessages()) |msg_idx| { for (bundle.getMessages()) |msg_idx| {
@ -578,7 +591,7 @@ switch (result) {
```zig ```zig
// Format identifier, escaping if needed // Format identifier, escaping if needed
var buf: [256]u8 = undefined; 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("while")}); // @"while"
try w.print("{f}", .{std.zig.fmtId("hello")}); // hello try w.print("{f}", .{std.zig.fmtId("hello")}); // hello
try w.print("{f}", .{std.zig.fmtId("123abc")}); // @"123abc" try w.print("{f}", .{std.zig.fmtId("123abc")}); // @"123abc"
@ -596,7 +609,7 @@ std.zig.isValidId("a b") // false (contains space)
```zig ```zig
// Escape string for Zig string literal // Escape string for Zig string literal
var buf: [256]u8 = undefined; 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")}); try w.print("\"{f}\"", .{std.zig.fmtString("hello\nworld")});
// Output: "hello\nworld" // Output: "hello\nworld"
@ -621,17 +634,18 @@ const hash = std.zig.hashSrc(source);
// Compare hashes // Compare hashes
if (std.zig.srcHashEql(hash1, hash2)) { 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 ### Read Source File
```zig ```zig
// Read and decode source file (handles UTF-16LE BOM) // Read and decode source file (handles UTF-16LE BOM)
const file = try std.fs.cwd().openFile("source.zig", .{}); const file = try std.Io.Dir.cwd().openFile(io, "source.zig", .{});
defer file.close(); defer file.close(io);
var reader = file.reader(&buf); var reader = file.reader(io, &buf);
const source = try std.zig.readSourceFileToEndAlloc(allocator, &reader); const source = try std.zig.readSourceFileToEndAlloc(allocator, &reader);
defer allocator.free(source); defer allocator.free(source);
``` ```

View File

@ -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 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 ## Table of Contents
- [Module Structure](#module-structure) - [Module Structure](#module-structure)
@ -33,11 +33,11 @@ std.zip.CompressionMethod // .store, .deflate
Extract all files from a ZIP archive to a directory: Extract all files from a ZIP archive to a directory:
```zig ```zig
const file = try std.fs.cwd().openFile("archive.zip", .{}); const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{});
defer file.close(); defer file.close(io);
var buf: [4096]u8 = undefined; 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, .{}); try std.zip.extract(output_dir, &file_reader, .{});
``` ```
@ -65,10 +65,12 @@ if (diagnostics.root_dir.len > 0) {
pub const ExtractOptions = struct { pub const ExtractOptions = struct {
allow_backslashes: bool = false, // normalize \ to / in filenames allow_backslashes: bool = false, // normalize \ to / in filenames
diagnostics: ?*Diagnostics = null, // track extraction metadata 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 ## Iterating Over Entries
### Iterator API ### Iterator API
@ -76,15 +78,15 @@ pub const ExtractOptions = struct {
For more control, iterate over entries individually: For more control, iterate over entries individually:
```zig ```zig
const file = try std.fs.cwd().openFile("archive.zip", .{}); const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{});
defer file.close(); defer file.close(io);
var buf: [4096]u8 = undefined; 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 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| { while (try iter.next()) |entry| {
// Read filename from archive // Read filename from archive
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader)); 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 ```text
pub const Entry = struct { // Descriptive field inventory, not a declaration to copy: the concrete type
// of `flags` is private to std.zip.
struct {
version_needed_to_extract: u16, version_needed_to_extract: u16,
flags: GeneralPurposeFlags, flags: /* private general-purpose-flags type */,
compression_method: CompressionMethod, // .store or .deflate compression_method: CompressionMethod, // .store or .deflate
last_modification_time: u16, // DOS time format last_modification_time: u16, // DOS time format
last_modification_date: u16, // DOS date format last_modification_date: u16, // DOS date format
@ -124,7 +128,7 @@ pub const Entry = struct {
```zig ```zig
var iter = try std.zip.Iterator.init(&file_reader); 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| { while (try iter.next()) |entry| {
// Extract this entry to destination directory // Extract this entry to destination directory
try entry.extract(&file_reader, .{}, &filename_buf, output_dir); try entry.extract(&file_reader, .{}, &filename_buf, output_dir);
@ -138,7 +142,7 @@ Extract only specific files:
```zig ```zig
var iter = try std.zip.Iterator.init(&file_reader); 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| { while (try iter.next()) |entry| {
// Read filename first // Read filename first
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader)); 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 ### Extract ZIP to Directory
```zig ```zig
fn extractZip(allocator: Allocator, zip_path: []const u8, dest_path: []const u8) !void { fn extractZip(io: std.Io, allocator: std.mem.Allocator, zip_path: []const u8, dest_path: []const u8) !void {
const file = try std.fs.cwd().openFile(zip_path, .{}); const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{});
defer file.close(); defer file.close(io);
var buf: [4096]u8 = undefined; 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, .{}); var dest = try std.Io.Dir.cwd().createDirPathOpen(io, dest_path, .{});
defer dest.close(); defer dest.close(io);
var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator }; var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator };
defer diagnostics.deinit(); defer diagnostics.deinit();
@ -257,16 +261,16 @@ fn extractZip(allocator: Allocator, zip_path: []const u8, dest_path: []const u8)
### List ZIP Contents ### List ZIP Contents
```zig ```zig
fn listZip(zip_path: []const u8) !void { fn listZip(io: std.Io, zip_path: []const u8) !void {
const file = try std.fs.cwd().openFile(zip_path, .{}); const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{});
defer file.close(); defer file.close(io);
var buf: [4096]u8 = undefined; 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 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 total_size: u64 = 0;
var file_count: u64 = 0; var file_count: u64 = 0;
@ -299,13 +303,13 @@ fn listZip(zip_path: []const u8) !void {
```zig ```zig
fn extractFile( fn extractFile(
file_reader: *std.fs.File.Reader, file_reader: *std.Io.File.Reader,
target_name: []const u8, target_name: []const u8,
dest: std.fs.Dir, dest: std.Io.Dir,
) !bool { ) !bool {
var iter = try std.zip.Iterator.init(file_reader); 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| { while (try iter.next()) |entry| {
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader)); try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
const filename = filename_buf[0..entry.filename_len]; const filename = filename_buf[0..entry.filename_len];
@ -323,12 +327,12 @@ fn extractFile(
### Check if File is ZIP ### Check if File is ZIP
```zig ```zig
fn isZipFile(path: []const u8) !bool { fn isZipFile(io: std.Io, path: []const u8) !bool {
const file = std.fs.cwd().openFile(path, .{}) catch return false; const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch return false;
defer file.close(); defer file.close(io);
var buf: [4096]u8 = undefined; 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; _ = std.zip.EndRecord.findFile(&file_reader) catch return false;
return true; return true;
@ -337,7 +341,7 @@ fn isZipFile(path: []const u8) !bool {
## Supported Features ## 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 **Compression**: Store (uncompressed), Deflate

View File

@ -1,6 +1,6 @@
# std.zon - ZON Parsing and Serialization # 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 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(); defer _ = gpa.deinit();
const allocator = gpa.allocator(); 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); defer std.zon.parse.free(allocator, config);
// config.name == "server" // config.name == "server"
@ -85,7 +85,7 @@ pub fn main() !void {
var diag: std.zon.parse.Diagnostics = .{}; var diag: std.zon.parse.Diagnostics = .{};
defer diag.deinit(allocator); 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 // Print diagnostic errors
var errors = diag.iterateErrors(); var errors = diag.iterateErrors();
while (errors.next()) |parse_err| { while (errors.next()) |parse_err| {
@ -104,7 +104,7 @@ defer std.zon.parse.free(allocator, result);
### Parse Options ### Parse Options
```zig ```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 (default: false - errors on unknown)
.ignore_unknown_fields = true, .ignore_unknown_fields = true,
@ -128,10 +128,12 @@ const version = build_zon.version;
### Free Parsed Values ### Free Parsed Values
```zig ```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); 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 ## Serializing to ZON
### Simple Serialization ### Simple Serialization
@ -252,10 +254,12 @@ try tuple.end();
var container = try s.beginStruct(.{ var container = try s.beginStruct(.{
.whitespace_style = .{ .wrap = true }, // Always wrap fields .whitespace_style = .{ .wrap = true }, // Always wrap fields
// .whitespace_style = .{ .wrap = false }, // Never wrap (single line) // .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 ### Nested Containers
```zig ```zig
@ -394,42 +398,40 @@ const Config = struct {
debug: bool = false, debug: bool = false,
}; };
fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config { fn loadConfig(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !Config {
const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { const content = std.Io.Dir.cwd().readFileAllocOptions(
io,
path,
allocator,
.limited(1024 * 1024),
.of(u8),
0,
) catch |err| switch (err) {
error.FileNotFound => return Config{}, error.FileNotFound => return Config{},
else => return err, else => return err,
}; };
defer file.close();
const content = try file.readToEndAllocOptions(
allocator,
1024 * 1024,
null,
@alignOf(u8),
0, // null terminator
);
defer allocator.free(content); 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, .ignore_unknown_fields = true,
.free_on_error = 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 ### Serialize to File
```zig ```zig
fn saveConfig(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { fn saveConfig(io: std.Io, config: Config, path: []const u8) !void {
var aw: std.Io.Writer.Allocating = .init(allocator); const file = try std.Io.Dir.cwd().createFile(io, path, .{});
defer aw.deinit(); 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); try std.zon.stringify.serialize(config, .{ .whitespace = true }, &file_writer.interface);
try file_writer.interface.flush();
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
try file.writeAll(aw.written());
} }
``` ```
@ -472,7 +474,7 @@ try std.zon.stringify.serialize(settings, .{
### Round-Trip ZON Data ### Round-Trip ZON Data
```zig ```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 // Serialize
var aw: std.Io.Writer.Allocating = .init(allocator); var aw: std.Io.Writer.Allocating = .init(allocator);
defer aw.deinit(); 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]; const terminated: [:0]const u8 = zon_str[0 .. zon_str.len - 1 :0];
// Parse back // 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.

View File

@ -1,6 +1,6 @@
# Zig Style Guide # 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 ## Naming Conventions
@ -169,22 +169,22 @@ fn processRequest(
- **Omit redundant information** that's already clear from the name - **Omit redundant information** that's already clear from the name
- **Duplicate information** across similar functions (helps IDEs) - **Duplicate information** across similar functions (helps IDEs)
- Use **"assume"** for invariants that cause *unchecked* illegal behavior when violated - Use **"assume"** for unchecked preconditions whose violation may cause illegal behavior
- Use **"assert"** for invariants that cause *safety-checked* illegal behavior when violated - Use **"assert"** when the implementation actively checks an invariant and panics when it is violated
```zig ```zig
/// Reads a little-endian u32 from the buffer. /// Reads a little-endian u32 from the buffer.
/// ///
/// Caller must **assume** buffer has at least 4 bytes remaining. /// Caller must provide at least 4 bytes. The slice expression performs a
/// This is not checked and will cause undefined behavior if violated. /// bounds check in safety-enabled builds and panics if the buffer is shorter.
fn readU32Le(buf: []const u8) u32 { fn readU32Le(buf: []const u8) u32 {
return std.mem.readInt(u32, buf[0..4], .little); return std.mem.readInt(u32, buf[0..4], .little);
} }
/// Pops the last element from the list. /// Pops the last element from the list.
/// ///
/// **Asserts** the list is not empty. In safe modes, returns an error /// **Asserts** the list is not empty. Assertion failure panics; this function
/// or panics if the list is empty. /// has no error return.
fn pop(self: *Self) T { fn pop(self: *Self) T {
std.debug.assert(self.items.len > 0); 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) - **LF** (`\n`, 0x0a) line endings (CRLF discouraged but tolerated)
- End files with a newline - End files with a newline
- No hard tabs (spaces only) - 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 ## Applying the Style Guide

View File

@ -64,7 +64,6 @@ For ABI-sensitive bindings:
- `@Struct` - `@Struct`
- `@Union` - `@Union`
- `@Enum` - `@Enum`
- `@Opaque`
Migration rule: 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. - 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. - 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. - 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 ### Returning Local Addresses
@ -132,7 +133,8 @@ Important removals/renames:
- `std.fmt.format` is replaced by `std.Io.Writer.print`. - `std.fmt.format` is replaced by `std.Io.Writer.print`.
- `std.fmt.Formatter` is renamed to `std.fmt.Alt`. - `std.fmt.Formatter` is renamed to `std.fmt.Alt`.
- `std.fmt.FormatOptions` is renamed to `std.fmt.Options`. - `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. - `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`. - `BitSet` and `EnumSet` use decl literals instead of `initEmpty` / `initFull`.
@ -145,9 +147,11 @@ Important error changes:
### I/O as an Interface ### I/O as an Interface
The core 0.16 rule: all input/output functionality requires an `std.Io` instance. The core 0.16 direction is to route new blocking, OS-facing, and
nondeterministic APIs through an `std.Io` instance. Entropy, time, networking,
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. 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: Main patterns:
@ -203,7 +207,8 @@ Map:
- `std.Thread.ResetEvent` -> `std.Io.Event` - `std.Thread.ResetEvent` -> `std.Io.Event`
- `std.Thread.WaitGroup` -> `std.Io.Group` - `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.Mutex` -> `std.Io.Mutex`
- `std.Thread.Condition` -> `std.Io.Condition` - `std.Thread.Condition` -> `std.Io.Condition`
- `std.Thread.Semaphore` -> `std.Io.Semaphore` - `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. 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: Migration requires an explicit clock choice. Use
`std.Io.Timestamp.now(io, .real)` for wall-clock timestamps and `.boot` or
- `std.time.Instant` -> `std.Io.Timestamp` `.awake` for elapsed-time measurement; `Timestamp` is not a one-for-one timer
- `std.time.Timer` -> `std.Io.Timestamp` replacement.
- `std.time.timestamp` -> `std.Io.Timestamp.now`
Application preference: Application preference:
@ -382,7 +386,9 @@ Several low-level stdlib wrappers were removed as part of moving blocking/nondet
### Allocators ### 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. `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.