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
Starting in Zig 0.16.0, input/output APIs require a `std.Io` instance. Treat anything that can block, touch the OS, use entropy/time/network/process/file APIs, or introduce nondeterminism as owned by `std.Io`.
Zig 0.16.0 routes many new blocking, OS-facing, and nondeterministic APIs
through a `std.Io` instance. For new APIs, treat entropy, time, networking,
process, and file operations as `std.Io` responsibilities. Existing stdlib APIs
are not uniformly parameterized this way, so check each installed signature.
Preferred app entry:
@ -158,11 +161,11 @@ defer client.deinit();
### Time
0.16 routes time through `std.Io` types:
- `std.time.Instant` -> `std.Io.Timestamp`
- `std.time.Timer` -> `std.Io.Timestamp`
- `std.time.timestamp` -> `std.Io.Timestamp.now`
0.16 routes runtime-dependent time through `std.Io`. Choose an explicit
`std.Io.Clock` and pass `io`; for example, use
`std.Io.Timestamp.now(io, .real)` for wall time and `.boot` or `.awake` for
elapsed-time measurements. `std.Io.Timestamp` is not a one-for-one timer
replacement: preserve the old callsite's clock semantics during migration.
Applications may centralize timestamp reads behind a shared helper so clock selection and timestamp semantics remain consistent.
@ -190,7 +193,9 @@ Migration map:
- `std.Thread.RwLock` -> `std.Io.RwLock`
- `std.Thread.ResetEvent` -> `std.Io.Event`
- `std.Thread.WaitGroup` -> `std.Io.Group`
- `std.Thread.Futex` -> `std.Io.Futex`
- `std.Thread.Futex` operations -> `io.futexWait`, `io.futexWaitTimeout`,
`io.futexWaitUncancelable`, and `io.futexWake` on a suitably aligned
four-byte value
Cancelable lock:
@ -212,12 +217,14 @@ Do not replace these with custom spin loops. Use std primitives unless a measure
### Removed or Deprecated
- `@Type` removed. Use `@Int`, `@Struct`, `@Union`, `@Enum`, `@Pointer`, `@Fn`, `@Tuple`, `@Opaque`, or `@EnumLiteral`.
- `@Type` removed. Use `@Int`, `@Struct`, `@Union`, `@Enum`, `@Pointer`,
`@Fn`, `@Tuple`, or `@EnumLiteral`. Declare opaque types with `opaque {}`.
- `@cImport` is deprecated long-term. Prefer `b.addTranslateC(...)` and import `translate_c.createModule()`.
- `async`/`await` keywords remain removed; use `std.Io` task APIs, an application scheduler, or explicit threads.
- `usingnamespace` is removed; explicitly re-export names.
- `@fence` is removed; use stronger atomic orderings or RMW operations.
- `@intFromFloat` is deprecated; use `@trunc` when truncating float to integer.
- `@intFromFloat` converts a finite, in-range float to an integer. `@trunc`
truncates a floating value while retaining a floating result type.
### Packed and Extern Layout
@ -253,7 +260,8 @@ try list.append(gpa, 42);
- Priority queues use `push`/`pop` terminology and `.empty` initialization.
- `std.SegmentedList` removed.
- `std.heap.ThreadSafe` removed.
- `std.heap.ArenaAllocator` is thread-safe and lock-free.
- `std.heap.ArenaAllocator`'s allocator interface is thread-safe when its child
allocator is thread-safe. Do not assume a universal lock-free guarantee.
## Critical: Build System
@ -362,6 +370,6 @@ Load these selectively:
- **[Zig Patterns](references/patterns.md)** - Idiomatic patterns. Treat 0.16 release-note reference as authoritative when examples conflict.
- **[Code Review](references/code-review.md)** - Review checklist. Always apply the 0.16 release-note checks first.
General references remain available for specific modules: `std.mem`, `std.fmt`, `std.json`, `std.zon`, `std.crypto`, `std.http`, `std.net`, `std.compress`, `std.tar`, `std.zip`, `std.testing`, `std.debug`, `std.log`, `std.atomic`, `std.simd`, and other files under `references/`.
General references remain available for specific modules: `std.mem`, `std.fmt`, `std.json`, `std.zon`, `std.crypto`, `std.http`, `std.Io.net`, `std.compress`, `std.tar`, `std.zip`, `std.testing`, `std.debug`, `std.log`, `std.atomic`, `std.simd`, and other files under `references/`.
If any reference still contains a historical 0.14/0.15 example, the Zig 0.16 release-notes reference and the top-level rules in this file win.

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
- [Type Conversions](#type-conversions)
@ -199,10 +199,10 @@ const x = @abs(@as(i32, -5)); // 5
### @min / @max
```zig
@min(a: T, b: T) T
@max(a: T, b: T) T
@min(a: T, b: T, ...) T
@max(a: T, b: T, ...) T
```
Return minimum/maximum of two values.
Return the minimum/maximum of two or more values.
```zig
const m = @max(3, 7); // 7
```
@ -546,13 +546,13 @@ Get type of a struct field.
### @fieldParentPtr
```zig
@fieldParentPtr(field_ptr: anytype, comptime field_name: []const u8) anytype
@fieldParentPtr(comptime field_name: []const u8, field_ptr: anytype) anytype
```
Get pointer to containing struct from field pointer (for intrusive data structures).
```zig
const Node = struct { data: u32, hook: Hook };
fn getNode(hook: *Hook) *Node {
return @fieldParentPtr(hook, "hook");
return @fieldParentPtr("hook", hook);
}
```
@ -629,7 +629,7 @@ fn method(self: *Self) void { ... }
```zig
@src() std.builtin.SourceLocation
```
Get current source location (file, line, column, fn name).
Get current source location (module, file, line, column, and function name).
### @inComptime
```zig
@ -771,7 +771,7 @@ if (unlikely_condition) {
// rarely executed
}
```
Hints: `.none`, `.likely`, `.unlikely`, `.cold`
Hints: `.none`, `.likely`, `.unlikely`, `.cold`, `.unpredictable`
### @breakpoint
```zig
@ -836,7 +836,7 @@ Call function with modifier.
```zig
const result = @call(.always_inline, my_fn, .{ arg1, arg2 });
```
Modifiers: `.auto`, `.never_inline`, `.always_inline`, `.always_tail`, `.never_tail`, `.compile_time`
Modifiers: `.auto`, `.never_inline`, `.always_inline`, `.always_tail`, `.never_tail`, `.compile_time`, `.no_suspend`. The `.no_suspend` modifier asserts that the call will not suspend.
### @prefetch
```zig

View File

@ -26,7 +26,8 @@ Minimal C-compatible library:
```zig
const std = @import("std");
// Global state (opaque to C consumers)
// Global state (opaque to C consumers). This minimal example is not
// thread-safe; init must be called exactly once before get/set/deinit.
var context: ?*Context = null;
const Context = struct {
@ -36,6 +37,7 @@ const Context = struct {
/// Initialize the library. Returns 0 on success, -1 on failure.
export fn mylib_init() c_int {
if (context != null) return -1;
const gpa = std.heap.c_allocator;
context = gpa.create(Context) catch return -1;
context.?.* = .{ .allocator = gpa, .value = 0 };
@ -76,12 +78,10 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
// Link libc if using std.heap.c_allocator
lib.linkLibC();
b.installArtifact(lib);
// Install header alongside library
@ -284,10 +284,9 @@ const lib = b.addLibrary(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
.link_libc = true, // Required by c_allocator or direct libc calls.
}),
});
lib.linkLibC(); // If using c_allocator or libc functions
b.installArtifact(lib);
```
@ -301,11 +300,11 @@ const lib = b.addLibrary(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.version = .{ .major = 1, .minor = 0, .patch = 0 },
});
lib.linkLibC();
b.installArtifact(lib);
```
@ -406,7 +405,9 @@ const std = @import("std");
pub const Context = struct {
allocator: std.mem.Allocator,
error_msg: ?[]const u8 = null,
// Storage is owned by this context and remains valid until the next
// operation that changes the error or until mylib_destroy.
error_msg: ?[:0]const u8 = null,
callback: ?Callback = null,
const Callback = struct {
@ -417,7 +418,9 @@ pub const Context = struct {
export fn mylib_create() ?*Context {
const allocator = std.heap.c_allocator;
return allocator.create(Context) catch null;
const ctx = allocator.create(Context) catch return null;
ctx.* = .{ .allocator = allocator };
return ctx;
}
export fn mylib_destroy(ctx: ?*Context) void {
@ -663,7 +666,7 @@ Name: MyLib
Functions:
- Name: mylib_create
SwiftName: "MyLibContext.create()"
NullabilityOfRet: N # Non-null (returns Optional in Swift)
NullabilityOfRet: O # Nullable C pointer; imported as Optional in Swift
- Name: mylib_destroy
SwiftName: "MyLibContext.destroy(self:)"
- Name: mylib_get_error
@ -802,43 +805,29 @@ export fn get_greeting() [*:0]const u8 {
return greeting.ptr;
}
// Allocate string for caller to free
// Allocate a sentinel-terminated string. The caller must pass the same
// payload length to free_string; the sentinel is stored at index len.
export fn alloc_string(len: usize) ?[*:0]u8 {
const allocator = std.heap.c_allocator;
const buf = allocator.allocSentinel(u8, len, 0) catch return null;
return buf.ptr;
}
export fn free_string(s: ?[*:0]u8) void {
export fn free_string(s: ?[*:0]u8, len: usize) void {
if (s) |ptr| {
const allocator = std.heap.c_allocator;
// Need to know length to free - typically tracked separately
// or use c_allocator which can query allocation size
_ = allocator;
_ = ptr;
allocator.free(ptr[0..len :0]);
}
}
```
### Thread Safety
For thread-safe libraries, use atomics or mutexes:
For simple exported counters, atomics avoid the need to expose an I/O context across the C ABI. More complex synchronization in Zig 0.16 uses `std.Io.Mutex`, whose lock operations take an `std.Io` value; design that runtime context into the library rather than using the removed `std.Thread.Mutex` API.
```zig
const std = @import("std");
var global_mutex: std.Thread.Mutex = .{};
var shared_value: c_int = 0;
export fn thread_safe_increment() c_int {
global_mutex.lock();
defer global_mutex.unlock();
shared_value += 1;
return shared_value;
}
// Or use atomics for simple cases
var atomic_counter: std.atomic.Value(c_int) = .init(0);
export fn atomic_increment() c_int {

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 |
| `.?` unwrap | Runtime panic | Not guarded by `if` or `orelse` |
| `catch unreachable` on alloc/create | Runtime panic | Allocation can fail |
| Unjustified `catch unreachable` on alloc/create | Runtime panic | Allocation can fail and no capacity bound proves otherwise |
| `return &local_var` | Dangling pointer | Returns address of stack variable |
| `&packed_struct.field` | Undefined behavior | Pointer to packed struct field |
| pointer field in `packed struct`/`packed union` | Forbidden in 0.16 | Compile error / invalid layout | Store integer representation only if ABI requires it |
@ -395,7 +395,7 @@ fn getName(user: ?*User) []const u8 {
#### 1.6.3 Catch Unreachable on Allocation
| Detect | `catch unreachable` after `alloc`/`create` calls |
| Detect | Unjustified `catch unreachable` after `alloc`/`create` calls |
|--------|--------------------------------------------------|
| Risk | Runtime panic on OOM |
@ -413,7 +413,7 @@ fn createBuffer(allocator: Allocator) !*Buffer {
}
```
**Verify:** Search for `catch unreachable`, check if operation can fail.
**Verify:** Search for `catch unreachable` and check whether failure is genuinely impossible under a documented invariant. It can be appropriate after a successful reservation or with a fixed, proven capacity bound; ordinary allocation failure should be propagated.
#### 1.6.4 Returning Stack Pointer
@ -536,7 +536,7 @@ fn parseColor(byte: u8) Color {
**Right:**
```zig
fn parseColor(byte: u8) ?Color {
return std.meta.intToEnum(Color, byte) catch null;
return std.enums.fromInt(Color, byte);
}
```
@ -570,10 +570,10 @@ pub fn main() !void {
**Verify:** Search for `DebugAllocator`, check for `defer.*deinit()`.
#### 1.7.2 @ptrCast Size Mismatch
#### 1.7.2 Unsafe @ptrCast Access
| Detect | `@ptrCast` between types of different sizes |
|--------|---------------------------------------------|
| Detect | `@ptrCast` whose result may be under-aligned or address more memory than the source allocation provides |
|--------|------------------------------------------------------------------------------------------------|
| Risk | Memory corruption |
**Wrong:**
@ -585,12 +585,14 @@ fn dangerous(ptr: *u32) *u64 {
**Right:**
```zig
fn reinterpret(ptr: *u32) *[4]u8 {
return @ptrCast(ptr); // Same size
fn firstBytes(ptr: *u32) *align(@alignOf(u32)) [4]u8 {
// The u32 object provides four accessible bytes and the result preserves
// the source pointer's alignment contract.
return @ptrCast(ptr);
}
```
**Verify:** Check `@ptrCast` source and target sizes match.
**Verify:** Check the destination alignment, the number of bytes callers may access through the result, aliasing/lifetime constraints, and whether a byte copy would express the intent more safely. Equal pointee sizes alone do not make a cast valid.
#### 1.7.3 Packed Struct Field Pointer
@ -1311,13 +1313,13 @@ fn formatVersion(allocator: Allocator, major: u32, minor: u32) ![]u8 {
**Right (caller provides buffer):**
```zig
fn formatVersion(buf: []u8, major: u32, minor: u32) []u8 {
return std.fmt.bufPrint(buf, "{d}.{d}", .{ major, minor }) catch unreachable;
fn formatVersion(buf: []u8, major: u32, minor: u32) ![]u8 {
return std.fmt.bufPrint(buf, "{d}.{d}", .{ major, minor });
}
// Call site — buffer outlives the returned slice
var buf: [32]u8 = undefined;
const version = formatVersion(&buf, 1, 2);
const version = try formatVersion(&buf, 1, 2);
```
### 3.11 Comptime Optimization

View File

@ -61,7 +61,7 @@ comptime {
### Container-Level Comptime
Top-level declarations are implicitly comptime.
Top-level constants whose values must be compile-time known are evaluated on demand at compile time. This does not make every top-level declaration a `comptime` block or imply eager evaluation.
```zig
// These are computed at compile time automatically
@ -187,7 +187,7 @@ fn sumComptime(comptime values: []const i32) i32 {
### inline for
Loop unrolling with code generation. Body is duplicated per iteration. Can reference runtime values. Cannot use `break` to return values.
Loop unrolling with code generation. Body is duplicated per iteration and may reference runtime values. Like other loop expressions, an `inline for` can use `break` with a value when its control flow is valid.
```zig
fn printFields(value: anytype) void {
@ -219,7 +219,7 @@ fn eqlAny(comptime T: type, a: T, b: T) bool {
| Need | Use | Reason |
|------|-----|--------|
| Return value from loop | `comptime for` | Only comptime allows `break` with value |
| Evaluate the entire loop and its result at compile time | `comptime for` | All values and control flow must be comptime-known |
| Access runtime values in body | `inline for` | Comptime can't see runtime |
| Type-level computation only | `comptime for` | Clearer intent, no code gen |
| Generate code per iteration | `inline for` | Each iteration = separate code |
@ -250,6 +250,8 @@ const builtin = @import("builtin");
const native_endian = builtin.cpu.arch.endian();
pub fn readIntBig(comptime T: type, bytes: []const u8) T {
comptime std.debug.assert(@typeInfo(T) == .int);
std.debug.assert(bytes.len >= @sizeOf(T));
const value: T = @bitCast(bytes[0..@sizeOf(T)].*);
if (comptime native_endian == .big) {
return value;
@ -261,15 +263,15 @@ pub fn readIntBig(comptime T: type, bytes: []const u8) T {
### Propagating Across Functions
Use `inline fn` to propagate comptime conditions to call sites:
Comptime parameters specialize the function and make conditions that depend on them compile-time known. `inline fn` additionally requests call-site inlining; it is not required merely to eliminate a comptime-known branch:
```zig
// WITHOUT inline: branch exists at runtime
// The enabled branch is specialized at compile time.
fn maybeLog(comptime enabled: bool, msg: []const u8) void {
if (enabled) std.debug.print("{s}\n", .{msg});
}
// WITH inline: branch eliminated at each call site
// inline additionally requests call-site inlining.
inline fn maybeLogInline(comptime enabled: bool, msg: []const u8) void {
if (comptime enabled) std.debug.print("{s}\n", .{msg});
}
@ -414,11 +416,15 @@ fn getTypeName(value: anytype) []const u8 {
fn typeFromName(name: []const u8) type { ... }
```
### No I/O at Comptime
### Comptime I/O Boundary
```zig
// NOT POSSIBLE
const config = comptime std.fs.cwd().readFile("config.json");
// Ordinary runtime filesystem I/O is not available at comptime.
// This is intentionally invalid; Zig 0.16 has no such comptime std.Io call:
// const config = comptime std.Io.Dir.cwd().readFileAlloc(...);
// Compiler-supported static input is available:
const embedded = @embedFile("config.json");
// Alternatives:
const config = @embedFile("config.json"); // Static embedding
@ -448,7 +454,7 @@ fn addMethod(comptime T: type, comptime name: []const u8, impl: anytype) type {
| Type reflection | Yes | `@typeInfo`, `@TypeOf` |
| Generate types | Yes | Return struct from function |
| Add methods to types | No | Define in type definition |
| Read files | No | `@embedFile` or build.zig |
| Read files | Static inputs only | `@embedFile`; use build.zig for ordinary I/O |
| Syscalls | No | build.zig runs as program |
| Parse strings to code | No | Parse to data structures |
| Host detection | No | Build system queries |

View File

@ -31,7 +31,7 @@ Key 0.16 language changes:
### Primitive Types
```zig
// Integers (signed and unsigned, any bit width 1-65535)
// Integers (signed and unsigned, including arbitrary-width integer types)
i8, i16, i32, i64, i128, isize // signed
u8, u16, u32, u64, u128, usize // unsigned
i7, u24, i53 // arbitrary widths
@ -75,6 +75,7 @@ const z: u32 = @bitCast(float_val); // reinterpret bits
```zig
// Fixed-size arrays
const arr: [5]u8 = .{ 1, 2, 3, 4, 5 };
var mutable_arr: [5]u8 = .{ 1, 2, 3, 4, 5 };
const arr2 = [_]u8{ 1, 2, 3 }; // infer length
const zeros = [_]u8{0} ** 100; // repeat pattern
@ -89,7 +90,7 @@ const len = arr.len;
// Iteration
for (arr) |elem| { ... }
for (arr, 0..) |elem, i| { ... } // with index
for (&arr) |*elem| { elem.* = 0; } // mutable
for (&mutable_arr) |*elem| { elem.* = 0; } // mutable
```
### Tuples
@ -220,7 +221,10 @@ for (a, b, c) |x, y, z| { ... }
// Mutable iteration
for (&items) |*item| { item.* = new_value; }
// Range (comptime only for runtime, but works in comptime blocks)
// Runtime bounds work with an ordinary for loop.
for (start..end) |i| { ... }
// Use inline for when the range is comptime-known and should be unrolled.
inline for (0..10) |i| { ... }
```
@ -257,7 +261,7 @@ fn example() void {
}
// Only runs on error return
fn example() !void {
fn example() !*Resource {
const ptr = try allocate();
errdefer free(ptr); // runs only if function returns error
try doSomething(ptr);
@ -587,8 +591,12 @@ const ptr: [*]u8 = buffer.ptr;
const next = ptr + 1;
const offset = ptr + n;
// Single-item pointers do NOT support arithmetic
// Use slicing instead:
// A single-item pointer can only establish a one-element slice by itself.
const one_ptr: *u8 = ...;
const one_item = one_ptr[0..1];
// Slicing an arbitrary length requires a many-item pointer plus an external
// guarantee that at least n elements are accessible.
const slice = ptr[0..n];
```
@ -650,15 +658,9 @@ fn isInteger(comptime T: type) bool {
return @typeInfo(T) == .int;
}
fn fieldNames(comptime T: type) []const []const u8 {
const info = @typeInfo(T);
if (info != .@"struct") @compileError("expected struct");
var names: [info.@"struct".fields.len][]const u8 = undefined;
for (info.@"struct".fields, 0..) |field, i| {
names[i] = field.name;
}
return &names;
fn fieldNames(comptime T: type) *const [std.meta.fields(T).len][:0]const u8 {
// std.meta.fieldNames returns comptime-backed fixed storage.
return std.meta.fieldNames(T);
}
```

View File

@ -64,7 +64,8 @@ Comprehensive patterns for writing idiomatic Zig code. Zig 0.16.0 changes I/O ow
#### Allocator Setup
```zig
// Debug allocator (development - detects leaks, use-after-free)
// Note: GeneralPurposeAllocator is now an alias for DebugAllocator
// DebugAllocator is the development allocator used here. Do not rely on the
// old GeneralPurposeAllocator name being an alias.
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
@ -501,21 +502,21 @@ comptime const x, const y = .{ 1, 2 };
**When to use:** Multiple return values, array element extraction, SIMD vector unpacking.
#### Hashed Mappings Storage
Use multiple `AutoArrayHashMapUnmanaged` fields when storing complex interned data.
Use multiple `std.array_hash_map.Auto` fields when storing complex interned data. The older root-level `AutoArrayHashMapUnmanaged` name is deprecated in Zig 0.16.
```zig
// From llvm/Builder.zig - demonstrating the pattern of parallel maps for interned data
string_map: std.AutoArrayHashMapUnmanaged(void, void),
string_map: std.array_hash_map.Auto(void, void),
string_indices: std.ArrayListUnmanaged(u32),
string_bytes: std.ArrayListUnmanaged(u8),
types: std.AutoArrayHashMapUnmanaged(String, Type),
type_map: std.AutoArrayHashMapUnmanaged(void, void),
types: std.array_hash_map.Auto(String, Type),
type_map: std.array_hash_map.Auto(void, void),
type_items: std.ArrayListUnmanaged(Type.Item),
type_extra: std.ArrayListUnmanaged(u32),
attributes: std.AutoArrayHashMapUnmanaged(Attribute.Storage, void),
attributes_map: std.AutoArrayHashMapUnmanaged(void, void),
attributes: std.array_hash_map.Auto(Attribute.Storage, void),
attributes_map: std.array_hash_map.Auto(void, void),
attributes_indices: std.ArrayListUnmanaged(u32),
```
@ -751,19 +752,19 @@ try stdout.print("{f}", .{version});
**When to use:** Any type that needs custom string representation.
#### Custom Type JSON
Implement `jsonParse`, `jsonParseFromValue`, and `jsonStringify` for JSON support.
The example below implements token-source parsing with `jsonParse`. Complete custom JSON integration may also require `jsonParseFromValue` and `jsonStringify`, depending on the APIs callers use.
```zig
pub fn ArrayHashMap(comptime T: type) type {
return struct {
map: std.StringArrayHashMapUnmanaged(T) = .empty,
map: std.array_hash_map.String(T) = .empty,
pub fn jsonParse(
allocator: Allocator,
source: anytype,
options: ParseOptions,
) !@This() {
var map: std.StringArrayHashMapUnmanaged(T) = .empty;
var map: std.array_hash_map.String(T) = .empty;
errdefer map.deinit(allocator);
if (.object_begin != try source.next()) return error.UnexpectedToken;
@ -1096,11 +1097,9 @@ pub const Node = enum(u32) {
return @enumFromInt(@intFromEnum(r.start) + i);
}
/// Iterate over all nodes in range.
pub fn slice(r: Range) []const Node {
// Note: requires nodes stored contiguously
return @ptrCast(@as([*]const u32, @ptrFromInt(@intFromEnum(r.start)))[0..r.len]);
}
// A Range stores indices, not node values or an address. Use at() to
// iterate, or pass the owning node storage to an accessor that returns
// the corresponding data slice.
};
};
@ -1121,6 +1120,7 @@ When individual node deletion is needed, maintain a freelist stack:
```zig
pub const NodePool = struct {
allocator: std.mem.Allocator,
nodes: std.ArrayListUnmanaged(Node.Data),
/// Head of freelist, or none if no free slots.
free_head: OptionalNode = .none,
@ -1133,7 +1133,7 @@ pub const NodePool = struct {
}
// Allocate new slot
const index: Node = @enumFromInt(self.nodes.items.len);
try self.nodes.append(undefined);
try self.nodes.append(self.allocator, undefined);
return index;
}
@ -1356,7 +1356,7 @@ pub fn internString(state: *State, gpa: Allocator, bytes: []const u8) !String {
}
```
**Real-world example from HashMap.grow:**
**Abbreviated pattern based on HashMap growth logic** (helper declarations such as `new_cap`, `old_capacity`, and `Self.allocate` are intentionally omitted):
```zig
fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void {
var map: Self = .{};
@ -1388,7 +1388,7 @@ fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) All
- String/symbol interning (hash table + byte array)
- Any operation where failure after partial mutation leaves invalid state
**Key insight:** `ensureUnusedCapacity` is magic—it contains all the failure modes but changes nothing. Reservation failures are safe to retry; partial mutations are not.
**Key insight:** `ensureUnusedCapacity` concentrates the fallible reservation step. On error it leaves the container unchanged; on success it may allocate, rehash, change capacity, and invalidate pointers. Reservation failures are safe to retry; partial logical mutations are not.
### IV. Performance Patterns

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.heap.FixedBufferAllocator` | Stack-based, bounded size known | Optional |
| `std.heap.ArenaAllocator` | Batch free, CLI apps, request handlers | No |
| `std.heap.ArenaAllocator` | Batch free, CLI apps, request handlers | Allocator interface only, when child is thread-safe |
| `std.heap.page_allocator` | Backing for other allocators | Yes |
| `std.heap.c_allocator` | Linking libc, interop | Yes |
| `std.heap.raw_c_allocator` | Libc arena backing (no alignment overhead) | Yes |
| `std.heap.DebugAllocator` | Debug builds, leak/corruption detection | Configurable |
| `std.heap.smp_allocator` | ReleaseFast production multithreaded | Yes |
| `std.heap.MemoryPool` | High-frequency same-type allocations | No |
| `std.heap.ThreadSafeAllocator` | Wrap non-thread-safe allocator | Yes |
| `std.heap.StackFallbackAllocator` | Stack buffer with heap fallback | Depends |
| `std.heap.wasm_allocator` | WebAssembly targets | Yes |
@ -156,7 +154,7 @@ slice = try allocator.realloc(slice, new_len);
7. **Many same-type objects?** Use `MemoryPool(T)` for fast create/destroy
8. **Debug build?** Use `DebugAllocator` for leak/corruption detection
9. **ReleaseFast production?** Use `std.heap.smp_allocator`
10. **Linking libc?** Use `c_allocator` or `raw_c_allocator` (as arena backing)
10. **Linking libc?** Use `c_allocator`
## Common Allocators
@ -187,7 +185,7 @@ allocator.free(data);
fba.reset();
```
**Thread-safe variant** (allocate only - no resize/free):
**Thread-safe variant:** allocation is lock-free. Resize, remap, and free are supported, but only the most recent allocation can be expanded or release arena space; shrinking an older allocation succeeds without reclaiming its bytes.
```zig
const ts_allocator = fba.threadSafeAllocator();
```
@ -231,9 +229,9 @@ while (running) {
- `.retain_capacity` - Keep allocated pages for reuse (faster)
- `.{ .retain_with_limit = N }` - Retain up to N bytes
**Query current usage:**
**Query retained capacity:**
```zig
const bytes_used = arena.queryCapacity(); // Excludes internal overhead
const retained_bytes = arena.queryCapacity(); // Excludes internal overhead
```
**State optimization** - store just the state to save memory:
@ -305,47 +303,34 @@ const allocator = std.heap.page_allocator;
Fast allocator for many objects of the same type. Outperforms general-purpose allocators when allocating/freeing objects in rapid succession:
```zig
var pool = std.heap.MemoryPool(MyStruct).init(std.heap.page_allocator);
defer pool.deinit();
const allocator = std.heap.page_allocator;
var pool: std.heap.MemoryPool(MyStruct) = .empty;
defer pool.deinit(allocator);
// Allocate objects (very fast)
const obj1 = try pool.create();
const obj2 = try pool.create();
const obj1 = try pool.create(allocator);
const obj2 = try pool.create(allocator);
// Free returns to pool for reuse (not to backing allocator)
pool.destroy(obj1);
// Reuses freed slot
const obj3 = try pool.create(); // likely same address as obj1
const obj3 = try pool.create(allocator); // likely same address as obj1
// Reset all - batch destroy without individual frees
_ = pool.reset(.retain_capacity);
_ = pool.reset(allocator, .retain_capacity);
```
**Options:**
```zig
// Pre-allocate slots
var pool = try std.heap.MemoryPool(T).initPreheated(allocator, 100);
var pool = try std.heap.MemoryPool(T).initCapacity(allocator, 100);
// Custom alignment
var pool = std.heap.MemoryPoolAligned(T, .@"64").init(allocator);
var pool: std.heap.memory_pool.Aligned(T, .@"64") = .empty;
// Non-growable (fixed capacity)
var pool = try std.heap.MemoryPoolExtra(T, .{ .growable = false }).initPreheated(allocator, 50);
```
### ThreadSafeAllocator
Wraps any allocator with mutex for thread safety:
```zig
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var ts = std.heap.ThreadSafeAllocator{
.child_allocator = arena.allocator(),
};
const allocator = ts.allocator(); // Safe to use from multiple threads
var pool = try std.heap.memory_pool.Extra(T, .{ .growable = false }).initCapacity(allocator, 50);
```
### StackFallbackAllocator
@ -363,18 +348,6 @@ const small = try allocator.alloc(u8, 100);
const large = try allocator.alloc(u8, 10000);
```
### raw_c_allocator
Direct malloc/free without alignment overhead. Use as `ArenaAllocator` backing when linking libc:
```zig
// More efficient than c_allocator when wrapping with ArenaAllocator
var arena = std.heap.ArenaAllocator.init(std.heap.raw_c_allocator);
defer arena.deinit();
```
Requires linking libc. Does not support custom alignment - asserts alignment <= `@alignOf(std.c.max_align_t)`.
### Wasm Allocator
Optimized for WebAssembly. Uses `@wasmMemoryGrow`:
@ -498,7 +471,7 @@ fn process(allocator: Allocator) void {
Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html
- `std.heap.ArenaAllocator` is thread-safe and lock-free in Zig 0.16.
- `std.heap.ArenaAllocator.allocator()` is thread-safe when its child allocator is thread-safe. Concurrent lifecycle, reset, state, and capacity-query operations are outside that guarantee; do not treat “lock-free” as a universal public contract.
- `std.heap.ThreadSafe` allocator was removed.
- Allocators that perform blocking synchronization or file/entropy/time work should accept/store `std.Io` at initialization rather than constructing a local backend.
- Keep allocator naming by memory contract (`gpa`, `arena`, `scratch`) and expose stored `io` through a small accessor only when callsites need it.

View File

@ -7,11 +7,9 @@ Zig 0.16 removed the managed array hash map aliases:
- `std.ArrayHashMap` removed.
- `std.AutoArrayHashMap` removed.
- `std.StringArrayHashMap` removed.
- `std.AutoArrayHashMapUnmanaged` -> `std.array_hash_map.Auto`
- `std.StringArrayHashMapUnmanaged` -> `std.array_hash_map.String`
- `std.ArrayHashMapUnmanaged` -> `std.array_hash_map.Custom`
- The unmanaged root aliases still exist but are deprecated; prefer `std.array_hash_map.Auto`, `.String`, and `.Custom`.
Old examples below may use removed names; translate them to the `std.array_hash_map` names before using them in Zig 0.16 code.
All examples below use the Zig 0.16 unmanaged API: initialize with `.empty`, pass an allocator to operations that may allocate, and pass it again to `deinit`/`clearAndFree`.
A hash map that preserves insertion order and stores keys/values in contiguous arrays. Combines hash table lookup with array-like iteration.
@ -26,23 +24,22 @@ A hash map that preserves insertion order and stores keys/values in contiguous a
| Type | Description |
|------|-------------|
| `AutoArrayHashMap(K, V)` | Auto-hashing for common key types |
| `ArrayHashMap(K, V, Ctx, store_hash)` | Custom hash/equal context |
| `StringArrayHashMap(V)` | String keys |
| `ArrayHashMapUnmanaged(...)` | No stored allocator |
| `std.array_hash_map.Auto(K, V)` | Auto-hashing for common key types |
| `std.array_hash_map.Custom(K, V, Ctx, store_hash)` | Custom hash/equal context |
| `std.array_hash_map.String(V)` | String keys |
## Basic Usage
```zig
const std = @import("std");
var map = std.AutoArrayHashMap(u32, []const u8).init(allocator);
defer map.deinit();
var map: std.array_hash_map.Auto(u32, []const u8) = .empty;
defer map.deinit(allocator);
// Insert
try map.put(1, "one");
try map.put(2, "two");
try map.put(3, "three");
try map.put(allocator, 1, "one");
try map.put(allocator, 2, "two");
try map.put(allocator, 3, "three");
// Lookup
if (map.get(2)) |value| {
@ -58,9 +55,9 @@ if (map.contains(1)) {
## Insertion Order Preserved
```zig
try map.put(10, "ten");
try map.put(5, "five");
try map.put(15, "fifteen");
try map.put(allocator, 10, "ten");
try map.put(allocator, 5, "five");
try map.put(allocator, 15, "fifteen");
// Iteration is in insertion order: 10, 5, 15
var it = map.iterator();
@ -101,13 +98,13 @@ if (map.fetchSwapRemove(key)) |kv| {
```zig
// Get existing or insert new
const result = try map.getOrPut(key);
const result = try map.getOrPut(allocator, key);
if (!result.found_existing) {
result.value_ptr.* = "new_value";
}
// Get or put with default value
const result2 = try map.getOrPutValue(key, "default");
const result2 = try map.getOrPutValue(allocator, key, "default");
```
## Index-Based Operations
@ -125,24 +122,24 @@ if (map.getIndex(key)) |idx| {
## Capacity Management
```zig
try map.ensureTotalCapacity(100);
try map.ensureUnusedCapacity(10);
try map.ensureTotalCapacity(allocator, 100);
try map.ensureUnusedCapacity(allocator, 10);
const cap = map.capacity();
const len = map.count();
map.clearRetainingCapacity();
map.clearAndFree();
map.clearAndFree(allocator);
```
## String Keys
```zig
var map = std.StringArrayHashMap(i32).init(allocator);
defer map.deinit();
var map: std.array_hash_map.String(i32) = .empty;
defer map.deinit(allocator);
try map.put("apple", 1);
try map.put("banana", 2);
try map.put(allocator, "apple", 1);
try map.put(allocator, "banana", 2);
// Keys are stored by reference, not copied
// Make sure string lifetime exceeds map usage
@ -164,16 +161,16 @@ const CaseInsensitiveContext = struct {
}
};
var map = std.ArrayHashMap(
var map: std.array_hash_map.Custom(
[]const u8,
i32,
CaseInsensitiveContext,
true, // store_hash for better performance
).initContext(allocator, .{});
defer map.deinit();
) = .empty;
defer map.deinit(allocator);
try map.put("Hello", 1);
_ = map.get("HELLO"); // finds it!
try map.putContext(allocator, "Hello", 1, .{});
_ = map.getContext("HELLO", .{}); // finds it!
```
## Complete Example: Word Counter
@ -185,13 +182,14 @@ pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
var counts = std.StringArrayHashMap(u32).init(gpa.allocator());
defer counts.deinit();
const allocator = gpa.allocator();
var counts: std.array_hash_map.String(u32) = .empty;
defer counts.deinit(allocator);
const words = [_][]const u8{ "apple", "banana", "apple", "cherry", "banana", "apple" };
for (words) |word| {
const result = try counts.getOrPut(word);
const result = try counts.getOrPut(allocator, word);
if (result.found_existing) {
result.value_ptr.* += 1;
} else {
@ -221,13 +219,13 @@ pub fn main() !void {
| orderedRemove | N/A | O(n) |
| Iteration order | Undefined | Insertion order |
| Key/value arrays | No | Yes |
| Memory layout | Scattered | Contiguous |
| Sequential iteration storage | Not exposed as direct key/value arrays | Direct key/value arrays |
## Notes
- Iteration order equals insertion order
- `swapRemove` is O(1) but changes order
- `orderedRemove` preserves order but is O(n)
- Use `store_hash=true` when `eql` is expensive
- `store_hash=true` trades memory for avoiding some repeated hash work; benchmark it with the actual key/context workload
- Keys/values are stored in `MultiArrayList` (cache-friendly)
- Pointer stability only guaranteed with pre-allocated capacity
- Any modification invalidates iterators. Growth/rehash can invalidate key/value pointers; consult the API's pointer-locking rules rather than assuming a capacity reservation guarantees stability across all mutations.

View File

@ -7,17 +7,19 @@ Dynamic array (vector) that grows as needed.
## Initialization
```zig
// CRITICAL: Use .empty, not .{}
// Use the supported explicit empty initializer.
var list: std.ArrayList(u32) = .empty;
defer list.deinit(allocator);
// With pre-allocated capacity
var list = try std.ArrayList(u32).initCapacity(allocator, 100);
var reserved = try std.ArrayList(u32).initCapacity(allocator, 100);
defer reserved.deinit(allocator);
// From existing slice (takes ownership)
var list = std.ArrayList(u32).fromOwnedSlice(existing_slice);
// Fixed buffer (no allocator needed for operations)
// Fixed buffer: use bounded/no-allocator operations only. Passing an allocator
// to a method on an initBuffer list is illegal.
var buffer: [8]i32 = undefined;
var stack = std.ArrayList(i32).initBuffer(&buffer);
```
@ -36,7 +38,8 @@ list.appendSliceAssumeCapacity(&[_]u32{1, 2, 3});
// Access items
const items = list.items; // []T slice
const first = list.items[0];
const last = list.getLast(); // returns ?T
const last = list.getLast(); // returns T; asserts if empty
const maybe_last = list.getLastOrNull(); // returns ?T
const popped = list.pop(); // returns ?T, removes last
// Insert at index
@ -44,8 +47,8 @@ try list.insert(allocator, 2, value);
try list.insertSlice(allocator, 2, slice);
// Remove
const removed = list.orderedRemove(index); // O(n), preserves order
const removed = list.swapRemove(index); // O(1), doesn't preserve order
const removed_ordered = list.orderedRemove(index); // O(n), preserves order
const removed_swapped = list.swapRemove(index); // O(1), changes order
```
## Capacity Management
@ -57,7 +60,8 @@ try list.ensureUnusedCapacity(allocator, 10);
// Ensure total capacity is at least N
try list.ensureTotalCapacity(allocator, 100);
// Shrink to fit
// Attempt to shrink capacity. If resizing runs out of memory, the list keeps
// excess capacity while still reducing its logical length.
list.shrinkAndFree(allocator, list.items.len);
// Clear
@ -74,6 +78,7 @@ defer allocator.free(owned);
// Get null-terminated slice
const z_str = try list.toOwnedSliceSentinel(allocator, 0);
defer allocator.free(z_str);
```
## Iteration
@ -123,14 +128,18 @@ When inserting into multiple containers or when partial mutation would corrupt s
```zig
// BAD - partial failure leaves invalid state
fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Allocator, value: u32) !void {
fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMapUnmanaged(u32, usize), gpa: Allocator, value: u32) !void {
try list.append(gpa, value); // Can fail
try map.put(gpa, value, list.items.len); // If this fails, list has orphan entry!
try map.put(gpa, value, list.items.len - 1); // If this fails, list has orphan entry!
}
// GOOD - reserve first, then mutate
fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Allocator, value: u32) !void {
// Phase 1: Reserve (fallible, but no mutation)
fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMapUnmanaged(u32, usize), gpa: Allocator, value: u32) !void {
if (map.contains(value)) return error.DuplicateItem;
const index = list.items.len;
// Phase 1: Reserve. Logical contents are unchanged, but capacity may grow
// and existing element pointers may be invalidated.
try list.ensureUnusedCapacity(gpa, 1);
try map.ensureUnusedCapacity(gpa, 1);
@ -138,12 +147,13 @@ fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Al
// Phase 3: Mutate (infallible)
list.appendAssumeCapacity(value);
map.getOrPutAssumeCapacity(value).value_ptr.* = list.items.len;
map.putAssumeCapacityNoClobber(value, index);
}
```
**Key methods:**
- `ensureUnusedCapacity(gpa, n)` - Reserve space for n more items (can fail, doesn't mutate)
- `ensureUnusedCapacity(gpa, n)` - Reserve space for n more items. It can fail
and can reallocate, but it does not append or remove logical elements.
- `appendAssumeCapacity(item)` - Append without allocation (cannot fail, asserts capacity)
- `appendSliceAssumeCapacity(items)` - Append slice without allocation
@ -160,6 +170,8 @@ var arr = std.BoundedArray(u8, 64){};
// NEW
var buffer: [64]u8 = undefined;
var arr = std.ArrayList(u8).initBuffer(&buffer);
// Note: Operations will panic if capacity exceeded
try arr.appendBounded(value); // returns error.OutOfMemory if full
// Bounded operations report capacity exhaustion.
try arr.appendBounded(value); // error.OutOfMemory if full
// Assume-capacity operations assert; allocator-taking operations are illegal
// for an initBuffer list.
```

View File

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

View File

@ -19,8 +19,9 @@ Lock-free atomics do not need `std.Io`. Blocking synchronization should use `std
```zig
std.atomic.Value(T) // Atomic wrapper for T (integers, enums, floats, bools, pointers)
std.atomic.Mutex // Non-blocking/spinning atomic mutex
std.atomic.spinLoopHint() // CPU hint for spin-wait loops
std.atomic.cache_line // CPU cache line size (comptime constant)
std.atomic.cache_line // Target-based cache-line estimate (comptime constant)
```
## Atomic Value Wrapper
@ -179,16 +180,16 @@ fn spinWait(flag: *std.atomic.Value(bool)) void {
}
```
Architecture-specific behavior:
Representative architecture-specific behavior (the implementation contains additional target/feature cases):
- **x86/x86_64**: `pause` instruction
- **AArch64**: `isb` instruction
- **ARM**: `yield` instruction (v6k+)
- **ARM**: feature-dependent yield/spin hint
- **RISC-V**: `pause` (Zihintpause extension)
- **Others**: No-op
- **Some unsupported targets**: No-op
## Cache Line Size
`cache_line` is the CPU cache line size, used to prevent false sharing:
`cache_line` is a target-based estimate of cache-line size, useful when reducing false sharing. It is not a runtime query of the current CPU:
```zig
const cache_line = std.atomic.cache_line; // 64, 128, etc.
@ -205,7 +206,7 @@ const ThreadCounters = struct {
};
```
Typical values by architecture:
Representative values selected by the stdlib's target table (not a complete hardware guarantee):
- x86_64, AArch64: 128 bytes (big cores)
- ARM, MIPS: 32 bytes
- Most others: 64 bytes
@ -300,18 +301,20 @@ fn Stack(comptime T: type) type {
```zig
var initialized = std.atomic.Value(bool).init(false);
var init_mutex: std.Thread.Mutex = .{};
var init_mutex: std.Io.Mutex = .init;
var global_resource: ?*Resource = null;
fn getResource() *Resource {
fn getResource(io: std.Io) *Resource {
// Fast path: already initialized
if (initialized.load(.acquire)) {
return global_resource.?;
}
// Slow path: initialize with lock
init_mutex.lock();
defer init_mutex.unlock();
// This variant deliberately makes initialization uncancelable. Use
// `try init_mutex.lock(io)` in a cancelable function instead.
init_mutex.lockUncancelable(io);
defer init_mutex.unlock(io);
if (!initialized.load(.acquire)) {
global_resource = initializeResource();
@ -412,5 +415,5 @@ const Barrier = struct {
## See Also
- **[std.Thread](std-thread.md)** - Higher-level synchronization (Mutex, RwLock, Condition, Semaphore)
- **[std.Thread.Futex](std-thread.md)** - OS-level blocking primitives
- **[std.Io synchronization](std-io.md)** - Blocking `Mutex`, `RwLock`, `Condition`, `Semaphore`, `Event`, and futex methods
- **[std.Thread](std-thread.md)** - Thread creation, joining, and thread-local facilities

View File

@ -10,7 +10,7 @@ For examples that write encoded output to files/stdout, use `std.Io.Writer` and
| Codec | Use Case |
|-------|----------|
| `standard` | Standard Base64 with `=` padding (email, MIME) |
| `standard` | Standard RFC 4648 Base64 with `=` padding; MIME framing and line wrapping are separate |
| `standard_no_pad` | Standard Base64 without padding |
| `url_safe` | URL-safe Base64 with `=` padding |
| `url_safe_no_pad` | URL-safe Base64 without padding (JWT, URLs) |
@ -63,7 +63,7 @@ const encoded = "SGVs bG8s\nIFdv cmxk IQ=="; // with spaces and newlines
const decoder = base64.standard.decoderWithIgnore(" \n");
var buf: [100]u8 = undefined;
const max_size = try decoder.calcSizeUpperBound(encoded.len);
const max_size = decoder.calcSizeUpperBound(encoded.len);
const decoded_len = try decoder.decode(buf[0..max_size], encoded);
const decoded = buf[0..decoded_len];
// "Hello, World!"
@ -71,6 +71,8 @@ const decoded = buf[0..decoded_len];
## Streaming Encoding
This fragment assumes a caller-provided `io: std.Io` and `data: []const u8`:
```zig
var buf: [4096]u8 = undefined;
var writer = std.Io.File.stdout().writer(io, &buf);
@ -110,6 +112,7 @@ base64.standard.Decoder.decode(dest, source) catch |err| switch (err) {
### Encode binary data for JSON/URLs
```zig
fn encodeForUrl(data: []const u8, buf: []u8) []const u8 {
std.debug.assert(buf.len >= std.base64.url_safe_no_pad.Encoder.calcSize(data.len));
return std.base64.url_safe_no_pad.Encoder.encode(buf, data);
}
```
@ -126,11 +129,12 @@ fn decodeJwtPayload(payload: []const u8, buf: []u8) ![]u8 {
### Handle multi-line Base64 (PEM format)
```zig
fn decodePem(pem_data: []const u8, buf: []u8) ![]u8 {
// Skip header/footer, decode with newline ignoring
fn decodePemBody(base64_body: []const u8, buf: []u8) ![]u8 {
// The caller must extract and validate the PEM header/footer first. This
// helper only decodes the Base64 body while ignoring line breaks.
const decoder = std.base64.standard.decoderWithIgnore("\n\r");
const max = try decoder.calcSizeUpperBound(pem_data.len);
const len = try decoder.decode(buf[0..max], pem_data);
const max = decoder.calcSizeUpperBound(base64_body.len);
const len = try decoder.decode(buf[0..max], base64_body);
return buf[0..len];
}
```
@ -141,4 +145,4 @@ fn decodePem(pem_data: []const u8, buf: []u8) ![]u8 {
- URL-safe uses `-` and `_` which are safe in URLs
- Padding (`=`) makes length divisible by 4
- `calcSizeForSlice` gives exact size; `calcSizeUpperBound` gives max (ignores padding)
- All codecs use little-endian byte order internally
- Base64 operates on byte slices; integer endianness is not part of its public contract

View File

@ -13,9 +13,9 @@ Densely stored sets of integers with efficient set operations (union, intersecti
| Type | Size | Allocation |
|------|------|------------|
| `IntegerBitSet(N)` | Compile-time, N <= 128 | None (single integer) |
| `IntegerBitSet(N)` | Compile-time, backed by one integer of N bits | None (single integer) |
| `ArrayBitSet(usize, N)` | Compile-time, any N | None (array) |
| `StaticBitSet(N)` | Compile-time | Auto-selects Integer or Array |
| `StaticBitSet(N)` | Compile-time | Uses Integer when N <= `@bitSizeOf(usize)`, otherwise Array |
| `DynamicBitSet` | Runtime | Allocator (managed) |
| `DynamicBitSetUnmanaged` | Runtime | Allocator (unmanaged) |
@ -27,7 +27,7 @@ const std = @import("std");
// StaticBitSet auto-selects best implementation
const Flags = std.StaticBitSet(64);
var flags = Flags.initEmpty();
var flags: Flags = .empty;
flags.set(5);
flags.set(10);
@ -52,7 +52,7 @@ bits.set(100);
// Resize dynamically
try bits.resize(2000, false); // false = new bits are 0
try bits.resize(2000, true); // true = new bits are 1
try bits.resize(2500, true); // true = newly added bits are 1
// Clone
var copy = try bits.clone(allocator);
@ -62,8 +62,8 @@ defer copy.deinit();
## Set Operations
```zig
var a = Flags.initEmpty();
var b = Flags.initEmpty();
var a: Flags = .empty;
var b: Flags = .empty;
a.set(1); a.set(2);
b.set(2); b.set(3);
@ -99,7 +99,7 @@ if (a.supersetOf(b)) {
## Iteration
```zig
var flags = Flags.initEmpty();
var flags: Flags = .empty;
flags.set(1); flags.set(5); flags.set(10);
// Iterate set bits (ascending order by default)
@ -135,7 +135,7 @@ if (flags.findLastSet()) |index| {
// index of highest set bit
}
// Find and toggle (atomic-like)
// Find the first set bit and remove it from this set (not an atomic operation)
if (flags.toggleFirstSet()) |index| {
// returns index and unsets the bit
}
@ -171,7 +171,7 @@ const Permission = enum(u8) {
admin = 4,
};
const Permissions = std.StaticBitSet(8);
const Permissions = std.StaticBitSet(@typeInfo(Permission).@"enum".fields.len);
fn hasPermission(perms: Permissions, p: Permission) bool {
return perms.isSet(@intFromEnum(p));
@ -186,14 +186,14 @@ fn revoke(perms: *Permissions, p: Permission) void {
}
pub fn main() void {
var user_perms = Permissions.initEmpty();
var user_perms: Permissions = .empty;
grant(&user_perms, .read);
grant(&user_perms, .write);
var admin_perms = Permissions.initFull();
const admin_perms: Permissions = .full;
// Check if user has all admin permissions
if (user_perms.subsetOf(admin_perms)) {
if (admin_perms.subsetOf(user_perms)) {
// user can do everything admin can (not in this case)
}
@ -206,6 +206,6 @@ pub fn main() void {
- `StaticBitSet` is zero-allocation, copyable by value
- `DynamicBitSet` requires allocation, call `deinit()`
- `initFull()` creates set with all bits set
- `.full` creates a static set with all bits set; dynamic sets use their initialization functions
- Iteration order is index order, not insertion order
- Use `std.enums.EnumSet` for enum-based bit flags

View File

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

View File

@ -457,7 +457,7 @@ const zlib = b.dependency("zlib", .{
exe.root_module.addImport("zlib", zlib.module("zlib"));
// Get artifact from dependency
exe.linkLibrary(zlib.artifact("z"));
exe.root_module.linkLibrary(zlib.artifact("z"));
// Get path from dependency
const include_path = zlib.path("include");
@ -495,8 +495,8 @@ const dawn_dep = switch (target.result.os.tag) {
if (dawn_dep) |dep| {
// Dependency is available, use normally
exe.addLibraryPath(dep.path("lib"));
exe.linkSystemLibrary("dawn");
exe.root_module.addLibraryPath(dep.path("lib"));
exe.root_module.linkSystemLibrary("dawn", .{});
}
```
@ -542,7 +542,7 @@ run_step.dependOn(&run_cmd.step);
const cmd = b.addSystemCommand(&.{ "git", "describe", "--tags" });
// Capture output
const version = cmd.captureStdOut();
const version = cmd.captureStdOut(.{});
// Use output as file
const version_file = b.addInstallFile(version, "version.txt");
@ -705,7 +705,7 @@ const config_h = b.addConfigHeader(.{
.VERSION_STRING = "1.0.0",
});
exe.addConfigHeader(config_h);
exe.root_module.addConfigHeader(config_h);
```
### Code Generation with Zig Tool
@ -729,9 +729,8 @@ exe.root_module.addAnonymousImport("schema", .{
## C/C++ Integration
> **Note:** Compile-level methods like `exe.addCSourceFiles()`, `exe.linkSystemLibrary()`,
> `exe.addIncludePath()`, `exe.linkLibC()` are **deprecated** (to be removed after 0.15.0).
> Use `exe.root_module.*` equivalents shown below.
> **Note:** Zig 0.16 configures compilation and linking on the artifact's `root_module`.
> Older Compile-level methods are no longer the API shown here.
### Adding C Sources
```zig
@ -762,11 +761,11 @@ exe.root_module.linkSystemLibrary("pthread", .{});
exe.root_module.linkSystemLibrary("ssl", .{});
// Static library file
exe.addObjectFile(b.path("lib/libfoo.a"));
exe.root_module.addObjectFile(b.path("lib/libfoo.a"));
// Library search path
exe.addLibraryPath(b.path("lib"));
exe.addRPath(b.path("lib"));
exe.root_module.addLibraryPath(b.path("lib"));
exe.root_module.addRPath(b.path("lib"));
// Link libc (set via createModule options or directly)
exe.root_module.link_libc = true;
@ -835,7 +834,7 @@ const cwd_path: std.Build.LazyPath = .{ .cwd_relative = "/absolute/path" };
exe.root_module.root_source_file = b.path("src/main.zig");
// As include path
exe.addIncludePath(dep.path("include"));
exe.root_module.addIncludePath(dep.path("include"));
// Install
b.installFile(generated_file, "share/output.txt");
@ -849,16 +848,20 @@ LazyPath is central to how data flows between build steps. Understanding its var
```zig
const LazyPath = union(enum) {
// Path relative to build root (most common)
src_path: struct { root: ?*Build, sub_path: []const u8 },
src_path: struct { owner: *Build, sub_path: []const u8 },
// Output from a build step (e.g., compiled binary, generated file)
generated: struct { file: *GeneratedFile, sub_path: ?[]const u8 },
generated: struct {
file: *const GeneratedFile,
up: usize = 0,
sub_path: []const u8 = "",
},
// Absolute or CWD-relative path (use sparingly)
cwd_relative: []const u8,
// Path inside a dependency package
dependency: struct { dep: *Dependency, sub_path: []const u8 },
dependency: struct { dependency: *Dependency, sub_path: []const u8 },
};
```
@ -902,10 +905,12 @@ const base = b.path("src/modules/parser");
const parent = base.dirname(); // "src/modules"
// Concatenate subpath
const file = base.join("lexer.zig"); // "src/modules/parser/lexer.zig"
const file = try base.join(b.allocator, "lexer.zig");
// Convenience form with the build allocator (panics on OOM):
const same_file = base.path(b, "lexer.zig");
// Chain operations
const sibling = base.dirname().join("utils/helpers.zig"); // "src/modules/utils/helpers.zig"
const sibling = try base.dirname().join(b.allocator, "utils/helpers.zig");
```
## Build Allocation
@ -1123,13 +1128,5 @@ fmt_step.dependOn(&fmt.step);
```
### Clean Step
```zig
const clean_step = b.step("clean", "Clean build artifacts");
clean_step.dependOn(&b.addRemoveDirTree(b.path("zig-out")).step);
// Note: zig-cache deletion may fail on Windows while build is running
if (@import("builtin").os.tag != .windows) {
clean_step.dependOn(&b.addRemoveDirTree(b.path(".zig-cache")).step);
}
```
Zig 0.16 does not provide `Build.addRemoveDirTree`. Prefer keeping generated outputs in normal build/install locations and clean them outside the running build graph. If a project adds an explicit platform-specific removal command, treat it as an external command with its own portability and concurrent-build hazards; do not delete the active cache from a build step.

View File

@ -31,13 +31,13 @@ Use `std.c` when:
Prefer higher-level alternatives when available:
```zig
// High-level (recommended)
const file = try std.fs.cwd().openFile("data.txt", .{});
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
// POSIX-level
const fd = try std.posix.open("data.txt", .{}, 0);
// Checked OS-level wrappers live under std.posix on supported targets. Their
// signatures and flag types are target-aware and report Zig errors.
// C-level (direct libc, lowest level)
const fd = std.c.open("data.txt", .{}, 0);
// C-level std.c bindings are raw libc calls: check the return code and errno.
const fd = std.c.open("data.txt", flags, mode);
```
## Fundamental Types
@ -94,21 +94,11 @@ c.pid_t // Process ID (platform-specific)
```zig
const c = std.c;
// Platform-specific stat structure
// Stat is selected for the compilation target. Its field names and layout are
// not a portable record; inspect `c.Stat` for the selected target before direct
// field access, or prefer a higher-level std.Io API.
const stat: c.Stat = undefined;
// Fields (vary by platform):
stat.dev // Device ID
stat.ino // Inode number
stat.mode // File mode
stat.nlink // Number of hard links
stat.uid // Owner UID
stat.gid // Owner GID
stat.size // File size in bytes
stat.atim // Last access time (timespec)
stat.mtim // Last modification time
stat.ctim // Last status change time
stat.blksize // Preferred block size
stat.blocks // Number of 512-byte blocks
_ = stat;
```
### I/O Vectors
@ -273,10 +263,10 @@ c.sockaddr // Generic socket address
.family // Address family (sa_family_t)
.data // Address data
c.sockaddr_in // IPv4 address (from std.posix)
c.sockaddr_in6 // IPv6 address
c.sockaddr_un // Unix domain socket
c.sockaddr_storage // Large enough for any address
c.sockaddr.in // IPv4 address, when exported for the selected target
c.sockaddr.in6 // IPv6 address, when exported for the selected target
c.sockaddr.un // Unix domain socket, on applicable targets
c.sockaddr.storage // Generic storage, on applicable targets
c.socklen_t // Socket address length type
c.sa_family_t // Address family type
@ -307,10 +297,10 @@ c.SOCK // Socket types
.CLOEXEC // Set close-on-exec
.NONBLOCK // Non-blocking
c.SOL // Socket level for options
.SOCKET // Socket-level options
.IP, .IPV6 // IP-level options
.TCP, .UDP // Protocol-level options
c.SOL // Target-specific socket option levels
.SOCKET // Widely available socket-level option value
// Other members vary by target. Protocol numbers such as IP/TCP/UDP are
// not a portable generic `SOL` member set.
c.SO // Socket options (SOL_SOCKET level)
.REUSEADDR, .REUSEPORT
@ -455,8 +445,7 @@ c.MAP // mmap flags
.SHARED // Share changes
.PRIVATE // Private copy-on-write
.FIXED // Use exact address
.ANONYMOUS // No file backing (Linux/BSD)
.ANON // Alias for ANONYMOUS
.ANONYMOUS // No file backing on targets that export this spelling
.NORESERVE // Don't reserve swap
.STACK // Stack mapping
// Platform-specific flags
@ -714,6 +703,8 @@ c.port_event // Port event structure
## Libc Function Bindings
`std.c` declarations are selected by target OS, architecture, libc, and sometimes libc version. The names below are an orientation guide, not a universally callable inventory: verify that each declaration is non-`void` for the selected target and link libc when required.
### File Operations
```zig
c.close // Close file descriptor
@ -792,49 +783,47 @@ c.getcontext // Get current context (some platforms)
### darwin (macOS/iOS)
```zig
const darwin = std.c.darwin; // Internal, re-exported via std.c
const c = std.c; // Darwin declarations below are public direct re-exports.
// Mach types and functions
darwin.mach_port_t
darwin.mach_task_self()
darwin.mach_msg()
darwin.mach_host_self()
darwin.mach_timebase_info()
darwin.mach_absolute_time()
c.mach_port_t
c.mach_task_self()
c.mach_msg()
c.mach_host_self()
c.mach_timebase_info()
c.mach_absolute_time()
// Exception handling
darwin.EXC, darwin.EXCEPTION
darwin.task_set_exception_ports()
darwin.task_get_exception_ports()
c.EXC, c.EXCEPTION
c.task_set_exception_ports()
c.task_get_exception_ports()
// Thread state
darwin.thread_state
darwin.thread_get_state()
darwin.thread_set_state()
c.thread_state
c.thread_get_state()
c.thread_set_state()
// VM operations
darwin.mach_vm_read()
darwin.mach_vm_write()
darwin.mach_vm_protect()
darwin.mach_vm_region()
c.mach_vm_read()
c.mach_vm_write()
c.mach_vm_protect()
c.mach_vm_region()
// Dispatch/GCD semaphores
darwin.dispatch_semaphore_create()
darwin.dispatch_semaphore_wait()
darwin.dispatch_semaphore_signal()
// Public Dispatch/GCD aliases (only names exported by this namespace)
c.dispatch
// Unfair locks
darwin.os_unfair_lock
darwin.os_unfair_lock_lock()
darwin.os_unfair_lock_unlock()
c.os_unfair_lock
c.os_unfair_lock_lock()
c.os_unfair_lock_unlock()
// Process spawning
darwin.posix_spawn()
darwin.posix_spawn_file_actions_*
c.posix_spawn()
c.posix_spawn_file_actions_*
// File copy
darwin.fcopyfile()
darwin.COPYFILE
c.fcopyfile()
c.COPYFILE
```
### freebsd
@ -944,7 +933,7 @@ extern "c" fn c_function(fd: std.c.fd_t, buf: [*]u8, len: std.c.size_t) std.c.ss
pub fn wrapper(fd: std.posix.fd_t, buf: []u8) !usize {
const result = c_function(fd, buf.ptr, buf.len);
if (result < 0) {
const err = std.posix.errno(std.c._errno().*);
const err: std.c.E = @enumFromInt(std.c._errno().*);
return std.posix.unexpectedErrno(err);
}
return @intCast(result);
@ -963,7 +952,7 @@ fn platformSpecificCall() void {
},
.macos, .ios => {
// Darwin uses different types
const port = std.c.darwin.mach_port_t;
const port = std.c.mach_port_t;
},
.windows => {
// Windows uses HANDLE

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
Old examples below may use removed `std.io.GenericReader` or `fixedBufferStream` patterns. Translate them to `std.Io.Reader` / `std.Io.Writer` before using them in Zig 0.16 code.
The examples below use Zig 0.16's concrete `std.Io.Reader` / `std.Io.Writer` interfaces rather than removed generic-reader or fixed-buffer-stream patterns.
## Table of Contents
- [Module Structure](#module-structure)
@ -80,13 +80,15 @@ var output: std.Io.Writer.Allocating = .init(allocator);
defer output.deinit();
var buffer: [flate.max_window_len]u8 = undefined;
var compress: flate.Compress = .init(&output.writer, &buffer, .{
.level = .default,
.container = .gzip,
});
var compress: flate.Compress = try .init(
&output.writer,
&buffer,
.gzip,
.default,
);
try compress.writer.writeAll(data);
try compress.end();
try compress.finish();
const compressed = output.written();
```
@ -94,7 +96,10 @@ const compressed = output.written();
### Compression Levels
```zig
const Level = enum {
const Options = struct {
// Options are parameter sets rather than an enum. Levels 1 through 9 are
// available; these public constants select common presets:
level_1: Options,
level_4, // Fastest
level_5,
level_6, // Default
@ -102,7 +107,7 @@ const Level = enum {
level_8,
level_9, // Best compression
fast, // Alias for level_4
fastest, // Alias for level_1
default, // Alias for level_6
best, // Alias for level_9
};
@ -110,12 +115,7 @@ const Level = enum {
### Huffman-Only Compression
For faster compression without LZ77 match searching:
```zig
const HuffmanEncoder = flate.HuffmanEncoder;
// Used internally for Huffman-only encoding (bigger output, faster compression)
```
`flate.Compress.Huffman` exposes public initialization and a writer that skips LZ77 match searching. However, in the installed Zig 0.16 source its end-of-stream `finish` method is private. External code therefore cannot complete the container lifecycle through the public API alone. Treat it as an implementation-facing type in this release rather than a standalone archive-writing recipe; use `flate.Compress` when a complete public compression lifecycle is required.
## Zstandard
@ -171,38 +171,38 @@ LZMA decompression with streaming reader interface.
```zig
const lzma = std.compress.lzma;
var decompress = try lzma.decompress(allocator, reader);
var decoder_buffer = try allocator.alloc(u8, 4096);
errdefer allocator.free(decoder_buffer);
var decompress: lzma.Decompress = try .initOptions(
&reader,
allocator,
decoder_buffer,
.{},
128 * 1024 * 1024,
);
defer decompress.deinit();
var buf: [4096]u8 = undefined;
while (true) {
const n = try decompress.read(&buf);
if (n == 0) break;
// Process buf[0..n]
}
_ = try decompress.reader.streamRemaining(&output.writer);
```
### With Options
```zig
var decompress = try lzma.decompressWithOptions(allocator, reader, .{
.memlimit = 128 * 1024 * 1024, // 128 MB memory limit
});
var decompress: lzma.Decompress = try .initOptions(
&reader,
allocator,
decoder_buffer,
.{ .allow_incomplete = false },
128 * 1024 * 1024,
);
```
### Decompress Type
```zig
pub fn Decompress(comptime ReaderType: type) type {
return struct {
pub const Reader = std.io.GenericReader(*Self, Error, read);
pub fn init(allocator: Allocator, source: ReaderType, params: Params, memlimit: ?usize) !Self;
pub fn deinit(self: *Self) void;
pub fn reader(self: *Self) Reader;
pub fn read(self: *Self, output: []u8) Error!usize;
};
}
// Decompress embeds `reader: std.Io.Reader` and owns the caller-supplied
// buffer after init. `deinit` frees that buffer unless `takeBuffer` first
// reclaims it. `initParams` and `initOptions` are the construction paths.
```
## LZMA2
@ -214,11 +214,12 @@ LZMA2 decompression (improved LZMA with better streaming support).
```zig
const lzma2 = std.compress.lzma2;
var output = std.ArrayList(u8).empty;
defer output.deinit(allocator);
var stream = std.io.fixedBufferStream(compressed_data);
try lzma2.decompress(allocator, stream.reader(), output.writer(allocator));
var input: std.Io.Reader = .fixed(compressed_data);
var output: std.Io.Writer.Allocating = .init(allocator);
defer output.deinit();
var decode = try lzma2.Decode.init(allocator);
defer decode.deinit(allocator);
_ = try decode.decompress(&input, &output);
```
## XZ
@ -230,20 +231,17 @@ XZ format decompression (LZMA2 in a container with checksums).
```zig
const xz = std.compress.xz;
var decompress = try xz.decompress(allocator, reader);
var decoder_buffer = try allocator.alloc(u8, 4096);
errdefer allocator.free(decoder_buffer);
var decompress: xz.Decompress = try .init(&reader, allocator, decoder_buffer);
defer decompress.deinit();
var buf: [4096]u8 = undefined;
while (true) {
const n = try decompress.read(&buf);
if (n == 0) break;
// Process buf[0..n]
}
_ = try decompress.reader.streamRemaining(&output.writer);
```
### Check Types
XZ supports multiple integrity check types:
XZ parses these integrity-check identifiers, but Zig 0.16 does not implement full XZ block-check verification. Do not treat successful decoding as verification of CRC32, CRC64, or SHA-256 block checks:
```zig
pub const Check = enum(u4) {
@ -312,23 +310,24 @@ fn decompressZstd(allocator: Allocator, compressed: []const u8) ![]u8 {
```zig
fn decompressToFile(
io: std.Io,
input_path: []const u8,
output_path: []const u8,
container: std.compress.flate.Container,
) !void {
const flate = std.compress.flate;
const input_file = try std.fs.cwd().openFile(input_path, .{});
defer input_file.close();
const input_file = try std.Io.Dir.cwd().openFile(io, input_path, .{});
defer input_file.close(io);
const output_file = try std.fs.cwd().createFile(output_path, .{});
defer output_file.close();
const output_file = try std.Io.Dir.cwd().createFile(io, output_path, .{});
defer output_file.close(io);
var input_buf: [4096]u8 = undefined;
var input_reader = input_file.reader(&input_buf);
var input_reader = input_file.reader(io, &input_buf);
var output_buf: [4096]u8 = undefined;
var output_writer = output_file.writer(&output_buf);
var output_writer = output_file.writer(io, &output_buf);
var decompress: flate.Decompress = .init(&input_reader.interface, container, &.{});
_ = try decompress.reader.streamRemaining(&output_writer.interface);
@ -437,7 +436,7 @@ pub const Error = error{
**DEFLATE (flate)**:
- Decompression: gzip, zlib, raw deflate
- Compression: gzip, zlib, raw deflate (levels 4-9)
- Compression: gzip, zlib, raw deflate (levels 1-9)
- Streaming with history buffer
**Zstandard (zstd)**:
@ -453,5 +452,5 @@ pub const Error = error{
**XZ**:
- Decompression only
- CRC32/CRC64/SHA256 integrity checks
- CRC32/CRC64/SHA256 check IDs are parsed; full block-check verification is incomplete
- Multiple block support

View File

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

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
Zig 0.16 reworked debug information and expanded target support for segfault handling/unwinding. When debug output writes to stderr/stdout directly, use `std.Io.File.stderr().writer(io, &buf)` / `std.Io.File.stdout().writer(io, &buf)` rather than old `std.io` or `std.fs.File` APIs.
Zig 0.16 reworked debug information and expanded target support for segfault handling/unwinding. For application-owned output, create a writer from the caller's `std.Io` (for example `std.Io.File.stderr().writer(io, &buf)`). Low-level `std.debug.print` and stack-dump helpers intentionally use the configured debug I/O path instead.
## Quick Reference
@ -33,7 +33,7 @@ std.debug.print("loading...", .{});
## Format Specifiers
Format string syntax: `{[arg]:[fill][alignment][width][.precision][specifier]}`
Format string syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}`. Named arguments use square brackets, for example `{[name]s}`.
### Type Specifiers
@ -99,11 +99,11 @@ std.debug.print("{x:.4}\n", .{@as(f32, 1.0)}); // "0x1.0000p0"
std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a"
// Named (with struct)
std.debug.print("{name}: {value}\n", .{ .name = "x", .value = 42 });
std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 });
// Runtime width/precision
std.debug.print("{d:[width]}\n", .{ .width = 5, 42 });
std.debug.print("{d:.[precision]}\n", .{ .precision = 2, 3.14159 });
std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = 5 });
std.debug.print("{[value]d:.[precision]}\n", .{ .value = 3.14159, .precision = 2 });
```
### Escape Braces
@ -177,47 +177,32 @@ Panic prints message + stack trace to stderr, then aborts.
```zig
// Print current stack trace to stderr
std.debug.dumpCurrentStackTrace(null);
std.debug.dumpCurrentStackTrace(.{});
// Skip frames until this address
std.debug.dumpCurrentStackTrace(@returnAddress());
std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
```
### Dump to Writer
```zig
var buf: [4096]u8 = undefined;
var stderr = std.Io.File.stderr().writer(io, &buf);
try std.debug.dumpCurrentStackTraceToWriter(null, &stderr.interface);
var locked = try io.lockStderr(&buf, null);
defer io.unlockStderr();
try std.debug.writeCurrentStackTrace(.{}, locked.terminal());
```
### Capture Stack Trace
```zig
var addrs: [32]usize = undefined;
var trace: std.builtin.StackTrace = .{
.instruction_addresses = &addrs,
.index = 0,
};
std.debug.captureStackTrace(@returnAddress(), &trace);
const trace = std.debug.captureCurrentStackTrace(.{}, &addrs);
// Later: print captured trace
std.debug.dumpStackTrace(trace);
std.debug.dumpStackTrace(&trace);
```
### StackIterator
Walk the stack manually:
```zig
var it = std.debug.StackIterator.init(@returnAddress(), null);
defer it.deinit();
while (it.next()) |return_address| {
const addr = return_address -| 1;
std.debug.print("0x{x}\n", .{addr});
}
```
`StackUnwindOptions` can also carry a target-specific `cpu_context.Native` pointer and opt into unsafe fallback unwinding. The stack iterator itself is an implementation detail; use `captureCurrentStackTrace`, `writeCurrentStackTrace`, and `dumpCurrentStackTrace` as the public interface.
## Hex Dump
@ -229,13 +214,12 @@ std.debug.dumpHex(data);
// Output:
// 7fff5fbff8a0 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 00 01 02 Hello, World!...
// Dump to writer
var buf: [256]u8 = undefined;
var aw: std.io.Writer.Allocating = .init(allocator);
defer aw.deinit();
try std.debug.dumpHexFallible(&aw.writer, .no_color, data);
// Fallible dump to an existing terminal abstraction
try std.debug.dumpHexFallible(terminal, data);
```
`dumpHexFallible` accepts `std.Io.Terminal`, not a bare writer, because it uses terminal color/mode operations. Use `.no_color` when constructing a terminal for a non-terminal sink.
Output format:
- Address (lowercase hex)
- 16 bytes per line (uppercase hex)
@ -282,7 +266,7 @@ if (MyTrace.enabled) {
std.debug.print("trace: {}", .{trace});
```
In release builds (`enabled = false`), all trace operations are no-ops with zero size.
The predefined `std.debug.Trace` is enabled only in Debug mode. A custom `ConfigurableTrace` follows its explicit `is_enabled` argument; when disabled, its operations are no-ops and its storage is zero-sized.
## SafetyLock
@ -304,8 +288,8 @@ fn checkNotLocked() void {
}
```
- In Debug/ReleaseSafe: actively tracks lock state
- In ReleaseFast/ReleaseSmall: all methods are no-ops
- `SafetyLock` follows runtime-safety mode: active in Debug and ReleaseSafe, and a no-op in ReleaseFast and ReleaseSmall.
- `Trace` has the separate Debug-only default described above.
## Source Location
@ -327,7 +311,7 @@ const unknown = SourceLocation.invalid;
```zig
const Symbol = std.debug.Symbol;
// Symbol with resolved source location
// Resolved fields are optional because debug information may be incomplete.
const sym: Symbol = .{
.name = "myFunction",
.compile_unit_name = "main.zig",
@ -335,7 +319,7 @@ const sym: Symbol = .{
};
// Unknown symbol
const unknown: Symbol = .{}; // name = "???", compile_unit_name = "???"
const unknown: Symbol = .unknown; // all three fields are null
```
## Segfault Handling
@ -356,23 +340,9 @@ const enabled = std.debug.default_enable_segfault_handler;
**Note:** `maybeEnableSegfaultHandler()` is called automatically by the runtime if `std.options.enable_segfault_handler` is true.
## Thread Context
## CPU Context Unwinding
Platform-specific CPU register state for stack unwinding:
```zig
const ThreadContext = std.debug.ThreadContext;
var ctx: ThreadContext = undefined;
if (std.debug.getContext(&ctx)) {
// ctx now contains register state
std.debug.dumpStackTraceFromBase(&ctx, stderr);
}
// Copy context (handles internal pointers)
var ctx_copy: ThreadContext = undefined;
std.debug.copyContext(&original_ctx, &ctx_copy);
```
Signal handlers that already receive a native CPU context can pass it through `std.debug.StackUnwindOptions.context` to `captureCurrentStackTrace`, `writeCurrentStackTrace`, or `dumpCurrentStackTrace`. There are no public `std.debug.ThreadContext`, `getContext`, `copyContext`, or `dumpStackTraceFromBase` helpers in Zig 0.16; native context capture is target- and signal-handler-specific under `std.debug.cpu_context`.
## Valgrind Detection
@ -389,28 +359,22 @@ if (std.debug.inValgrind()) {
// Get debug info for current executable
const info = try std.debug.getSelfDebugInfo();
// Get symbol at address
const symbol = try info.getSymbolAtAddress(allocator, address);
defer if (symbol.source_location) |sl| allocator.free(sl.file_name);
std.debug.print("{s}:{d}: {s}\n", .{
symbol.source_location.?.file_name,
symbol.source_location.?.line,
symbol.name,
});
// Public methods are target-specific through SelfInfo. A broadly available
// operation is resolving the owning module name; returned storage is owned by
// SelfInfo rather than by the caller.
const module_name = try info.getModuleName(io, address);
std.debug.print("module: {s}\n", .{module_name});
```
## Constants
```zig
// Whether runtime safety checks are enabled
std.debug.runtime_safety // true in Debug/ReleaseSafe
std.debug.runtime_safety // deprecated; reflects stdlib mode, not necessarily the caller's module
// Whether platform can produce stack traces
std.debug.sys_can_stack_trace // false on WASM, MIPS, etc.
// Whether platform has ucontext_t
std.debug.have_ucontext
```
## Submodules
@ -420,8 +384,8 @@ std.debug.have_ucontext
| `std.debug.Dwarf` | DWARF debug info parser |
| `std.debug.Pdb` | Windows PDB debug info parser |
| `std.debug.SelfInfo` | Debug info for current executable |
| `std.debug.MemoryAccessor` | Safe memory access for unwinding |
| `std.debug.Coverage` | Code coverage support |
| `std.debug.cpu_context` | Target-specific native CPU context definitions |
## FullPanic
@ -447,24 +411,22 @@ fn myPanicFn(msg: []const u8, ret_addr: ?usize) noreturn {
For multi-line debug output without interleaving:
```zig
// Lock stderr and clear any progress indicators
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
// Lock stderr and clear any progress indicators. The returned object exposes
// a file writer and terminal; unlock performs the final flush automatically.
var lock_buf: [256]u8 = undefined;
const locked = std.debug.lockStderr(&lock_buf);
defer std.debug.unlockStderr();
// Safe to write multiple lines
var buf: [256]u8 = undefined;
var stderr = std.Io.File.stderr().writer(io, &buf);
try stderr.interface.writeAll("Line 1\n");
try stderr.interface.writeAll("Line 2\n");
try stderr.interface.flush();
try locked.file_writer.interface.writeAll("Line 1\n");
try locked.file_writer.interface.writeAll("Line 2\n");
```
Or with a writer:
The matching function is spelled `unlockStderr`:
```zig
var buf: [256]u8 = undefined;
const writer = std.debug.lockStderrWriter(&buf);
defer std.debug.unlockStderrWriter();
try writer.print("Complex output: {}\n", .{value});
const locked = std.debug.lockStderr(&buf);
defer std.debug.unlockStderr();
try locked.file_writer.interface.print("Complex output: {}\n", .{value});
```

View File

@ -13,8 +13,8 @@ const Color = enum { red, green, blue, yellow };
const ColorSet = std.enums.EnumSet(Color);
// Initialize
var colors = ColorSet.initEmpty();
var all = ColorSet.initFull();
var colors: ColorSet = .empty;
const all: ColorSet = .full;
// Struct-style init
var primary = ColorSet.init(.{
@ -106,7 +106,7 @@ if (map.get(.red)) |value| {
}
// Get with default
const value = map.getOrDefault(.blue, 0);
const value = map.get(.blue) orelse 0;
// Get pointer
if (map.getPtr(.red)) |ptr| {
@ -132,10 +132,10 @@ while (it.next()) |entry| {
std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
}
// Iterate keys only
var key_it = map.keyIterator();
while (key_it.next()) |key| {
std.debug.print("{}\n", .{key});
// Iterate keys only by ignoring each entry's value pointer
var key_it = map.iterator();
while (key_it.next()) |entry| {
std.debug.print("{}\n", .{entry.key});
}
```
@ -171,8 +171,11 @@ for (std.enums.values(Color)) |color| {
std.debug.print("{}: {}\n", .{ color, rgb.get(color) });
}
// Direct slice access
const slice = rgb.values; // [3]u32
// Public sequential access uses the iterator.
var rgb_it = rgb.iterator();
while (rgb_it.next()) |entry| {
std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
}
```
## EnumIndexer
@ -191,7 +194,7 @@ const count = Indexer.count; // 3
```zig
// Get all values as slice
const colors = std.enums.values(Color); // [3]Color
const colors = std.enums.values(Color); // []const Color
// Safe tag name (works with non-exhaustive)
const name = std.enums.tagName(Color, .red); // "red" or null
@ -244,7 +247,7 @@ fn canAccess(user: User, required: Permissions) bool {
pub fn main() void {
const admin = User{
.name = "admin",
.perms = Permissions.initFull(),
.perms = .full,
};
const reader = User{
@ -293,8 +296,8 @@ pub fn main() void {
## Notes
- `EnumSet`: Bit-backed, use for presence tracking
- `EnumMap`: Sparse, only stores present values
- `EnumMap`: Fixed-size dense value storage plus a presence bitset
- `EnumArray`: Dense, all values always present
- All are fixed-size, zero-allocation, copyable by value
- Use `std.StaticBitSet` for non-enum integer sets
- Works with non-exhaustive enums (explicit fields only)
- Exhaustive sparse enums are remapped densely. For non-exhaustive enums, index-based containers span the representable tag range rather than only explicit fields, which can make them impractically large; check each container's `EnumIndexer` behavior before use.

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.Formatter` is renamed to `std.fmt.Alt`.
- `std.fmt.FormatOptions` is renamed to `std.fmt.Options`.
- `std.fmt.bufPrintZ` is renamed to `std.fmt.bufPrintSentinel`.
- `std.fmt.bufPrintZ` remains as a deprecated compatibility wrapper; use `std.fmt.bufPrintSentinel`.
- The `{D}` duration specifier was removed; format `std.Io.Duration` with `{f}`.
```zig
@ -38,13 +38,13 @@ pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
## Format String Syntax
Full syntax: `{[arg]:[fill][alignment][width][.precision][specifier]}`
Full syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}`
### Components
| Component | Description | Example |
|-----------|-------------|---------|
| `arg` | Argument index or name | `{0}`, `{name}` |
| `argument` | Argument index or bracketed name | `{0}`, `{[name]s}` |
| `fill` | Padding character | `{:0>5}` uses `0` |
| `alignment` | `<` left, `^` center, `>` right | `{:<10}` |
| `width` | Minimum field width | `{:10}` |
@ -63,14 +63,14 @@ std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a"
### Named Arguments
```zig
std.debug.print("{name}: {value}\n", .{ .name = "x", .value = 42 });
std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 });
```
### Runtime Width/Precision
```zig
std.debug.print("{d:[width]}\n", .{ .width = @as(usize, 8), 42 });
std.debug.print("{d:.[prec]}\n", .{ .prec = @as(usize, 2), 3.14159 });
std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = @as(usize, 8) });
std.debug.print("{[value]d:.[prec]}\n", .{ .value = 3.14159, .prec = @as(usize, 2) });
```
### Escape Braces
@ -81,7 +81,9 @@ std.debug.print("{{literal}}\n", .{}); // "{literal}"
## Format Specifiers
### Type Specifiers
### Selected Type Specifiers
This table lists common specifiers; it is not a complete substitute for `std.Io.Writer`'s type-specific formatting rules.
| Specifier | Types | Output |
|-----------|-------|--------|
@ -171,7 +173,8 @@ const e = try std.fmt.parseInt(i32, "0b101", 0); // 5 (binary)
const f = try std.fmt.parseInt(i32, "0o17", 0); // 15 (octal)
const g = try std.fmt.parseInt(i32, "42", 0); // 42 (decimal)
// Underscores allowed between digits
// Underscores are ignored within the digit sequence. Leading/trailing
// underscores and underscores immediately after a base prefix are invalid.
const h = try std.fmt.parseInt(u32, "1_000_000", 10); // 1000000
const i = try std.fmt.parseInt(u32, "0xff_ff", 0); // 65535
```
@ -306,19 +309,19 @@ const result = try std.fmt.bufPrint(&buf, "Hello {s}!", .{"world"});
**Errors:**
- `error.NoSpaceLeft` - Buffer too small
### bufPrintZ
### bufPrintSentinel
Format into buffer with null terminator.
```zig
var buf: [256]u8 = undefined;
const result = try std.fmt.bufPrintZ(&buf, "Hello {s}!", .{"world"});
const result = try std.fmt.bufPrintSentinel(&buf, "Hello {s}!", .{"world"}, 0);
// result is [:0]u8 = "Hello world!" (null-terminated)
```
### count
Count characters needed for format (without allocating).
Count output bytes needed for the format (without allocating).
```zig
const len = std.fmt.count("Value: {d}, Name: {s}", .{ 42, "test" });

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.
The snippets below are focused fragments. They assume `const std =
@import("std")`, a caller-supplied `io: std.Io`, a suitable allocator, and any
named application values such as `max_size`, `from`, and `to`.
## Core Types
```zig
@ -41,7 +45,10 @@ const file = try std.Io.Dir.cwd().createFile(io, "out.txt", .{});
defer file.close(io);
```
Common options remain conceptually similar: truncate, exclusive create, read access, mode/permissions, and locking where supported.
With default options, `createFile` truncates an existing regular file. Use the
exclusive-create option when overwriting must fail. Other options cover read
access, `permissions`, and locking where supported. File access mode is an
`openFile` option, not the name of the creation-permissions field.
## Reading Files
@ -68,6 +75,9 @@ defer allocator.free(bytes);
```
The limit uses `std.Io.Limit`. Hitting the limit returns `error.StreamTooLong`.
`readFileAlloc` creates a file reader internally and reads until the supplied
limit. Use an explicit `File.Reader` when streaming, reusing buffers, or
controlling incremental consumption.
### Read To End From Existing File
@ -130,14 +140,16 @@ if (try stdin_reader.interface.takeDelimiter('\n')) |line| {
## Directories
```zig
var dir = try std.Io.Dir.cwd().openDir(io, "assets", .{});
var dir = try std.Io.Dir.cwd().openDir(io, "assets", .{ .iterate = true });
defer dir.close(io);
try std.Io.Dir.cwd().createDir(io, "new-dir", .default_dir);
try std.Io.Dir.cwd().createDirPath(io, "path/to/nested");
```
Use `openDir` options for iteration/access/no-follow behavior as needed.
Both walking APIs require a directory opened with `.iterate = true`; iterating
without that capability is illegal behavior. Use other `openDir` options for
access/no-follow behavior as needed.
## Walking
@ -190,7 +202,10 @@ try file.setTimestamps(io, .{
`std.Io.Dir.path` / `std.fs.path` functions handle Windows paths more consistently in 0.16.
`relative`, `relativeWindows`, and `relativePosix` are pure: pass the current directory and optional environment map instead of letting the function query the OS.
The relative-path helpers are pure and receive the current directory rather
than querying the OS. `relative` and `relativeWindows` also accept an optional
environment map for Windows per-drive current-directory resolution;
`relativePosix` has a distinct signature without that map.
```zig
const cwd_path = try std.process.currentPathAlloc(io, allocator);
@ -202,7 +217,10 @@ defer allocator.free(rel);
## Atomic Files
Use `std.Io.File.Atomic` or directory atomic helpers instead of hand-rolled random temporary names. The 0.16 implementation is routed through `std.Io`, including entropy.
Obtain a `std.Io.File.Atomic` value through `Dir.createFileAtomic`, then use its
public cleanup/materialization lifecycle. Do not construct `File.Atomic`
directly or hand-roll random temporary names; the helper routes entropy and
filesystem work through `std.Io`.
## Absolute Operations

View File

@ -68,7 +68,7 @@ const h7 = hash.CityHash64.hash("hello world");
### Streaming/Incremental Hashing
All hashers support incremental updates for large or streaming data:
Hashers that expose `init`/`update`/`final` support incremental input. Some checksum types have a simpler state shape, and some algorithms expose only one-shot helpers; check the selected type rather than assuming a uniform interface.
```zig
const std = @import("std");
@ -101,7 +101,7 @@ const h3 = fnv.final();
## Auto-Hashing (Generic Types)
`std.hash.autoHash` automatically hashes any Zig type:
`std.hash.autoHash` hashes eligible Zig value types. It intentionally rejects ambiguous categories such as slices unless the caller selects an explicit pointer strategy, and some type categories are unsupported.
```zig
const std = @import("std");
@ -117,7 +117,7 @@ fn hashPoint(p: Point) u64 {
return hasher.final();
}
// Works with any hashable type
// Works with types accepted by autoHash; slices/pointers require deliberate handling.
fn hashAny(value: anytype) u64 {
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHash(&hasher, value);
@ -141,7 +141,7 @@ const Strategy = std.hash.Strategy;
var hasher = std.hash.Wyhash.init(0);
const data: []const u8 = "hello";
// Shallow: hash pointer address only (default for autoHash)
// Shallow slice hashing includes its pointer and length, not its contents.
std.hash.autoHashStrat(&hasher, data, .Shallow);
// Deep: follow pointer, hash contents (one level)
@ -153,7 +153,7 @@ std.hash.autoHashStrat(&hasher, data, .DeepRecursive);
| Strategy | Behavior |
|----------|----------|
| `.Shallow` | Hash pointer address, not contents |
| `.Shallow` | Hash a one-item pointer's address; for a slice, hash pointer and length, not elements |
| `.Deep` | Follow pointer one level, hash contents |
| `.DeepRecursive` | Follow all pointers, hash all contents |
@ -240,14 +240,14 @@ const Adler32 = std.hash.Adler32;
const checksum = Adler32.hash("data");
// Streaming
var adler = Adler32.init();
var adler: Adler32 = .{};
adler.update("data");
const result = adler.final();
const result = adler.adler;
```
## Using with HashMap
HashMap uses `std.hash.autoHash` by default:
`AutoHashMap` uses the stdlib's automatic hashing context. `StringHashMap` uses a string-specific context, while custom `HashMap` users provide their own context:
```zig
const std = @import("std");
@ -409,8 +409,13 @@ const std = @import("std");
// Requires 128-bit key
const key: [16]u8 = .{0} ** 16;
const h64 = std.hash.SipHash64(2, 4).hash(&key, "data");
const h128 = std.hash.SipHash128(2, 4).hash(&key, "data");
var sip64 = std.hash.SipHash64(2, 4).init(&key);
sip64.update("data");
const h64 = sip64.finalInt();
var sip128 = std.hash.SipHash128(2, 4).init(&key);
sip128.update("data");
const h128 = sip128.finalInt();
// Default parameters (2-4 rounds)
const SipHash = std.hash.SipHash64(2, 4);
@ -440,17 +445,19 @@ fn hashPair(comptime T: type, a: T, b: T) u64 {
### File Checksum
```zig
fn checksumFile(path: []const u8) !u32 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
fn checksumFile(io: std.Io, path: []const u8) !u32 {
const file = try std.Io.Dir.cwd().openFile(io, path, .{});
defer file.close(io);
var crc = std.hash.Crc32.init();
var buf: [4096]u8 = undefined;
var crc: std.hash.Crc32 = .init();
var reader_buf: [4096]u8 = undefined;
var chunk: [4096]u8 = undefined;
var reader = file.reader(io, &reader_buf);
while (true) {
const n = try file.read(&buf);
const n = try reader.interface.readSliceShort(&chunk);
if (n == 0) break;
crc.update(buf[0..n]);
crc.update(chunk[0..n]);
}
return crc.final();
@ -460,15 +467,13 @@ fn checksumFile(path: []const u8) !u32 {
### Bloom Filter Hash
```zig
fn bloomHashes(data: []const u8, k: usize) []u64 {
var hashes: [16]u64 = undefined;
fn bloomHashes(data: []const u8, hashes: []u64) void {
const h1 = std.hash.Wyhash.hash(0, data);
const h2 = std.hash.Wyhash.hash(h1, data);
for (0..k) |i| {
hashes[i] = h1 +% @as(u64, i) *% h2;
for (hashes, 0..) |*hash, i| {
hash.* = h1 +% @as(u64, i) *% h2;
}
return hashes[0..k];
}
```

View File

@ -13,9 +13,10 @@ std.AutoHashMapUnmanaged(KeyType, ValueType) // no stored allocator
std.StringHashMap(ValueType)
std.StringHashMapUnmanaged(ValueType)
// ArrayHashMap - preserves insertion order, fast iteration
std.ArrayHashMap(K, V, Context, store_hash)
std.StringArrayHashMap(V)
// ArrayHashMap - Zig 0.16 unmanaged ordered maps
std.array_hash_map.Auto(K, V)
std.array_hash_map.String(V)
std.array_hash_map.Custom(K, V, Context, store_hash)
```
## AutoHashMap Usage
@ -54,7 +55,7 @@ const n = map.count();
## Unmanaged Variant
```zig
// No stored allocator - pass to each method
// No stored allocator - pass it to operations that allocate or release memory
var map: std.AutoHashMapUnmanaged(u32, []const u8) = .empty;
defer map.deinit(allocator);
@ -98,12 +99,16 @@ while (iter.next()) |entry| {
}
// Keys only
for (map.keys()) |key| { }
var keys = map.keyIterator();
while (keys.next()) |key_ptr| { _ = key_ptr.*; }
// Values only
for (map.values()) |value| { }
var values = map.valueIterator();
while (values.next()) |value_ptr| { _ = value_ptr.*; }
```
Any modification invalidates iterators. Growth or rehashing can invalidate returned key/value pointers; removal immediately invalidates the removed entry's pointers.
## Capacity
```zig
@ -138,11 +143,11 @@ var map = std.HashMap(MyKey, Value, Context, 80).initContext(allocator, context)
Preserves insertion order, supports indexed access:
```zig
var map = std.StringArrayHashMap(i32).init(allocator);
defer map.deinit();
var map: std.array_hash_map.String(i32) = .empty;
defer map.deinit(allocator);
try map.put("b", 2);
try map.put("a", 1);
try map.put(allocator, "b", 2);
try map.put(allocator, "a", 1);
// Iterate in insertion order: "b", "a"
for (map.keys(), map.values()) |k, v| { }
@ -152,10 +157,10 @@ const key = map.keys()[0]; // "b"
const val = map.values()[0]; // 2
// Swap remove (O(1) but changes order)
map.swapRemove("b");
_ = map.swapRemove("b");
// Ordered remove (O(n) but preserves order)
map.orderedRemove("a");
_ = map.orderedRemove("a");
```
## Common Patterns
@ -174,8 +179,15 @@ for (words) |word| {
// Cache with owned keys
var cache = std.StringHashMap(Data).init(allocator);
defer cache.deinit();
defer {
var owned_keys = cache.keyIterator();
while (owned_keys.next()) |key_ptr| allocator.free(key_ptr.*);
}
// When inserting, dupe the key if needed:
const key_copy = try allocator.dupe(u8, external_key);
errdefer allocator.free(key_copy);
try cache.put(key_copy, data);
```
On removal, free the duplicated key returned by `fetchRemove`. Before clearing or deinitializing the map, iterate and free every remaining owned key as above; the map does not own string storage automatically.

View File

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

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
@ -148,6 +148,8 @@ const header = try reader.takeStruct(Header, .little);
const leb = try reader.takeLeb128(u64);
```
`takeStruct` reads an extern/packed memory representation through the reader buffer; use a layout with a defined byte representation, ensure the reader can supply the full size, and do not treat a native-layout struct as a portable wire format.
## File Integration
Use `std.Io.Dir` and `std.Io.File`.
@ -206,11 +208,14 @@ Use `io.randomSecure` for fresh secure entropy with error reporting.
## Time
The release notes map:
Timestamp reads now require an explicit clock choice:
- `std.time.Instant` -> `std.Io.Timestamp`
- `std.time.Timer` -> `std.Io.Timestamp`
- `std.time.timestamp` -> `std.Io.Timestamp.now`
```zig
const now = std.Io.Timestamp.now(io, .real);
const elapsed_mark = std.Io.Timestamp.now(io, .awake);
```
`Timestamp` is not a one-for-one replacement for every old `Instant`/`Timer` behavior; choose `.real` versus an appropriate monotonic clock such as `.awake`, and build elapsed-time helpers around that choice.
Use a shared application helper when common timestamp reads require consistent clock selection or conversion semantics.
@ -220,9 +225,9 @@ Use a shared application helper when common timestamp reads require consistent c
- `io.async(...)`
- `std.Io.Group`
- `std.Io.Select`
- `std.Io.Select(U)` where `U` is the tagged union of possible results
- `std.Io.Batch`
- `std.Io.Queue(T)`
- `std.Io.Queue(Elem)`, initialized with caller-provided typed element storage
Cancelation guidance:
@ -250,8 +255,8 @@ Blocking sync moved to `std.Io` equivalents:
| `std.Thread.Semaphore` | `std.Io.Semaphore` |
| `std.Thread.RwLock` | `std.Io.RwLock` |
| `std.Thread.ResetEvent` | `std.Io.Event` |
| `std.Thread.WaitGroup` | `std.Io.Group` |
| `std.Thread.Futex` | `std.Io.Futex` |
| `std.Thread.WaitGroup` | Conceptually `std.Io.Group`; submit tasks, then await or cancel the group |
| `std.Thread.Futex` | `io.futexWait*` / `io.futexWake`; waits have cancelable and uncancelable forms |
```zig
try mutex.lock(io);

View File

@ -128,7 +128,7 @@ defer allocator.free(json);
// To writer
var buf: [4096]u8 = undefined;
var writer = std.fs.File.stdout().writer(&buf);
var writer = std.Io.File.stdout().writer(io, &buf);
try std.json.Stringify.value(config, .{}, &writer.interface);
try writer.interface.flush();
```
@ -184,22 +184,23 @@ pub const Value = union(enum) {
float: f64,
number_string: []const u8, // unparsed number
string: []const u8,
array: Array, // std.ArrayList(Value)
object: ObjectMap, // StringArrayHashMap(Value)
array: Array, // std.array_list.Managed(Value)
object: ObjectMap, // std.array_hash_map.String(Value)
};
```
### Building Values Manually
```zig
var obj = std.json.ObjectMap.init(allocator);
try obj.put("name", .{ .string = "test" });
try obj.put("count", .{ .integer = 42 });
var obj: std.json.ObjectMap = .empty;
defer obj.deinit(allocator);
try obj.put(allocator, "name", .{ .string = "test" });
try obj.put(allocator, "count", .{ .integer = 42 });
var arr = std.json.Array.init(allocator);
try arr.append(.{ .integer = 1 });
try arr.append(.{ .integer = 2 });
try obj.put("items", .{ .array = arr });
try obj.put(allocator, "items", .{ .array = arr });
const value = std.json.Value{ .object = obj };
```
@ -283,7 +284,7 @@ const Point = struct {
Build JSON incrementally:
```zig
var out: std.io.Writer.Allocating = .init(allocator);
var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit();
var jw: std.json.Stringify = .{
@ -345,14 +346,16 @@ const Config = struct {
debug: bool = false,
};
fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config {
const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
fn loadConfig(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !Config {
const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
error.FileNotFound => return Config{}, // defaults
else => return err,
};
defer file.close();
defer file.close(io);
const content = try file.readToEndAlloc(allocator, 1024 * 1024);
var read_buf: [4096]u8 = undefined;
var reader = file.reader(io, &read_buf);
const content = try reader.interface.allocRemaining(allocator, .limited(1024 * 1024));
defer allocator.free(content);
const parsed = try std.json.parseFromSlice(Config, allocator, content, .{
@ -360,7 +363,8 @@ fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config {
});
defer parsed.deinit();
// Copy strings to owned memory since parsed will be freed
// Copy strings to owned memory since parsed and the source buffer are
// about to be freed. The caller owns and must free the returned host.
return Config{
.host = try allocator.dupe(u8, parsed.value.host),
.port = parsed.value.port,

View File

@ -5,7 +5,7 @@ Intrusive linked lists for O(1) insertion/removal. Nodes are embedded in user st
## When to Use
- O(1) insertion/removal anywhere in list
- Elements that need to be in multiple lists
- Elements that need to be in multiple lists (embed one distinct node per simultaneous list membership)
- Preallocated/arena-allocated nodes
- No allocation on insert (nodes already exist)
@ -27,12 +27,13 @@ var list: std.DoublyLinkedList = .{};
var a: Item = .{ .data = 1 };
var b: Item = .{ .data = 2 };
var c: Item = .{ .data = 3 };
var d: Item = .{ .data = 4 };
// Insert
list.append(&a.node); // add to end
list.prepend(&b.node); // add to start
list.insertAfter(&a.node, &c.node); // insert c after a
list.insertBefore(&a.node, &c.node); // insert c before a
list.insertBefore(&a.node, &d.node); // insert a different unlinked node before a
// Remove
list.remove(&a.node); // O(1) remove specific node
@ -46,20 +47,26 @@ if (list.first) |node| {
}
// Traverse forward
var it = list.first;
while (it) |node| : (it = node.next) {
{
var it = list.first;
while (it) |node| : (it = node.next) {
const item: *Item = @fieldParentPtr("node", node);
// use item.data
}
}
// Traverse backward
var it = list.last;
while (it) |node| : (it = node.prev) {
{
var it = list.last;
while (it) |node| : (it = node.prev) {
const item: *Item = @fieldParentPtr("node", node);
// use item.data
}
}
// Concatenate (moves all from list2 to end of list1)
var list1: std.DoublyLinkedList = .{};
var list2: std.DoublyLinkedList = .{};
list1.concatByMoving(&list2);
// Length (O(n) - consider tracking separately)
@ -85,10 +92,11 @@ var b: Item = .{ .data = 2 };
list.prepend(&a.node); // add to front
a.node.insertAfter(&b.node); // insert b after a
// Remove
const first = list.popFirst(); // remove and return first
_ = a.node.removeNext(); // remove node after a
list.remove(&b.node); // O(n) - must find predecessor
// Remove b after a, then reinsert it so the remaining operations are valid.
_ = a.node.removeNext(); // removes and returns b
list.prepend(&b.node);
list.remove(&a.node); // O(n) - must find predecessor
const first = list.popFirst(); // removes and returns b
// Traverse (forward only)
var it = list.first;
@ -122,9 +130,11 @@ node.insertAfter(new_node)
node.removeNext() // ?*Node - removes and returns next
node.findLast() // *Node
node.countChildren() // usize
node.reverse(&optional_ptr)
std.SinglyLinkedList.Node.reverse(&optional_ptr)
```
Insertion requires a node that is not currently linked into a list, and removal requires membership in that exact list. Removal updates surrounding links and list heads but does not clear every stale `prev`/`next` field on the removed node; reinitializing or deliberately reinserting the node establishes its next valid state.
## Common Pattern: LRU Cache
```zig

View File

@ -34,10 +34,12 @@ pub fn main() void {
| Level | Build Mode Default | Purpose |
|-------|-------------------|---------|
| `.err` | Always shown | Something went wrong |
| `.warn` | Always shown | Uncertain if wrong, worth investigating |
| `.info` | Debug + Release | General program state |
| `.debug` | Debug only | Messages only useful for debugging |
| `.err` | Enabled by the default configuration | Something went wrong |
| `.warn` | Enabled by the default configuration | Uncertain if wrong, worth investigating |
| `.info` | Enabled in common default modes | General program state |
| `.debug` | Commonly Debug-only by default | Messages only useful for debugging |
Compile-time level/scope filtering is controlled by `std.options`; a custom `logFn` also decides what its sink actually emits.
Default level by build mode:
- **Debug**: `.debug` (all messages)
@ -122,14 +124,10 @@ fn myLogFn(
const level_txt = comptime level.asText();
const prefix = "[" ++ level_txt ++ "] (" ++ scope_prefix ++ "): ";
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
const io = applicationIo(); // Application-owned accessor for std.Io.
var buf: [64]u8 = undefined;
var stderr = std.Io.File.stderr().writer(io, &buf);
stderr.interface.print(prefix ++ format ++ "\n", args) catch return;
stderr.interface.flush() catch return;
const locked = std.debug.lockStderr(&buf);
defer std.debug.unlockStderr(); // flushes the returned writer
locked.file_writer.interface.print(prefix ++ format ++ "\n", args) catch return;
}
```
@ -148,7 +146,7 @@ fn process() void {
}
// For default scope
if (std.log.defaultLogEnabled(.debug)) {
if (std.log.logEnabled(.debug, .default)) {
std.log.debug("Debug message", .{});
}
}
@ -177,9 +175,15 @@ fn myLogFn(
comptime format: []const u8,
args: anytype,
) void {
// Add timestamp, then forward to default
std.debug.print("[{d}] ", .{applicationTimestampNow().toNanoseconds()});
std.log.defaultLog(level, scope, format, args);
// Emit the timestamp and message under one stderr lock so concurrent
// records cannot split the prefix from the message.
var buf: [128]u8 = undefined;
const locked = std.debug.lockStderr(&buf);
defer std.debug.unlockStderr();
locked.file_writer.interface.print(
"[{d}] " ++ format ++ "\n",
.{applicationTimestampNow().toNanoseconds()} ++ args,
) catch return;
}
```
@ -243,8 +247,10 @@ fn fileLogFn(
args: anytype,
) void {
const io = applicationIo();
const file = std.Io.Dir.cwd().openFile(io, "app.log", .{ .mode = .write_only }) catch return;
defer file.close(io);
// Returns a long-lived handle opened during startup with an explicit
// create/append policy. Plain write_only would require an existing file
// and reopening each record could overwrite earlier data.
const file = applicationLogFile();
var buf: [256]u8 = undefined;
var writer = file.writer(io, &buf);
@ -257,3 +263,5 @@ fn fileLogFn(
w.flush() catch return;
}
```
If multiple tasks share this sink, serialize the complete record and keep the file/writer alive; opening, seeking, writing, and closing separately for each record is not atomic.

View File

@ -108,7 +108,7 @@ const log10_int_val = std.math.log10_int(1000); // 3
```zig
// Powers
const pow_val = std.math.pow(f64, 2.0, 3.0); // 2^3 = 8.0
const powi_val = std.math.powi(f64, 2.0, 3); // 2^3 (integer exponent)
const powi_val = try std.math.powi(i32, 2, 3); // checked integer power
// Roots
const sqrt_val = std.math.sqrt(@as(f64, 16.0)); // 4.0
@ -202,11 +202,13 @@ const negated = std.math.negate(@as(i8, -128)) catch |err| {
};
// Shift with overflow check
const shifted = std.math.shlExact(u8, 1, 8) catch |err| {
return err; // Overflow: 1 << 8 doesn't fit in u8
const shifted = std.math.shlExact(u8, 2, 7) catch |err| {
return err; // Overflow: 2 << 7 doesn't fit in u8
};
```
The shift amount has type `std.math.Log2Int(T)`, so an out-of-range count such as 8 for `u8` is rejected before `shlExact` can report value overflow.
## Division Functions
```zig
@ -362,13 +364,17 @@ defer b.deinit();
// Arithmetic
try a.add(&a, &b);
try a.mul(&a, &b);
try a.div(&q, &r, &a, &b); // quotient and remainder
var q = try Managed.init(allocator);
defer q.deinit();
var r = try Managed.init(allocator);
defer r.deinit();
try q.divTrunc(&r, &a, &b); // quotient in q, remainder in r
// Comparison
const ord = a.order(b); // .lt, .eq, or .gt
// Convert to primitive (if fits)
const val = a.to(i128) catch |err| {
const val = a.toInt(i128) catch |err| {
// Value doesn't fit in i128
return err;
};
@ -386,8 +392,9 @@ try c.setString(10, "123456789012345678901234567890");
const gcd_val = std.math.gcd(@as(u32, 48), @as(u32, 18)); // 6
// Least common multiple
const lcm_val = try std.math.lcm(@as(u32, 4), @as(u32, 6)); // 12
// Returns error.Overflow if result doesn't fit
const lcm_val = std.math.lcm(@as(u32, 4), @as(u32, 6)); // 12
// lcm is not an error union; intermediate multiplication follows the selected
// integer overflow/safety behavior.
```
## Gamma Functions
@ -402,9 +409,9 @@ const lg = std.math.lgamma(f64, 100.0);
## Notes
- Most functions work with `f16`, `f32`, `f64`, `f80`, `f128` and `comptime_float`
- Floating-point support varies by function and target; check each signature rather than assuming every float width and `comptime_float` are accepted
- Many functions support SIMD vectors: `sin(@Vector(4, f32){...})`
- Integer overflow-checking functions return `error.Overflow` or `error.DivisionByZero`
- Integer helpers document their own error sets; `error.Overflow` and `error.DivisionByZero` are common examples, not a universal pair
- Hardware instructions used when available (`@sin`, `@cos`, `@sqrt`, etc.)
- `approxEqAbs` for values near zero, `approxEqRel` for larger values
- Complex number operations available in `std.math.complex`

View File

@ -22,7 +22,9 @@ std.mem.cutPrefix(u8, path, "assets/")
std.mem.cutSuffix(u8, file_name, ".zig")
```
Older examples below may use 0.15 `indexOf` names; translate them to the `find`/`cut` APIs in Zig 0.16.
The examples below use the Zig 0.16 `find`/`cut` names rather than the older `indexOf` family.
The legacy `copyForwards`/`copyBackwards` helpers are deprecated; use `@memmove` when source and destination may overlap (or `@memcpy` when they provably do not).
## Slice Comparison & Search
@ -101,10 +103,12 @@ defer allocator.free(joined); // "a, b, c"
// Join with null terminator
const joinedZ = try std.mem.joinZ(allocator, "/", &.{ "path", "to", "file" });
defer allocator.free(joinedZ);
// [:0]u8 = "path/to/file"
// Concatenate without separator
const concatted = try std.mem.concat(allocator, u8, &.{ "hello", " ", "world" });
defer allocator.free(concatted);
// "hello world"
```
@ -125,7 +129,8 @@ std.mem.trim(u8, "\n\thello\n\t", " \t\n")
// In-place replace (returns count)
var buf: [100]u8 = undefined;
const count = std.mem.replace(u8, "hello", "l", "L", &buf);
// buf contains "heLLo", count = 2
const output_len = std.mem.replacementSize(u8, "hello", "l", "L");
const replaced = buf[0..output_len]; // "heLLo"; count = 2
// Allocate new slice
const result = try std.mem.replaceOwned(u8, allocator, "hello", "l", "L");
@ -148,18 +153,20 @@ const bytes = std.mem.asBytes(&val); // *const [4]u8
const byte_copy = std.mem.toBytes(val); // [4]u8 (copy)
// Bytes to value
const bytes = [_]u8{ 0xEF, 0xBE, 0xAD, 0xDE };
const ptr = std.mem.bytesAsValue(u32, &bytes); // *const u32
const val = std.mem.bytesToValue(u32, &bytes); // u32 (copy)
const word_bytes: [4]u8 align(@alignOf(u32)) = .{ 0xEF, 0xBE, 0xAD, 0xDE };
const ptr = std.mem.bytesAsValue(u32, &word_bytes); // native-memory view retaining input alignment
const word = std.mem.bytesToValue(u32, &word_bytes); // u32 copy
// Slice conversions
const u16_slice = [_]u16{ 0x0102, 0x0304 };
const u8_slice = std.mem.sliceAsBytes(&u16_slice); // []const u8
const u8_data = [_]u8{ 1, 0, 2, 0, 3, 0, 4, 0 };
const u8_data: [8]u8 align(@alignOf(u16)) = .{ 1, 0, 2, 0, 3, 0, 4, 0 };
const u16_view = std.mem.bytesAsSlice(u16, &u8_data); // []const u16
```
These are native-memory views: alignment follows the input pointer and integer byte order follows the target. Use `readInt`/`writeInt` for portable byte-order decoding.
## Alignment
```zig
@ -182,8 +189,9 @@ std.mem.isValidAlign(3) // false
const ptr: [*]u8 = @ptrFromInt(0x123);
const aligned = std.mem.alignPointer(ptr, 0x100); // ?[*]u8 = 0x200
// Find aligned slice within bytes
const aligned_slice = std.mem.alignInBytes(bytes, 16); // ?[]align(16) u8
// Find aligned mutable slice within bytes
var storage: [128]u8 = undefined;
const aligned_slice = std.mem.alignInBytes(&storage, 16); // ?[]align(16) u8
```
## Alignment Type
@ -208,14 +216,14 @@ const ok = align_val.check(0x100); // true if aligned
```zig
// To/from native endianness
const native = std.mem.littleToNative(u32, 0x12345678);
const native = std.mem.bigToNative(u32, 0x12345678);
const native_from_little = std.mem.littleToNative(u32, 0x12345678);
const native_from_big = std.mem.bigToNative(u32, 0x12345678);
const little = std.mem.nativeToLittle(u32, native_val);
const big = std.mem.nativeToBig(u32, native_val);
// General conversion
const val = std.mem.toNative(u32, x, .little); // from little to native
const val = std.mem.nativeTo(u32, x, .big); // from native to big
const decoded = std.mem.toNative(u32, x, .little); // from little to native
const encoded = std.mem.nativeTo(u32, x, .big); // from native to big
// Byte swap all fields in struct
std.mem.byteSwapAllFields(MyStruct, &my_struct);
@ -255,6 +263,8 @@ const partial = std.mem.zeroInit(MyStruct, .{
});
```
Prefer explicit field initialization. `zeroes` is mainly for types whose all-zero state is intentionally valid (often C interop); in review, verify that every enum, pointer-like field, invariant, and nested type has a meaningful zero state.
## Min/Max
```zig
@ -264,9 +274,9 @@ std.mem.min(i32, &slice) // 1
std.mem.max(i32, &slice) // 5
std.mem.minMax(i32, &slice) // .{ 1, 5 }
std.mem.indexOfMin(i32, &slice) // 1
std.mem.indexOfMax(i32, &slice) // 4
std.mem.indexOfMinMax(i32, &slice) // .{ 1, 4 }
std.mem.findMin(i32, &slice) // 1
std.mem.findMax(i32, &slice) // 4
std.mem.findMinMax(i32, &slice) // .{ 1, 4 }
```
## Reverse & Rotate
@ -300,7 +310,7 @@ const len = std.mem.len(c_string); // length of null-terminated string
const slice = std.mem.span(c_string);
// Index of first difference
std.mem.indexOfDiff(u8, "hello", "helps") // ?usize = 3
std.mem.findDiff(u8, "hello", "helps") // ?usize = 3
// Collapse repeated elements
var data = "aabbcc".*;

View File

@ -7,7 +7,7 @@ Comptime type introspection and manipulation utilities. Essential for generic pr
| Function | Purpose |
|----------|---------|
| `stringToEnum(T, str)` | Convert string to enum variant |
| `fields(T)` | Get struct/union/enum/error fields |
| `fields(T)` | Get struct/union/enum or concrete error-set fields |
| `fieldNames(T)` | Get field names as string slice |
| `fieldInfo(T, field)` | Get info for specific field |
| `fieldIndex(T, name)` | Get field index by name |
@ -71,6 +71,8 @@ const MyError = error{ NotFound, Timeout };
const error_fields = std.meta.fields(MyError);
```
`fields` cannot enumerate the global `anyerror` set; use it only with a concrete error set whose members are known.
### Get Field Names
```zig
@ -153,6 +155,8 @@ inline for (std.meta.tags(PointField)) |field| {
}
```
For a tagged union, `FieldEnum` reuses the existing tag enum only when field names match in order and the tag values are consecutive from zero; otherwise it constructs a separate enum.
For tagged unions, returns the existing tag type if compatible.
### DeclEnum - Generate Enum from Declarations
@ -170,8 +174,8 @@ const ApiMethod = std.meta.DeclEnum(Api);
### Int/Float Type Construction
```zig
const U24 = std.meta.Int(.unsigned, 24); // u24
const I7 = std.meta.Int(.signed, 7); // i7
const U24 = @Int(.unsigned, 24); // u24
const I7 = @Int(.signed, 7); // i7
const F32 = std.meta.Float(32); // f32
const F16 = std.meta.Float(16); // f16
```
@ -180,7 +184,7 @@ const F16 = std.meta.Float(16); // f16
```zig
// From type array
const T1 = std.meta.Tuple(&.{ u32, f32, bool });
const T1 = std.meta.Tuple(&.{ u32, f32, bool }); // deprecated compatibility helper
// Equivalent to: struct { u32, f32, bool }
// From function signature
@ -213,9 +217,9 @@ std.meta.Elem(?[*]u8) // u8 (through optional)
### Sentinel
```zig
std.meta.sentinel([:0]u8) // @as(u8, 0)
std.meta.sentinel([*:0]u8) // @as(u8, 0)
std.meta.sentinel([5:0]u8) // @as(u8, 0)
const slice_sentinel = std.meta.sentinel([:0]u8).?; // @as(u8, 0)
const ptr_sentinel = std.meta.sentinel([*:0]u8).?; // @as(u8, 0)
const array_sentinel = std.meta.sentinel([5:0]u8).?; // @as(u8, 0)
std.meta.sentinel([]u8) // null
std.meta.sentinel([5]u8) // null
```
@ -259,6 +263,8 @@ std.meta.eql(&p1, &p1) // true
**Supported types:** structs, arrays, vectors, optionals, error unions, tagged unions, primitives.
Non-packed untagged unions cannot be compared generically because there is no active tag to select a field. Packed unions are the exception handled as their packed representation.
**Not supported:** untagged unions (compile error).
## Type Queries
@ -333,13 +339,17 @@ const decls = std.meta.declarations(S);
## Error Handling
```zig
// Check if value is error (deprecated: use std.enums.fromInt)
// Inspect an error union with ordinary error-union syntax.
const result = std.math.divTrunc(u8, 5, 0);
std.meta.isError(result) // true
if (result) |value| {
_ = value;
} else |err| {
_ = err;
}
// Enum from int (deprecated: use std.enums.fromInt)
// Checked enum conversion returns an optional.
const Color = enum { red, green, blue };
const c = std.meta.intToEnum(Color, 1) catch unreachable; // Color.green
const c = std.enums.fromInt(Color, 1) orelse return error.InvalidColor;
```
## TrailerFlags
@ -349,11 +359,12 @@ Memory-efficient optional field storage using bit flags:
```zig
const std = @import("std");
const Flags = std.meta.TrailerFlags(struct {
const Trailer = struct {
name: []const u8,
age: u32,
email: []const u8,
});
};
const Flags = std.meta.TrailerFlags(Trailer);
// Initialize with some fields active
var flags = Flags.init(.{
@ -364,7 +375,7 @@ var flags = Flags.init(.{
// Allocate only needed space
const size = flags.sizeInBytes();
const data = try allocator.alignedAlloc(u8, @alignOf(@TypeOf(flags).Fields), size);
const data = try allocator.alignedAlloc(u8, .of(Trailer), size);
defer allocator.free(data);
// Set values

View File

@ -1,6 +1,6 @@
# std.MultiArrayList
Struct-of-Arrays container for cache-efficient struct storage. Stores each field in a separate contiguous array, reducing padding overhead and improving cache locality when accessing individual fields.
Struct-of-Arrays container for cache-efficient struct storage. Stores each field in a separate contiguous array, reducing padding overhead and improving cache locality when accessing individual fields. The element type must be a struct or tagged union; untagged unions are not supported.
## When to Use
@ -54,7 +54,7 @@ const n = list.len;
When accessing multiple fields, use `slice()` to compute pointers once:
```zig
const slices = list.slice();
var slices = list.slice();
// Now access fields without recomputing offsets
for (slices.items(.id), slices.items(.score)) |id, score| {
@ -75,8 +75,9 @@ list.swapRemove(index);
// O(n) but preserves order
list.orderedRemove(index);
// Remove multiple indices (must be sorted ascending)
list.orderedRemoveMany(&.{ 1, 5, 7, 9 });
// Remove multiple in-bounds indices from the pre-removal list. They must be
// sorted ascending; duplicates are allowed and count as one removed element.
list.orderedRemoveMany(&.{ 0, 1 });
```
## Tagged Union Support
@ -96,7 +97,7 @@ try values.append(allocator, .{ .float = 3.14 });
// Access tags and data separately
const tags = values.items(.tags); // []meta.Tag(Value)
const data = values.items(.data); // []Value.Bare (untagged union)
const data = values.items(.data); // slice of internal payload-only union storage
// Reconstruct full union
const full = values.get(0); // Value{ .int = 42 }
@ -132,6 +133,11 @@ list.clearAndFree(allocator);
## Clone and Transfer
```zig
const copy = try list.clone(allocator);
const owned_slice = list.toOwnedSlice(); // empties list, caller owns
var copy = try list.clone(allocator);
defer copy.deinit(allocator);
var owned_slice = list.toOwnedSlice(); // empties list; Slice now owns storage
defer owned_slice.deinit(allocator);
```
A cached `Slice` contains derived field pointers and length metadata. Operations on the original list that grow, reallocate, remove, resize, or transfer storage can make that cached metadata stale; reacquire `list.slice()` after mutations. A successful growth can also invalidate previously returned field pointers.

View File

@ -1,12 +1,12 @@
# std.Io.net Reference (Zig 0.16.0)
Cross-platform networking abstractions for TCP/IP connections, address handling, and DNS resolution.
Cross-platform networking abstractions for IP connections, address handling, DNS resolution, Unix-domain sockets, and lower-level socket operations. Zig 0.16 exposes these APIs under `std.Io.net`; there is no root `std.net` module.
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
Zig 0.16 moves networking under `std.Io.net`. New networking code should accept/use `std.Io`, and stream/socket close/read/write operations should use the `io` parameter. Older `std.net` examples below are historical; translate them to `std.Io.net` before using them in new code.
Networking operations take an explicit `std.Io`. Readers, writers, servers, sockets, and streams also use that `io` value for construction or cleanup.
High-level HTTP clients also store `io`:
High-level HTTP clients store it directly:
```zig
var client: std.http.Client = .{
@ -17,626 +17,550 @@ defer client.deinit();
```
## Table of Contents
- [TCP Client](#tcp-client)
- [TCP Server](#tcp-server)
- [Address Types](#address-types)
- [API Map](#api-map)
- [TCP Clients](#tcp-clients)
- [TCP Servers](#tcp-servers)
- [IP Address Types](#ip-address-types)
- [Stream I/O](#stream-io)
- [DNS Resolution](#dns-resolution)
- [Unix Sockets](#unix-sockets)
- [DNS and Host Names](#dns-and-host-names)
- [Unix-Domain Sockets](#unix-domain-sockets)
- [Sockets and Datagram APIs](#sockets-and-datagram-apis)
- [Common Patterns](#common-patterns)
- [Error Sets](#error-sets)
## TCP Client
## API Map
### Connect by Hostname
```zig
const net = std.Io.net;
net.IpAddress // tagged union: .ip4 or .ip6
net.Ip4Address // value-oriented IPv4 bytes + port
net.Ip6Address // IPv6 bytes + port + flow + interface
net.HostName // validated DNS host name and lookup/connect APIs
net.UnixAddress // Unix-domain socket path
net.Socket // open socket plus resolved/bound address
net.Stream // reliable connected byte stream
net.Server // listening socket
net.Protocol // tcp, udp, and other protocol identifiers
```
Use `IpAddress.connect` when an IP is already known. Use `HostName.connect` when DNS resolution and address fallback are required.
## TCP Clients
### Connect by Host Name
```zig
const std = @import("std");
const net = std.net;
const net = std.Io.net;
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
pub fn main(init: std.process.Init) !void {
const io = init.io;
const host: net.HostName = try .init("example.com");
// Connect to host:port (handles DNS resolution)
const stream = try net.tcpConnectToHost(allocator, "example.com", 80);
defer stream.close();
const stream = try host.connect(io, 80, .{
.mode = .stream,
.protocol = .tcp,
});
defer stream.close(io);
// Create buffered reader/writer
var read_buf: [4096]u8 = undefined;
var write_buf: [1024]u8 = undefined;
var reader = stream.reader(io, &read_buf);
var writer = stream.writer(io, &write_buf);
var reader = stream.reader(&read_buf);
var writer = stream.writer(&write_buf);
// Write request
try writer.interface.writeAll("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
try writer.interface.writeAll(
"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n",
);
try writer.interface.flush();
// Read response
while (reader.interface().take(4096)) |chunk| {
while (reader.interface.take(4096)) |chunk| {
std.debug.print("{s}", .{chunk});
} else |err| switch (err) {
error.EndOfStream => {},
else => return err,
error.ReadFailed => return reader.err.?,
}
}
```
### Connect by Address
`HostName.connect` performs lookup and races/falls back across returned addresses. The host's bytes are externally owned, so keep their backing storage alive for the call.
### Connect to a Parsed Address
```zig
// Parse and connect to IP address directly (no DNS)
const address = try net.Address.parseIp4("192.168.1.1", 8080);
const stream = try net.tcpConnectToAddress(address);
defer stream.close();
const address = try net.IpAddress.parseIp4("192.168.1.1", 8080);
const stream = try address.connect(io, .{
.mode = .stream,
.protocol = .tcp,
});
defer stream.close(io);
```
### Connect with IPv6
### IPv6 and Scoped IPv6
```zig
// IPv6 address
const addr6 = try net.Address.parseIp6("::1", 8080);
const stream = try net.tcpConnectToAddress(addr6);
defer stream.close();
// Pure parsing: no interface-name scope lookup.
const loopback = try net.IpAddress.parseIp6("::1", 8080);
const stream = try loopback.connect(io, .{ .mode = .stream, .protocol = .tcp });
defer stream.close(io);
// IPv6 with scope ID (link-local)
const link_local = try net.Address.resolveIp6("fe80::1%eth0", 8080);
// Resolving `%eth0` / `%eno1` requires Io because the interface name must be
// converted to an operating-system interface index.
const link_local = try net.IpAddress.resolveIp6(io, "fe80::1%eth0", 8080);
```
## TCP Server
### Connection Timeout
```zig
const host: net.HostName = try .init("example.com");
const stream = try host.connect(io, 443, .{
.mode = .stream,
.protocol = .tcp,
.timeout = .{ .duration = .fromSeconds(5) },
});
defer stream.close(io);
```
Timeouts can be `.none`, a relative `.duration`, or an absolute `.deadline`.
## TCP Servers
### Basic Server
```zig
const std = @import("std");
const net = std.net;
const net = std.Io.net;
pub fn main() !void {
// Create address to listen on
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080);
// Start listening
var server = try address.listen(.{
fn serve(io: std.Io) !void {
const address: net.IpAddress = .{ .ip4 = .unspecified(8080) };
var server = try address.listen(io, .{
.reuse_address = true,
.kernel_backlog = 256,
});
defer server.deinit();
defer server.deinit(io);
std.debug.print("Listening on port {d}\n", .{server.listen_address.getPort()});
std.debug.print("Listening on port {d}\n", .{server.socket.address.getPort()});
// Accept loop
while (true) {
const conn = try server.accept();
defer conn.stream.close();
// Handle connection
try handleClient(conn.stream, conn.address);
const client = try server.accept(io);
defer client.close(io);
try handleClient(io, client);
}
}
fn handleClient(stream: net.Stream, client_addr: net.Address) !void {
fn handleClient(io: std.Io, stream: net.Stream) !void {
var read_buf: [4096]u8 = undefined;
var write_buf: [1024]u8 = undefined;
var reader = stream.reader(io, &read_buf);
var writer = stream.writer(io, &write_buf);
var reader = stream.reader(&read_buf);
var writer = stream.writer(&write_buf);
// Read request
const request = reader.interface().takeDelimiter('\n') catch |err| switch (err) {
error.EndOfStream => return,
else => return err,
const request_line = reader.interface.takeDelimiter('\n') catch |err| switch (err) {
error.ReadFailed => return reader.err.?,
error.StreamTooLong => return error.RequestLineTooLong,
} orelse return;
std.debug.print("Request from {f}: {s}\n", .{ stream.socket.address, request_line });
std.debug.print("Request from client: {s}\n", .{request});
// Send response
try writer.interface.writeAll("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello");
try writer.interface.flush();
}
```
`Server.accept(io)` returns a `Stream`, not a separate connection wrapper. The accepted stream contains its socket and address.
### Listen Options
```zig
const server = try address.listen(.{
// Allow address reuse (SO_REUSEADDR + SO_REUSEPORT on POSIX)
const server = try address.listen(io, .{
.kernel_backlog = 128,
.reuse_address = true,
// Connection backlog (default 128)
.kernel_backlog = 256,
// Non-blocking accept (O_NONBLOCK)
.force_nonblocking = false,
.mode = .stream,
.protocol = .tcp,
});
```
### Server on Any Available Port
`IpAddress.ListenOptions` contains exactly `kernel_backlog`, `reuse_address`, `mode`, and `protocol`. It does not contain the older `force_nonblocking` field.
### Ephemeral Port
```zig
// Listen on port 0 to let OS assign an available port
const address = net.Address.initIp4(.{ 127, 0, 0, 1 }, 0);
var server = try address.listen(.{});
defer server.deinit();
const address: net.IpAddress = .{ .ip4 = .loopback(0) };
var server = try address.listen(io, .{});
defer server.deinit(io);
// Get the assigned port
const port = server.listen_address.getPort();
std.debug.print("Listening on port {d}\n", .{port});
const assigned_port = server.socket.address.getPort();
```
## Address Types
The resolved port is stored on `server.socket.address`; there is no `listen_address` field.
### Address Union
## IP Address Types
### Tagged Union
```zig
pub const Address = extern union {
any: posix.sockaddr,
in: Ip4Address,
in6: Ip6Address,
un: posix.sockaddr.un, // Unix socket (if supported)
pub const IpAddress = union(enum) {
ip4: Ip4Address,
ip6: Ip6Address,
};
```
### Creating Addresses
This is a value-oriented union, not an extern `sockaddr` overlay. OS-specific socket-address conversion is handled below this API.
### Construction
```zig
// IPv4 from bytes
const addr4 = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080);
const loopback4: net.IpAddress = .{ .ip4 = .loopback(8080) };
const any4: net.IpAddress = .{ .ip4 = .unspecified(8080) };
const loopback6: net.IpAddress = .{ .ip6 = .loopback(8080) };
const any6: net.IpAddress = .{ .ip6 = .unspecified(8080) };
// IPv6 from bytes
const addr6 = net.Address.initIp6(
.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, // ::1
8080, // port
0, // flowinfo
0, // scope_id
);
// Unix socket
const unix = try net.Address.initUnix("/tmp/my.sock");
const explicit4: net.IpAddress = .{ .ip4 = .{
.bytes = .{ 127, 0, 0, 1 },
.port = 8080,
} };
```
### Parsing Addresses
`Ip6Address` also has `flow: u32 = 0` and `interface: net.Interface = .none` fields.
### Parsing
```zig
// Parse IPv4
const addr4 = try net.Address.parseIp4("192.168.1.1", 8080);
const addr4 = try net.IpAddress.parseIp4("192.168.1.1", 8080);
const addr6 = try net.IpAddress.parseIp6("2001:db8::1", 8080);
const either = try net.IpAddress.parse("::1", 8080);
// Parse IPv6
const addr6 = try net.Address.parseIp6("2001:db8::1", 8080);
// Address plus optional port. IPv6 must be bracketed.
const literal4 = try net.IpAddress.parseLiteral("192.168.1.1:8080");
const literal6 = try net.IpAddress.parseLiteral("[2001:db8::1]:8080");
// Parse either (tries IPv4 first, then IPv6)
const addr = try net.Address.parseIp("::1", 8080);
// Parse IP:port format
// IPv4: "192.168.1.1:8080"
// IPv6: "[::1]:8080" (brackets required)
const addr_port = try net.Address.parseIpAndPort("[::1]:8080");
// Resolve with interface lookup (for link-local IPv6)
const resolved = try net.Address.resolveIp6("fe80::1%eth0", 8080);
// Handles an IPv6 interface-name scope and therefore requires Io.
const scoped = try net.IpAddress.resolve(io, "fe80::1%eth0", 8080);
```
### Address Methods
`parseLiteral` uses port zero when no port is present. `parse` accepts an explicit port and tries IPv4, then IPv6. `resolve` adds scoped-IPv6 interface lookup.
### Methods and Formatting
```zig
var addr = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080);
var address = try net.IpAddress.parseIp4("127.0.0.1", 8080);
const port = address.getPort();
address.setPort(9090);
// Get/set port (native endian)
const port = addr.getPort(); // 8080
addr.setPort(9090);
const same = address.eql(&other_address);
// Get socket length for syscalls
const socklen = addr.getOsSockLen();
// Compare addresses
if (addr.eql(other_addr)) {
// addresses match
}
// Format for printing
var buf: [64]u8 = undefined;
var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try addr.format(&writer);
const formatted = writer.buffered(); // "127.0.0.1:8080"
try address.format(&writer); // omits an IPv6 interface-name scope
try address.formatResolved(io, &writer); // includes a resolvable IPv6 scope
```
### Ip4Address
`Ip4Address.format` and `Ip6Address.format` include the native-endian port. `IpAddress.fromIp6` converts IPv4-mapped IPv6 addresses back to `.ip4` where possible.
### IPv4 Parse Errors
```zig
const Ip4Address = extern struct {
sa: posix.sockaddr.in,
pub fn parse(buf: []const u8, port: u16) !Ip4Address;
pub fn init(addr: [4]u8, port: u16) Ip4Address;
pub fn getPort(self: Ip4Address) u16;
pub fn setPort(self: *Ip4Address, port: u16) void;
pub fn format(self: Ip4Address, w: *std.Io.Writer) !void;
pub const Ip4Address.ParseError = error{
Overflow,
InvalidEnd,
InvalidCharacter,
Incomplete,
NonCanonical,
};
```
### Ip6Address
```zig
const Ip6Address = extern struct {
sa: posix.sockaddr.in6,
pub fn parse(buf: []const u8, port: u16) !Ip6Address;
pub fn resolve(buf: []const u8, port: u16) !Ip6Address; // handles %interface
pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address;
pub fn getPort(self: Ip6Address) u16;
pub fn setPort(self: *Ip6Address, port: u16) void;
pub fn format(self: Ip6Address, w: *std.Io.Writer) !void;
};
```
For example, leading-zero forms such as `01.2.3.4` are non-canonical.
## Stream I/O
### Stream Type
### Stream Shape and Lifecycle
```zig
```text
pub const Stream = struct {
handle: Handle, // fd on POSIX, SOCKET on Windows
socket: net.Socket,
pub fn close(s: Stream) void;
pub fn reader(stream: Stream, buffer: []u8) Reader;
pub fn writer(stream: Stream, buffer: []u8) Writer;
pub fn close(stream: *const Stream, io: std.Io) void;
pub fn shutdown(stream: *const Stream, io: std.Io, how: net.ShutdownHow) !void;
pub fn reader(stream: Stream, io: std.Io, buffer: []u8) Stream.Reader;
pub fn writer(stream: Stream, io: std.Io, buffer: []u8) Stream.Writer;
};
```
### Reading from Stream
Do not close a stream twice. Flush a buffered writer before shutdown/close when its bytes must reach the peer.
### Reading
```zig
const stream = try net.tcpConnectToHost(allocator, "example.com", 80);
defer stream.close();
var read_buf: [4096]u8 = undefined;
var reader = stream.reader(io, &read_buf);
const r = &reader.interface;
var buf: [4096]u8 = undefined;
var reader = stream.reader(&buf);
const r = reader.interface();
// Read bytes
const data = r.take(100) catch |err| switch (err) {
error.EndOfStream => &.{},
error.ReadFailed => return reader.getError().?,
error.EndOfStream => return,
error.ReadFailed => return reader.err.?,
};
// Read until delimiter
const line = r.takeDelimiter('\n') catch |err| switch (err) {
error.EndOfStream => null,
error.ReadFailed => return reader.err.?,
error.StreamTooLong => return error.LineTooLong,
error.ReadFailed => return reader.getError().?,
} orelse return;
// Discard bytes
_ = try r.discard(.limited(100));
// Stream to writer
_ = try r.streamRemaining(&output_writer);
_ = data;
_ = line;
```
### Writing to Stream
The generic reader surface reports `error.ReadFailed`; the network-specific cause is stored in `reader.err`, with cases such as `ConnectionResetByPeer`, `Timeout`, `SocketUnconnected`, or `NetworkDown`.
### Writing
```zig
var buf: [1024]u8 = undefined;
var writer = stream.writer(&buf);
var write_buf: [1024]u8 = undefined;
var writer = stream.writer(io, &write_buf);
const w = &writer.interface;
// Write bytes
try w.writeAll("Hello, World!");
// Formatted output
try w.print("Count: {d}\n", .{42});
// MUST flush before close
try w.flush();
```
### Error Handling
The generic writer reports `error.WriteFailed`; inspect `writer.err` for the network-specific cause. A successful `writeAll` may still be buffered until `flush`.
### Half-Close
```zig
var reader = stream.reader(&buf);
const r = reader.interface();
const data = r.take(100) catch |err| switch (err) {
error.EndOfStream => {
// Connection closed normally
return;
},
error.ReadFailed => {
// Get underlying error
const read_err = reader.getError().?;
switch (read_err) {
error.ConnectionResetByPeer => return error.Disconnected,
error.SocketNotConnected => return error.Disconnected,
else => return read_err,
}
},
};
try stream.shutdown(io, .send); // no more application writes
// Continue reading until EndOfStream if the protocol expects a response.
```
## DNS Resolution
`ShutdownHow` is `.recv`, `.send`, or `.both`.
### Get Address List
## DNS and Host Names
### Validation
```zig
const std = @import("std");
const net = std.net;
const host = try net.HostName.init("example.com");
try net.HostName.validate("api.example.com");
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const same = host.eql(try .init("EXAMPLE.COM")); // DNS names compare case-insensitively
const child = host.sameParentDomain(try .init("www.example.com"));
```
// Resolve hostname to addresses
const list = try net.getAddressList(allocator, "example.com", 80);
defer list.deinit();
`HostName` retains a borrowed byte slice. Labels and total length are validated; the maximum is `net.HostName.max_len`.
// Canonical name (if available)
if (list.canon_name) |name| {
std.debug.print("Canonical name: {s}\n", .{name});
}
### Queue-Based Lookup
// Iterate addresses
for (list.addrs) |addr| {
var buf: [64]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
try addr.format(&w);
std.debug.print("Address: {s}\n", .{w.buffered()});
}
```zig
const host: net.HostName = try .init("example.com");
var result_storage: [16]net.HostName.LookupResult = undefined;
var results: std.Io.Queue(net.HostName.LookupResult) = .init(&result_storage);
try host.lookup(io, &results, .{ .port = 443 });
while (results.getOne(io)) |result| switch (result) {
.address => |address| std.debug.print("address: {f}\n", .{address}),
.canonical_name => |name| std.debug.print("canonical: {s}\n", .{name.bytes}),
} else |err| switch (err) {
error.Closed => {},
error.Canceled => return err,
}
```
### Connect with Fallback
`lookup` adds zero or more `.address` results and exactly one `.canonical_name`, then closes the queue even on error. Capacity 16 guarantees the call itself need not block waiting for a consumer.
`tcpConnectToHost` automatically tries all resolved addresses:
### Connect with Lookup and Fallback
```zig
// Tries each resolved address until one connects
const stream = net.tcpConnectToHost(allocator, "example.com", 80) catch |err| switch (err) {
error.ConnectionRefused => return error.ServerDown,
error.UnknownHostName => return error.DnsError,
error.TemporaryNameServerFailure => return error.DnsError,
const host: net.HostName = try .init("example.com");
const stream = host.connect(io, 443, .{
.mode = .stream,
.protocol = .tcp,
}) catch |err| switch (err) {
error.UnknownHostName, error.NoAddressReturned => return error.DnsFailure,
error.ConnectionRefused => return error.ServerUnavailable,
else => return err,
};
defer stream.close(io);
```
## Unix Sockets
For advanced callers, `HostName.connectMany` asynchronously attempts all resolved addresses and writes successes or per-address connection errors to a caller-provided queue.
### Check Platform Support
## Unix-Domain Sockets
### Support and Client
```zig
if (net.has_unix_sockets) {
// Unix sockets available
const address = try net.UnixAddress.init("/var/run/app.sock");
const stream = try address.connect(io);
defer stream.close(io);
var read_buf: [4096]u8 = undefined;
var reader = stream.reader(io, &read_buf);
_ = &reader;
}
```
### Connect to Unix Socket
`UnixAddress` borrows its path and rejects paths longer than `UnixAddress.max_len`. `isAbstract()` detects an empty/leading-NUL abstract address representation.
### Server
```zig
const stream = try net.connectUnixSocket("/var/run/app.sock");
defer stream.close();
const socket_path = "/tmp/my.sock";
std.Io.Dir.deleteFileAbsolute(io, socket_path) catch |err| switch (err) {
error.FileNotFound => {},
else => return err,
};
defer std.Io.Dir.deleteFileAbsolute(io, socket_path) catch {};
var buf: [4096]u8 = undefined;
var reader = stream.reader(&buf);
var writer = stream.writer(&buf);
// ... use like TCP
```
### Unix Socket Server
```zig
const address = try net.Address.initUnix("/tmp/my.sock");
var server = try address.listen(.{ .reuse_address = true });
defer server.deinit();
// Remove socket file on cleanup
defer std.fs.deleteFileAbsolute("/tmp/my.sock") catch {};
const address = try net.UnixAddress.init(socket_path);
var server = try address.listen(io, .{ .kernel_backlog = 128 });
defer server.deinit(io);
while (true) {
const conn = try server.accept();
defer conn.stream.close();
// handle connection...
const client = try server.accept(io);
defer client.close(io);
// Handle one client in this loop body.
}
```
Unix listen options contain only `kernel_backlog`; IP `reuse_address` options do not apply to this type.
## Sockets and Datagram APIs
`IpAddress.bind` is the non-streaming counterpart to `listen`:
```zig
const address: net.IpAddress = .{ .ip4 = .unspecified(5353) };
const socket = try address.bind(io, .{
.mode = .dgram,
.protocol = .udp,
.allow_broadcast = false,
});
defer socket.close(io);
```
`BindOptions` contains `ip6_only`, `allow_broadcast`, required `mode`, and optional `protocol`. `Socket` also exposes message send/receive operations, shutdown, option accessors, and `closeMany`; use those when datagram boundaries or raw socket features matter.
The listening API intentionally has a smaller option set than `bind`. In particular, `IpAddress.ListenOptions` has no `ip6_only` flag, so do not assume an IPv6 listener is unconditionally dual-stack on every target. Use explicitly managed IPv4/IPv6 listeners when that behavior must be controlled.
## Common Patterns
### Echo Server
### Echo One Connection
```zig
const std = @import("std");
const net = std.net;
pub fn main() !void {
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 7); // echo port
var server = try address.listen(.{ .reuse_address = true });
defer server.deinit();
while (true) {
const conn = try server.accept();
defer conn.stream.close();
var buf: [4096]u8 = undefined;
var reader = conn.stream.reader(&buf);
var writer = conn.stream.writer(&buf);
// Echo back everything received
_ = reader.interface().streamRemaining(&writer.interface) catch {};
writer.interface.flush() catch {};
}
}
```
### Simple HTTP GET
```zig
fn httpGet(allocator: Allocator, host: []const u8, path: []const u8) ![]u8 {
const stream = try net.tcpConnectToHost(allocator, host, 80);
defer stream.close();
var write_buf: [1024]u8 = undefined;
var writer = stream.writer(&write_buf);
const w = &writer.interface;
try w.print("GET {s} HTTP/1.1\r\n", .{path});
try w.print("Host: {s}\r\n", .{host});
try w.writeAll("Connection: close\r\n\r\n");
try w.flush();
fn echoConnection(io: std.Io, stream: net.Stream) !void {
var read_buf: [4096]u8 = undefined;
var reader = stream.reader(&read_buf);
var write_buf: [4096]u8 = undefined;
var reader = stream.reader(io, &read_buf);
var writer = stream.writer(io, &write_buf);
var response: std.ArrayList(u8) = .empty;
defer response.deinit(allocator);
while (true) {
const chunk = reader.interface().take(4096) catch |err| switch (err) {
error.EndOfStream => break,
error.ReadFailed => return reader.getError().?,
_ = reader.interface.streamRemaining(&writer.interface) catch |err| switch (err) {
error.ReadFailed => return reader.err.?,
error.WriteFailed => return writer.err.?,
};
try response.appendSlice(allocator, chunk);
}
return response.toOwnedSlice(allocator);
try writer.interface.flush();
}
```
### Non-blocking Accept with Timeout
For a concurrent server, schedule each accepted stream through the active `std.Io` implementation and make one owner responsible for closing it.
### Address and Host Validation
```zig
const std = @import("std");
const net = std.net;
const posix = std.posix;
fn acceptWithTimeout(server: *net.Server, timeout_ms: i32) !?net.Server.Connection {
var pfd = [1]posix.pollfd{.{
.fd = server.stream.handle,
.events = posix.POLL.IN,
.revents = undefined,
}};
const ready = try posix.poll(&pfd, timeout_ms);
if (ready == 0) return null; // timeout
return try server.accept();
}
```
### Address Validation
```zig
fn isValidIpAddress(str: []const u8) bool {
_ = net.Address.parseIp(str, 0) catch return false;
fn isValidIpAddress(text: []const u8) bool {
_ = net.IpAddress.parse(text, 0) catch return false;
return true;
}
fn isValidHostname(hostname: []const u8) bool {
return net.isValidHostName(hostname);
fn isValidHostName(text: []const u8) bool {
net.HostName.validate(text) catch return false;
return true;
}
```
### Dual-Stack Server (IPv4 + IPv6)
Parsing an IP is a pure syntax/canonicality check. Host-name validation does not perform DNS lookup.
### Index-Based Connection Pool
```zig
// Listen on IPv6 with dual-stack (accepts both IPv4 and IPv6)
const address = net.Address.initIp6(
.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // ::
8080,
0,
0,
);
var server = try address.listen(.{ .reuse_address = true });
defer server.deinit();
// IPv4 clients appear as IPv4-mapped IPv6 addresses (::ffff:x.x.x.x)
```
### Connection Pool Pattern
```zig
const Connection = struct {
stream: net.Stream,
in_use: bool,
};
const Pool = struct {
connections: std.ArrayList(Connection),
allocator: Allocator,
const Entry = struct { stream: net.Stream, in_use: bool };
pub fn acquire(self: *Pool, address: net.Address) !net.Stream {
// Find free connection
for (self.connections.items) |*conn| {
if (!conn.in_use) {
conn.in_use = true;
return conn.stream;
}
}
// Create new connection
const stream = try net.tcpConnectToAddress(address);
try self.connections.append(self.allocator, .{
.stream = stream,
.in_use = true,
});
return stream;
}
entries: std.ArrayList(Entry) = .empty,
allocator: std.mem.Allocator,
io: std.Io,
pub fn release(self: *Pool, stream: net.Stream) void {
for (self.connections.items) |*conn| {
if (conn.stream.handle == stream.handle) {
conn.in_use = false;
return;
}
fn acquire(self: *Pool, address: *const net.IpAddress) !usize {
for (self.entries.items, 0..) |*entry, index| {
if (!entry.in_use) {
entry.in_use = true;
return index;
}
}
pub fn deinit(self: *Pool) void {
for (self.connections.items) |conn| {
conn.stream.close();
const stream = try address.connect(self.io, .{ .mode = .stream, .protocol = .tcp });
errdefer stream.close(self.io);
try self.entries.append(self.allocator, .{ .stream = stream, .in_use = true });
return self.entries.items.len - 1;
}
self.connections.deinit(self.allocator);
fn get(self: *Pool, index: usize) *net.Stream {
return &self.entries.items[index].stream;
}
fn release(self: *Pool, index: usize) void {
self.entries.items[index].in_use = false;
}
fn deinit(self: *Pool) void {
for (self.entries.items) |entry| entry.stream.close(self.io);
self.entries.deinit(self.allocator);
self.* = undefined;
}
};
```
## Error Types
Indices are used because growing an `ArrayList` can invalidate pointers into its storage. A production pool also needs protocol-aware liveness checks, concurrency control, capacity limits, idle expiry, and a policy for discarding failed streams.
### Connection Errors
### Prefer Protocol-Specific Clients
Direct stream examples are useful for custom protocols and learning the I/O model. For HTTP, use `std.http.Client`: it handles framing, redirects/options, response-body lifecycle, proxies, and TLS concerns that a raw `GET` snippet does not.
## Error Sets
### IP Connection Errors
`net.IpAddress.ConnectError` includes address/family and resource failures plus network outcomes such as:
```zig
pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError;
// Includes: ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, etc.
error.ConnectionRefused
error.ConnectionResetByPeer
error.HostUnreachable
error.NetworkUnreachable
error.NetworkDown
error.Timeout
error.WouldBlock
error.AccessDenied
```
### DNS Errors
It also includes cancellation and implementation-specific unexpected I/O errors. Match only cases the caller can handle meaningfully and propagate the rest.
### Host Lookup and Connect Errors
```zig
pub const GetAddressListError = error{
TemporaryNameServerFailure,
NameServerFailure,
AddressFamilyNotSupported,
UnknownHostName,
HostLacksNetworkAddresses,
// ... and others
};
net.HostName.LookupError // UnknownHostName, NameServerFailure,
// NoAddressReturned, configuration/DNS record errors, ...
net.HostName.ConnectError // LookupError || net.IpAddress.ConnectError
```
### Address Parse Errors
The old `GetAddressListError` and `TcpConnectToHostError` aliases are not Zig 0.16 APIs.
```zig
pub const IPv4ParseError = error{
Overflow,
InvalidEnd,
InvalidCharacter,
Incomplete,
NonCanonical, // e.g., leading zeros like "01.02.03.04"
};
### Stream Error Translation
pub const IPv6ParseError = error{
Overflow,
InvalidEnd,
InvalidCharacter,
Incomplete,
InvalidIpv4Mapping,
};
```
`Stream.Reader` and `Stream.Writer` deliberately adapt network errors to the generic `std.Io.Reader`/`std.Io.Writer` interfaces:
- On `error.ReadFailed`, inspect `reader.err`.
- On `error.WriteFailed`, inspect `writer.err`.
- `EndOfStream` is the normal generic-reader signal for an orderly peer close.
- Close, shutdown, accept, connect, lookup, bind, and listen all take the explicit `io` used to create or operate the resource.

View File

@ -23,13 +23,9 @@ std.os.wasi // WebAssembly System Interface
std.os.plan9 // Plan 9 system calls
std.os.uefi // UEFI firmware interface
std.os.emscripten // Emscripten runtime
std.os.freebsd // FreeBSD-specific definitions
std.os.environ // Environment variables (populated at startup)
std.os.argv // Command line arguments (POSIX only)
```
**Note**: For most use cases, prefer `std.posix` (cross-platform POSIX-like APIs) or `std.fs`/`std.process` (high-level abstractions). Use `std.os` when you need direct OS-specific functionality.
Those six target modules are the complete public surface of `std.os` in Zig 0.16. For most use cases, prefer `std.Io` (portable, capability-based I/O) or `std.posix` (cross-platform POSIX-like APIs). Process startup arguments and environment data are supplied through `std.process.Init`, not `std.os.argv` or `std.os.environ`.
## Platform Submodules
@ -37,13 +33,14 @@ std.os.argv // Command line arguments (POSIX only)
```zig
// High-level (recommended for most code)
const file = try std.fs.cwd().openFile("data.txt", .{});
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
defer file.close(io);
// POSIX-level (cross-platform low-level)
const fd = try std.posix.open("data.txt", .{}, 0);
// OS-specific (platform-specific features)
const result = std.os.linux.syscall3(.read, fd, buf.ptr, buf.len);
const result = std.os.linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len);
```
## Linux-Specific APIs
@ -55,7 +52,7 @@ const linux = std.os.linux;
// Raw syscall interface
const result = linux.syscall3(.write, fd, @intFromPtr(buf.ptr), buf.len);
if (linux.E.init(result) != .SUCCESS) {
if (linux.errno(result) != .SUCCESS) {
// handle error
}
@ -74,7 +71,7 @@ _ = linux.chroot(path);
const linux = std.os.linux;
// mmap with typed flags
const addr = linux.mmap(
const result = linux.mmap(
null,
length,
linux.PROT.READ | linux.PROT.WRITE,
@ -82,9 +79,10 @@ const addr = linux.mmap(
-1,
0,
);
if (addr == linux.MAP_FAILED) {
// handle error
}
const addr: [*]u8 = switch (linux.errno(result)) {
.SUCCESS => @ptrFromInt(result),
else => |err| return std.posix.unexpectedErrno(err),
};
// Remap
_ = linux.mremap(old_addr, old_size, new_size, .{ .MAYMOVE = true }, null);
@ -121,7 +119,7 @@ const linux = std.os.linux;
// Wait on futex
_ = linux.futex(
&futex_word,
.{ .op = .WAIT, .PRIVATE = true },
.{ .cmd = .WAIT, .private = true },
expected_value,
.{ .timeout = &timeout },
null,
@ -131,7 +129,7 @@ _ = linux.futex(
// Wake waiters
_ = linux.futex(
&futex_word,
.{ .op = .WAKE, .PRIVATE = true },
.{ .cmd = .WAKE, .private = true },
num_to_wake,
.{ .val2 = 0 },
null,
@ -150,7 +148,10 @@ var act: linux.Sigaction = .{
.mask = linux.empty_sigset,
.flags = .{},
};
_ = linux.sigaction(linux.SIG.INT, &act, null);
const result = linux.sigaction(.INT, &act, null);
if (linux.errno(result) != .SUCCESS) {
// translate or handle the raw errno
}
// Kill process
_ = linux.kill(pid, linux.SIG.TERM);
@ -173,7 +174,11 @@ _ = linux.epoll_ctl(epfd, .ADD, client_fd, &event);
// Wait for events
var events: [64]linux.epoll_event = undefined;
const n = linux.epoll_wait(epfd, &events, -1);
const result = linux.epoll_wait(epfd, &events, @intCast(events.len), -1);
const n: usize = switch (linux.errno(result)) {
.SUCCESS => result,
else => |err| return std.posix.unexpectedErrno(err),
};
for (events[0..n]) |ev| {
// handle event
}
@ -195,19 +200,13 @@ const platform = linux.getauxval(std.elf.AT_PLATFORM);
### File Operations
```zig
const windows = std.os.windows;
// Open file with NT API
const handle = try windows.OpenFile(path_utf16, .{
.access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE,
.creation = windows.FILE_OPEN,
.share_access = windows.FILE_SHARE_READ,
.filter = .file_only,
.follow_symlinks = true,
});
defer windows.CloseHandle(handle);
// Prefer the portable std.Io layer even in Windows-only programs.
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
defer file.close(io);
```
For Win32 or NT features that `std.Io` does not expose, use declarations actually exported by `std.os.windows` and its `kernel32`/`ntdll` submodules. There is no public `std.os.windows.OpenFile` helper in Zig 0.16.
### Process Information
```zig
@ -225,26 +224,13 @@ const err = windows.GetLastError();
### Pipes
```zig
const windows = std.os.windows;
var read_handle: windows.HANDLE = undefined;
var write_handle: windows.HANDLE = undefined;
var sa: windows.SECURITY_ATTRIBUTES = .{
.nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
.lpSecurityDescriptor = null,
.bInheritHandle = windows.TRUE,
};
try windows.CreatePipe(&read_handle, &write_handle, &sa);
```
Use the `std.Io` pipe/process APIs for portable pipes. If raw Windows handles are required, call a declaration that is actually exported from the relevant Windows submodule and translate its Win32 error explicitly; Zig 0.16 does not expose a fallible `std.os.windows.CreatePipe` wrapper with the signature shown in older examples.
### Submodules
```zig
windows.kernel32 // kernel32.dll functions
windows.ntdll // ntdll.dll functions (NT native API)
windows.advapi32 // advapi32.dll (security, registry)
windows.ws2_32 // Winsock 2 networking
windows.crypt32 // Cryptographic functions
windows.nls // National Language Support
@ -386,21 +372,21 @@ const submitted = try ring.submit();
_ = try ring.submit_and_wait(1);
// Process completions
while (ring.cq_ready() > 0) {
const cqe = ring.peek_cqe() orelse break;
var cqes: [32]std.os.linux.io_uring_cqe = undefined;
const ready = try ring.copy_cqes(&cqes, 1);
for (cqes[0..ready]) |cqe| {
const user_data = cqe.user_data;
const result = cqe.res; // bytes transferred or -errno
if (result < 0) {
const err = std.os.linux.E.init(@intCast(-result));
const err: std.os.linux.E = @enumFromInt(@as(u16, @intCast(-result)));
// handle error
}
ring.cq_advance(1); // mark CQE as consumed
}
```
`copy_cqes` copies and consumes completions as a batch. `copy_cqe` is the corresponding wait-for-one convenience method. Do not additionally call `cqe_seen` or `cq_advance` after either copying API.
### Common Operations
```zig
@ -411,8 +397,8 @@ sqe.prep_readv(fd, iovecs, offset);
sqe.prep_writev(fd, iovecs, offset);
// Fixed buffers (pre-registered, zero-copy)
sqe.prep_read_fixed(fd, buf, offset, buf_index);
sqe.prep_write_fixed(fd, data, offset, buf_index);
sqe.prep_read_fixed(fd, registered_iovec, offset, buf_index);
sqe.prep_write_fixed(fd, registered_iovec, offset, buf_index);
// Network
sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0);
@ -463,7 +449,7 @@ defer ring.unregister_buffers() catch {};
// Use registered buffer
const sqe = try ring.get_sqe();
sqe.prep_read_fixed(fd, &buffers[0], 0, 0); // buf_index = 0
sqe.prep_read_fixed(fd, &iovecs[0], 0, 0); // buf_index = 0
```
### File Descriptor Registration
@ -482,51 +468,14 @@ sqe.flags |= std.os.linux.IOSQE_FIXED_FILE;
## Common Functions
### getFdPath
Zig 0.16 deliberately has no cross-platform function layer at the `std.os` root. Older references may mention root functions such as `std.os.getFdPath`, `std.os.accessW`, `std.os.fstatat_wasi`, or `std.os.fstat_wasi`; those are not public Zig 0.16 APIs.
Get canonical path from file descriptor (not all platforms).
Choose the API by intent:
```zig
var buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try std.os.getFdPath(fd, &buf);
std.debug.print("Path: {s}\n", .{path});
// Check if supported at comptime
if (comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) {
// safe to call
}
```
**Supported**: Linux, macOS, FreeBSD, Windows, Solaris/illumos, DragonFly (6.0+), NetBSD (10.0+)
### accessW (Windows)
Check file accessibility with WTF-16LE path.
```zig
const path_w = std.unicode.utf8ToUtf16LeStringLiteral("C:\\file.txt");
std.os.accessW(path_w) catch |err| switch (err) {
error.FileNotFound => {},
error.AccessDenied => {},
else => return err,
};
```
### WASI stat functions
```zig
// stat by path
const stat = try std.os.fstatat_wasi(dirfd, path, .{ .SYMLINK_FOLLOW = true });
// stat by fd
const stat = try std.os.fstat_wasi(fd);
stat.size; // file size
stat.filetype; // .REGULAR_FILE, .DIRECTORY, .SYMBOLIC_LINK, etc.
stat.atim; // access time (nanoseconds)
stat.mtim; // modification time
stat.ctim; // status change time
```
- Use `std.Io.Dir` and `std.Io.File` for portable path, access, and metadata operations.
- Use `std.posix` for POSIX-like file-descriptor operations.
- Use `std.os.windows`, `std.os.wasi`, or another exported target module when the behavior is intentionally ABI-specific.
- Preserve the path separately when an application needs to associate a portable path with an open file; a descriptor does not portably retain a recoverable canonical pathname.
## Common Patterns
@ -562,11 +511,10 @@ fn eventLoop(ring: *std.os.linux.IoUring) !void {
// Submit pending and wait for completions
_ = try ring.submit_and_wait(1);
// Process all available completions
while (ring.cq_ready() > 0) {
const cqe = ring.peek_cqe() orelse break;
defer ring.cq_advance(1);
// Copying also advances the completion queue.
var cqes: [64]std.os.linux.io_uring_cqe = undefined;
const count = try ring.copy_cqes(&cqes, 1);
for (cqes[0..count]) |cqe| {
const ctx = @as(*Context, @ptrFromInt(cqe.user_data));
try ctx.handle_completion(cqe.res);
}
@ -582,7 +530,7 @@ const linux = std.os.linux;
fn readSyscall(fd: i32, buf: []u8) !usize {
const result = linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len);
switch (linux.E.init(result)) {
switch (linux.errno(result)) {
.SUCCESS => return result,
.INTR => return error.Interrupted,
.AGAIN => return error.WouldBlock,
@ -601,28 +549,17 @@ fn readSyscall(fd: i32, buf: []u8) !usize {
```zig
const windows = std.os.windows;
fn windowsOperation() !void {
const result = windows.kernel32.SomeFunction(...);
if (result == windows.FALSE) {
fn translateLastError() !void {
// Call this immediately after a Win32 API reports failure; another Win32
// call may overwrite the thread's last-error value.
switch (windows.GetLastError()) {
.ERROR_FILE_NOT_FOUND => return error.FileNotFound,
.ERROR_ACCESS_DENIED => return error.AccessDenied,
.FILE_NOT_FOUND => return error.FileNotFound,
.ACCESS_DENIED => return error.AccessDenied,
else => |e| return windows.unexpectedError(e),
}
}
}
```
### Cross-Platform File Descriptor Path
### Retaining a Portable File Path
```zig
fn getFilePath(fd: std.posix.fd_t, allocator: Allocator) ![]u8 {
if (comptime !std.os.isGetFdPathSupportedOnTarget(builtin.os)) {
return error.Unsupported;
}
var buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try std.os.getFdPath(fd, &buf);
return try allocator.dupe(u8, path);
}
```
If later logic needs both a file and its pathname, store an owned copy of the pathname when opening the file. Trying to reconstruct a canonical path from a descriptor is target-specific and can be ambiguous after rename, unlink, mount, or namespace changes.

View File

@ -7,12 +7,12 @@ Zig 0.16 changed priority dequeues to align with unmanaged containers:
- Initialize with `.empty`.
- `add` -> `push`.
- `addSlice` -> `pushSlice`.
- `addUnchecked` -> `pushUnchecked`.
- The old unchecked insertion helper has no public `pushUnchecked` replacement.
- `removeMin` / `removeMinOrNull` -> `popMin`.
- `removeMax` / `removeMaxOrNull` -> `popMax`.
- `removeIndex` -> `popIndex`.
Old examples below may use removed 0.15 names; translate them before using in 0.16 code.
All examples below use the Zig 0.16 unmanaged API. `.empty` leaves `context` undefined; use `initContext` whenever the comparator reads it.
A min-max heap that efficiently supports both min and max extraction. Unlike `PriorityQueue`, you can pop from either end.
@ -35,20 +35,20 @@ fn compare(context: void, a: u32, b: u32) std.math.Order {
const PDQ = std.PriorityDequeue(u32, void, compare);
var dequeue = PDQ.init(allocator, {});
defer dequeue.deinit();
var dequeue = PDQ.initContext({});
defer dequeue.deinit(allocator);
```
## Basic Operations
```zig
// Add elements
try dequeue.add(54);
try dequeue.add(12);
try dequeue.add(7);
try dequeue.push(allocator, 54);
try dequeue.push(allocator, 12);
try dequeue.push(allocator, 7);
// Add multiple
try dequeue.addSlice(&[_]u32{ 1, 2, 3 });
try dequeue.pushSlice(allocator, &[_]u32{ 1, 2, 3 });
// Peek at min/max (doesn't remove)
if (dequeue.peekMin()) |min| {
@ -59,12 +59,8 @@ if (dequeue.peekMax()) |max| {
}
// Remove min/max
const min = dequeue.removeMin(); // asserts non-empty
const max = dequeue.removeMax(); // asserts non-empty
// Safe removal (returns null if empty)
const maybe_min = dequeue.removeMinOrNull();
const maybe_max = dequeue.removeMaxOrNull();
const maybe_min = dequeue.popMin(); // ?T; null if empty
const maybe_max = dequeue.popMax(); // ?T; null if empty
// Size
const n = dequeue.count();
@ -76,21 +72,22 @@ const cap = dequeue.capacity();
```zig
// Take ownership of slice, heapify in place
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
var dequeue = PDQ.fromOwnedSlice(allocator, items, {});
defer dequeue.deinit();
var dequeue = PDQ.fromOwnedSlice(items, {});
defer dequeue.deinit(allocator);
```
## Update Priority
```zig
try dequeue.update(old_value, new_value);
// Error if old_value not found
// Lookup uses comparator equality. Equal-priority duplicates are ambiguous;
// the API does not identify a particular equal element. Errors if none exists.
```
## Remove by Index
```zig
const removed = dequeue.removeIndex(index);
const removed = dequeue.popIndex(index); // asserts in bounds; heap index is not priority rank
```
## Iteration
@ -107,9 +104,9 @@ it.reset();
## Capacity Management
```zig
try dequeue.ensureTotalCapacity(100);
try dequeue.ensureUnusedCapacity(10);
dequeue.shrinkAndFree(new_capacity);
try dequeue.ensureTotalCapacity(allocator, 100);
try dequeue.ensureUnusedCapacity(allocator, 10);
dequeue.shrinkAndFree(allocator, new_capacity);
```
## Context-Based Comparator
@ -122,7 +119,8 @@ fn compareByScore(scores: []const u32, a: usize, b: usize) std.math.Order {
const IndexPDQ = std.PriorityDequeue(usize, []const u32, compareByScore);
const scores = [_]u32{ 50, 30, 80, 20 };
var dequeue = IndexPDQ.init(allocator, &scores);
var dequeue = IndexPDQ.initContext(scores[0..]);
defer dequeue.deinit(allocator);
```
## Complete Example: Bounded Range Tracker
@ -140,15 +138,16 @@ pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
var tracker = RangePDQ.init(gpa.allocator(), {});
defer tracker.deinit();
const allocator = gpa.allocator();
var tracker = RangePDQ.initContext({});
defer tracker.deinit(allocator);
// Add values
try tracker.add(10);
try tracker.add(5);
try tracker.add(20);
try tracker.add(3);
try tracker.add(15);
try tracker.push(allocator, 10);
try tracker.push(allocator, 5);
try tracker.push(allocator, 20);
try tracker.push(allocator, 3);
try tracker.push(allocator, 15);
// Get range without removing
const min = tracker.peekMin().?; // 3
@ -158,8 +157,8 @@ pub fn main() !void {
std.debug.print("Range: {} to {} = {}\n", .{ min, max, range });
// Pop from both ends
_ = tracker.removeMin(); // removes 3
_ = tracker.removeMax(); // removes 20
_ = tracker.popMin(); // removes 3
_ = tracker.popMax(); // removes 20
// New range is 5 to 15
}
@ -177,7 +176,7 @@ pub fn main() !void {
## Notes
- Both `removeMin()` and `removeMax()` are O(log n)
- `peekMin()` is O(1), `peekMax()` is O(1) after first 2 elements
- Both `popMin()` and `popMax()` are O(log n)
- Both nullable peeks are O(1), including empty and one-element deques
- Iterator order is heap array order, not priority order
- Use when you need efficient access to both extremes

View File

@ -8,12 +8,12 @@ Zig 0.16 changed priority queues to align with unmanaged containers:
- Empty queues can use `.empty`.
- `init` -> `initContext` when context is needed.
- `add` -> `push`.
- `addUnchecked` -> `pushUnchecked`.
- The old unchecked insertion helper has no public `pushUnchecked` replacement; reserve and use supported public operations.
- `addSlice` -> `pushSlice`.
- `remove` / `removeOrNull` -> `pop`.
- `removeIndex` -> `popIndex`.
Old examples below may use removed 0.15 names; translate them before using in 0.16 code.
All examples below use the Zig 0.16 unmanaged API.
A binary heap-based priority queue. Efficiently retrieves elements by priority order.
@ -37,8 +37,8 @@ fn lessThan(context: void, a: u32, b: u32) std.math.Order {
const PQ = std.PriorityQueue(u32, void, lessThan);
var queue = PQ.init(allocator, {});
defer queue.deinit();
var queue = PQ.initContext({});
defer queue.deinit(allocator);
```
## Max-Heap
@ -56,12 +56,12 @@ const MaxPQ = std.PriorityQueue(u32, void, greaterThan);
```zig
// Add elements
try queue.add(54);
try queue.add(12);
try queue.add(7);
try queue.push(allocator, 54);
try queue.push(allocator, 12);
try queue.push(allocator, 7);
// Add multiple
try queue.addSlice(&[_]u32{ 1, 2, 3 });
try queue.pushSlice(allocator, &[_]u32{ 1, 2, 3 });
// Peek at highest priority (doesn't remove)
if (queue.peek()) |top| {
@ -69,8 +69,7 @@ if (queue.peek()) |top| {
}
// Remove highest priority
const top = queue.remove(); // asserts non-empty
const maybe = queue.removeOrNull(); // returns ?T
const maybe_top = queue.pop(); // ?T; null when empty
// Size
const n = queue.count();
@ -82,8 +81,8 @@ const cap = queue.capacity();
```zig
// Take ownership of slice, heapify in place
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
var queue = PQ.fromOwnedSlice(allocator, items, {});
defer queue.deinit();
var queue = PQ.fromOwnedSlice(items, {});
defer queue.deinit(allocator);
// Now queue is a valid heap
```
@ -92,14 +91,15 @@ defer queue.deinit();
```zig
// Change priority of existing element
try queue.update(old_value, new_value);
// Error if old_value not found
// Selection uses comparator equality. If duplicates compare equal, which one
// is updated is not a stable identity guarantee. Errors if no equal value exists.
```
## Remove by Index
```zig
// Remove element at specific position (not priority order)
const removed = queue.removeIndex(index);
const removed = queue.popIndex(index); // asserts index < count; heap index is not priority rank
```
## Iteration (Non-Priority Order)
@ -113,14 +113,16 @@ while (it.next()) |elem| {
it.reset(); // restart iteration
```
Any queue mutation invalidates the iterator.
## Capacity Management
```zig
try queue.ensureTotalCapacity(100);
try queue.ensureUnusedCapacity(10);
queue.shrinkAndFree(new_capacity);
try queue.ensureTotalCapacity(allocator, 100);
try queue.ensureUnusedCapacity(allocator, 10);
queue.shrinkAndFree(allocator, new_capacity);
queue.clearRetainingCapacity();
queue.clearAndFree();
queue.clearAndFree(allocator);
```
## Context-Based Comparator
@ -135,16 +137,16 @@ fn compareByScore(scores: []const u32, a: usize, b: usize) std.math.Order {
const IndexPQ = std.PriorityQueue(usize, []const u32, compareByScore);
const scores = [_]u32{ 50, 30, 80, 20 };
var queue = IndexPQ.init(allocator, &scores);
defer queue.deinit();
var queue = IndexPQ.initContext(scores[0..]);
defer queue.deinit(allocator);
try queue.add(0); // score 50
try queue.add(1); // score 30
try queue.add(2); // score 80
try queue.add(3); // score 20
try queue.push(allocator, 0); // score 50
try queue.push(allocator, 1); // score 30
try queue.push(allocator, 2); // score 80
try queue.push(allocator, 3); // score 20
// Removes index 3 (score 20 is smallest)
const best = queue.remove(); // 3
const best = queue.pop().?; // 3
```
## Complete Example: Task Scheduler
@ -167,14 +169,15 @@ pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
var tasks = TaskQueue.init(gpa.allocator(), {});
defer tasks.deinit();
const allocator = gpa.allocator();
var tasks = TaskQueue.initContext({});
defer tasks.deinit(allocator);
try tasks.add(.{ .name = "low priority", .priority = 100 });
try tasks.add(.{ .name = "urgent", .priority = 1 });
try tasks.add(.{ .name = "medium", .priority = 50 });
try tasks.push(allocator, .{ .name = "low priority", .priority = 100 });
try tasks.push(allocator, .{ .name = "urgent", .priority = 1 });
try tasks.push(allocator, .{ .name = "medium", .priority = 50 });
while (tasks.removeOrNull()) |task| {
while (tasks.pop()) |task| {
std.debug.print("Processing: {s}\n", .{task.name});
}
// Output:
@ -187,6 +190,6 @@ pub fn main() !void {
## Notes
- Heap property: parent has higher priority than children
- `remove()` is O(log n), `peek()` is O(1)
- `pop()` is nullable and O(log n); `peek()` is nullable and O(1)
- Iterator order is NOT priority order (it's heap array order)
- Use `removeOrNull()` for safe extraction from potentially empty queue
- Use `pop()` for extraction from a potentially empty queue

View File

@ -78,6 +78,10 @@ const cwd = try std.process.currentPathAlloc(io, gpa);
defer gpa.free(cwd);
```
On Windows these paths use WTF-8. On other platforms they are opaque path bytes
with no guaranteed text encoding; do not assume UTF-8 when displaying or
parsing them.
Do not add new `std.process.getCwd*` callsites.
## Run and Capture Output
@ -113,6 +117,10 @@ Important options:
- `disable_aslr`
- `timeout`
Supplying `.environ_map` replaces the child's environment, but its `PATH` does
not resolve `argv[0]`; executable lookup still uses the parent environment.
Use `std.process.spawnPath` for deterministic directory-relative resolution.
## Spawn Child Process
Use `std.process.spawn(io, options)`. `std.process.Child.init` is not the 0.16 pattern.
@ -214,6 +222,8 @@ Memory locking/protection APIs moved under `std.process`:
- `std.process.unlockMemory`
- `std.process.lockMemoryAll`
- `std.process.unlockMemoryAll`
- `std.process.MemoryProtection`
- `std.process.protectMemory`
Use them only for explicit platform/security needs.

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
System entropy moved under `std.Io`.
Random bytes moved under `std.Io`. `io.random` is the process CSPRNG stream and may use a less-secure seeding fallback on platforms where fresh system entropy is unavailable. `io.randomSecure` requests fresh external entropy and reports failure.
```zig
var seed: [32]u8 = undefined;
@ -18,7 +18,7 @@ const rng = source.interface();
Use `io.randomSecure(&bytes)` when fresh OS-backed secure entropy is required and errors must be surfaced.
Old examples below may mention `std.crypto.random`; use the `std.Io` patterns above in new 0.16 code.
The examples below use those `std.Io` patterns for current Zig 0.16 code.
## Quick Reference
@ -37,10 +37,8 @@ Old examples below may mention `std.crypto.random`; use the `std.Io` patterns ab
```
Need crypto security?
├─ Yes → ChaCha (DefaultCsprng) or Ascon
└─ No → Need speed?
├─ Yes → Xoshiro256 (DefaultPrng), Sfc64, or RomuTrio
└─ No → Pcg (smaller state), Xoroshiro128
|- Yes -> use `io.random`/`io.randomSecure`, or seed ChaCha/Ascon securely
`- No -> use Xoshiro256 (DefaultPrng), Sfc64, RomuTrio, or Pcg
```
| PRNG | State | Output | Use Case |
@ -50,8 +48,8 @@ Need crypto security?
| `Pcg` | 128-bit | 32-bit | Compact, statistically excellent |
| `Sfc64` | 256-bit | 64-bit | Very fast |
| `RomuTrio` | 192-bit | 64-bit | Fast, small code size |
| `Isaac64` | 8KB | 64-bit | Cryptographic-ish (prefer ChaCha) |
| `ChaCha` | 512-bit | stream | CSPRNG, forward secure |
| `Isaac64` | 8KB | 64-bit | Non-default PRNG; do not select it as the documented CSPRNG path |
| `ChaCha` | Internal state | stream | CSPRNG, forward secure |
| `Ascon` | 320-bit | stream | CSPRNG, lightweight |
## Basic Usage
@ -79,14 +77,13 @@ pub fn main() void {
```zig
const std = @import("std");
pub fn main() void {
// Use std.crypto.random for system entropy
const secure = std.crypto.random;
pub fn main(init: std.process.Init) void {
const io = init.io;
var key: [32]u8 = undefined;
secure.bytes(&key); // fill with cryptographically secure random bytes
io.random(&key); // process CSPRNG stream
const token = secure.int(u64);
var source: std.Random.IoSource = .{ .io = io };
const token = source.interface().int(u64);
}
```
@ -94,7 +91,7 @@ pub fn main() void {
```zig
var seed: u64 = undefined;
std.crypto.random.bytes(std.mem.asBytes(&seed));
io.random(std.mem.asBytes(&seed));
var prng = std.Random.DefaultPrng.init(seed);
```
@ -115,7 +112,7 @@ prng.jump();
```zig
// Requires 32-byte secret seed
var secret_seed: [std.Random.ChaCha.secret_seed_length]u8 = undefined;
std.crypto.random.bytes(&secret_seed);
try io.randomSecure(&secret_seed); // or io.random when process-CSPRNG semantics suffice
var csprng = std.Random.ChaCha.init(secret_seed);
const random = csprng.random();
@ -257,10 +254,13 @@ const int_weights = [_]u32{ 5, 3, 2 };
const int_choice = random.weightedIndex(u32, &int_weights);
```
Weights must be finite/nonnegative for floating-point use (or valid nonnegative integer weights), their sum must be positive, and accumulation must remain representable. Validate untrusted weight arrays before calling.
### Random Element from Slice
```zig
fn randomElement(comptime T: type, random: std.Random, slice: []const T) T {
std.debug.assert(slice.len != 0);
const index = random.uintLessThan(usize, slice.len);
return slice[index];
}
@ -272,10 +272,11 @@ const color = randomElement([]const u8, random, &colors);
### Random Sample (Without Replacement)
```zig
fn sample(comptime T: type, random: std.Random, source: []const T, dest: []T) void {
fn sample(comptime T: type, random: std.Random, source: []const T, dest: []T, indices: []usize) void {
std.debug.assert(dest.len <= source.len);
std.debug.assert(indices.len >= source.len);
// Fisher-Yates partial shuffle
var indices: [source.len]usize = undefined;
for (&indices, 0..) |*idx, i| idx.* = i;
for (indices[0..source.len], 0..) |*idx, i| idx.* = i;
for (dest, 0..) |*d, i| {
const j = random.intRangeLessThan(usize, i, source.len);
@ -304,10 +305,10 @@ std.debug.assert(prng1.random().int(u64) == prng2.random().int(u64));
```zig
threadlocal var tls_prng: ?std.Random.DefaultPrng = null;
fn getThreadRandom() std.Random {
fn getThreadRandom(io: std.Io) std.Random {
if (tls_prng == null) {
var seed: u64 = undefined;
std.crypto.random.bytes(std.mem.asBytes(&seed));
io.random(std.mem.asBytes(&seed));
tls_prng = std.Random.DefaultPrng.init(seed);
}
return tls_prng.?.random();
@ -318,6 +319,7 @@ fn getThreadRandom() std.Random {
```zig
fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator) ![]std.Random.Xoshiro256 {
std.debug.assert(n != 0);
const prngs = try allocator.alloc(std.Random.Xoshiro256, n);
prngs[0] = std.Random.Xoshiro256.init(base_seed);
@ -330,6 +332,8 @@ fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator)
}
```
The caller owns the returned slice and must free it with the same allocator.
### Monte Carlo Simulation
```zig
@ -357,7 +361,8 @@ fn generatePassword(random: std.Random, buf: []u8) void {
// Usage
var password: [16]u8 = undefined;
generatePassword(std.crypto.random, &password);
var source: std.Random.IoSource = .{ .io = io };
generatePassword(source.interface(), &password);
```
### Gaussian Random with Box-Muller
@ -406,7 +411,7 @@ const MyPrng = struct {
- `DefaultPrng` is `Xoshiro256` - fast, high quality, not cryptographic
- `DefaultCsprng` is `ChaCha` - cryptographically secure with forward secrecy
- For crypto: use `std.crypto.random` which provides system entropy
- For crypto: use caller-provided `std.Io` randomness, choosing `randomSecure` when fresh external entropy and explicit errors are required
- `uintLessThan`/`intRangeLessThan` may reject values (not constant-time)
- Use biased variants (`*Biased`) for timing-sensitive applications
- `jump()` on Xoshiro256 advances 2^128 steps for parallel streams

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
`std.SegmentedList` was removed in Zig 0.16. Do not use the API below in new code. Choose a project-owned segmented container, an arena-backed structure, an index-based `std.ArrayList`, or another stable-handle design depending on the ownership/lifetime requirement.
`std.SegmentedList` is not exported by the Zig 0.16 standard library, and there is no compatibility alias with these methods. The material below is retained as a **legacy API inventory** for migration and source archaeology; none of its snippets compile against Zig 0.16 as written. Choose a project-owned segmented container, an arena-backed structure, an index-based `std.ArrayList`, or another stable-handle design depending on the ownership/lifetime requirement.
A dynamic list where element pointers remain stable across growth. Unlike ArrayList, appending never invalidates existing pointers. Elements are stored in exponentially-sized segments.
Historically, the implementation described here was a dynamic list whose element pointers remained stable across growth. Unlike `ArrayList`, appending did not invalidate existing pointers, and elements were stored in exponentially sized segments. These are historical behavior and layout notes, not Zig 0.16 guarantees.
## When to Use
@ -15,11 +15,11 @@ A dynamic list where element pointers remain stable across growth. Unlike ArrayL
## Trade-offs
- Elements not contiguous (most are, but not guaranteed)
- Elements were not globally contiguous; each individual segment was contiguous
- O(log n) random access (vs O(1) for ArrayList)
- Higher per-element overhead
## Initialization
## Legacy Initialization
```zig
// Without preallocation
@ -32,7 +32,7 @@ var list = std.SegmentedList(i32, 16){};
defer list.deinit(allocator);
```
## Basic Operations
## Legacy Basic Operations
```zig
// Append (pointer remains valid forever)
@ -84,7 +84,7 @@ if (it.peek()) |ptr| {
it.set(50);
```
## Capacity Management
## Legacy Capacity Management
```zig
// Grow capacity
@ -125,7 +125,7 @@ prealloc=4: prealloc: 4 elements (inline)
...
```
## Common Pattern: Object Pool with Stable References
## Legacy Pattern: Object Pool with Stable References
```zig
const Object = struct {

View File

@ -171,7 +171,7 @@ const any_true = @reduce(.Or, mask); // true
### suggestVectorLength
Query the optimal vector length for the current CPU:
Query the suggested vector length for the current target. This is a comptime heuristic: its element type must be comptime-known.
```zig
const std = @import("std");
@ -187,7 +187,7 @@ Returns `null` if scalars are recommended (no SIMD benefit).
### suggestVectorLengthForCpu
Query optimal length for a specific CPU target:
Query the suggested length for a specific CPU target. Both the element type and `std.Target.Cpu` argument are comptime parameters:
```zig
const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2;
@ -196,7 +196,7 @@ const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2;
**Architecture support:**
- **x86**: SSE (128-bit), AVX2 (256-bit), AVX-512 (512-bit)
- **ARM**: NEON (128-bit)
- **AArch64**: NEON (128-bit), SVE (128-bit default)
- **AArch64**: SVE (256-bit heuristic), otherwise NEON (128-bit)
- **RISC-V**: V extension (32-bit to 65536-bit via zvl* features)
- **WebAssembly**: simd128 (128-bit)
- **PowerPC**: AltiVec (128-bit)
@ -289,7 +289,7 @@ const result = std.simd.deinterlace(2, interleaved);
### extract - Get Subvector
Extract a contiguous slice of elements:
Extract a contiguous slice of elements. `first` and `count` are comptime-known:
```zig
const vec: @Vector(8, u32) = .{ 0, 1, 2, 3, 4, 5, 6, 7 };
@ -299,7 +299,7 @@ const slice = std.simd.extract(vec, 2, 3);
### shiftElementsLeft / shiftElementsRight
Shift elements, filling with a value:
Shift elements, filling with a value. The shift amount is comptime-known:
```zig
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
@ -315,7 +315,7 @@ const right = std.simd.shiftElementsRight(vec, 2, 999);
### rotateElementsLeft / rotateElementsRight
Circular rotation (elements wrap around):
Circular rotation (elements wrap around). The rotation amount is comptime-known:
```zig
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
@ -339,7 +339,7 @@ const reversed = std.simd.reverseOrder(vec);
### mergeShift
Combine two vectors and extract a shifted window:
Combine two vectors and extract a shifted window. The shift amount is comptime-known:
```zig
const a: @Vector(4, u32) = .{ 1, 2, 3, 4 };
@ -400,7 +400,7 @@ const count = std.simd.countElementsWithValue(vec, 4); // 3
### prefixScan
Compute cumulative operations across vector lanes:
Compute cumulative operations across vector lanes. This is an O(log N), non-linear associative scan; floating-point results can differ from a scalar left-to-right scan because the grouping and rounding differ.
```zig
const vec: @Vector(4, i32) = .{ 11, 23, 9, -21 };
@ -443,7 +443,7 @@ const rev = std.simd.prefixScan(.Add, -1, vec);
### prefixScanWithFunc
Use a custom associative function:
Use a custom associative function. The callback may return an error union; choose `ErrorType` accordingly and use `try` on `prefixScanWithFunc` to propagate callback errors. Passing `void` selects the non-erroring form:
```zig
fn myMax(a: @Vector(4, f32), b: @Vector(4, f32)) @Vector(4, f32) {
@ -576,11 +576,11 @@ fn atan2Simd(y: @Vector(4, f64), x: @Vector(4, f64)) @Vector(4, f64) {
## Performance Notes
- **Optimal vector size:** Use `suggestVectorLength` for portable code; don't hardcode lane counts. Powers of two (2-64) are most efficient
- **Compilation:** Short vectors → single SIMD instruction; long vectors → multiple instructions; no SIMD → scalar fallback
- **Compilation:** Lowering depends on the target, vector width, operation, and optimizer; inspect generated code for performance-critical paths
- **Alignment:** Vectors are automatically aligned; use `@alignCast` when loading from byte pointers
- **Branching:** Replace scalar branches with `@select` for branchless SIMD code. `and`/`or` keywords don't work on bool vectors
- **Reductions:** `@reduce` operations break SIMD parallelism; minimize their use in hot paths
- **Memory layout:** Prefer Struct-of-Arrays over Array-of-Structs for better vectorization
- **Cache tiling:** For large datasets, process in cache-sized chunks (e.g., 64 elements) to maintain data locality
- **Fused operations:** Use `@mulAdd(a, b, c)` for `(a * b) + c` - rounds once, more accurate
- **Fused operations:** For floating-point `T`, use `@mulAdd(T, a, b, c)` for a fused `(a * b) + c` with one final rounding
- **MIPS limitation:** `interlace` and `prefixScan` don't work on MIPS architecture

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.
## Quick Reference
| Function | Stable | Complexity | When to Use |
| Function | Stable | Worst-case | When to Use |
|----------|--------|------------|-------------|
| `block` | Yes | O(n log n) | Default choice when stability matters |
| `pdq` | No | O(n log n) | Default choice when stability doesn't matter |
| `insertion` | Yes | O(n²) | Small arrays (<20), nearly sorted data |
| `insertion` | Yes | O(n²) | Small arrays or nearly sorted data |
| `heap` | No | O(n log n) | Guaranteed worst-case, no recursion |
## Comparator Functions
@ -33,7 +33,7 @@ const desc_i32 = std.sort.desc(i32); // descending
```zig
var items = [_]i32{ 5, 2, 8, 1, 9 };
// Unstable sort (fastest general-purpose)
// General-purpose unstable sort
std.sort.pdq(i32, &items, {}, std.sort.asc(i32));
// items = [1, 2, 5, 8, 9]
@ -188,7 +188,7 @@ var items = [_]i32{ 5, 2, 8, 1 };
const ctx = Context{ .items = &items };
// Sort a subrange using indices
std.sort.pdqContext(1, 4, ctx); // sort indices 1..4
std.sort.pdqContext(1, 4, ctx); // sort end-exclusive range [1, 4): indices 1, 2, 3
// items = [5, 1, 2, 8]
```
@ -220,15 +220,19 @@ std.sort.block(Item, &items, {}, byPriority);
## Algorithm Selection
- **`pdq`** (Pattern-Defeating Quicksort): Best general-purpose unstable sort. Adapts to input patterns, falls back to heapsort for worst cases.
- **`block`**: Best general-purpose stable sort. Preserves relative order of equal elements.
- **`pdq`** (Pattern-Defeating Quicksort): General-purpose unstable sort. Adapts to input patterns, falls back to heapsort for worst cases.
- **`block`**: General-purpose stable sort. Preserves relative order of equal elements.
- **`insertion`**: O(n) on nearly sorted data. Use for small arrays or as final pass.
- **`heap`**: Guaranteed O(n log n) with O(1) memory. No recursion, predictable performance.
## Notes
- All sorts are **in-place** with O(1) or O(log n) auxiliary memory
- Comparators must define strict weak ordering (if `a < b` then not `b < a`)
- Comparators must define a strict weak ordering: irreflexive and transitive,
with a transitive equivalence relation for elements where neither compares
less than the other. `block` is limited to less-than/greater-than style
comparisons as documented by that algorithm.
- `asc`/`desc` helpers work with any type supporting `<` operator
- Binary search functions require the array to already be sorted
- `equalRange` is more efficient than calling `lowerBound` + `upperBound` separately
- `equalRange` returns both bounds in O(log n); do not assume it is faster than
calling the two bound helpers without measurement.

View File

@ -1,6 +1,6 @@
# std.StaticStringMap
Compile-time optimized string lookup. Perfect hash for small, fixed sets of string keys.
Compile-time optimized lookup for small, fixed sets of string keys. Keys are grouped by length; lookup quickly rejects absent lengths and then compares candidates in the matching length group. This is not a perfect hash.
## When to Use
@ -8,7 +8,7 @@ Compile-time optimized string lookup. Perfect hash for small, fixed sets of stri
- Command/option parsing
- Static configuration keys
- When string set is known at compile time
- Very fast O(1) lookups by string length
- Fast length-based narrowing, followed by a linear scan of keys with the queried length (plus string comparison cost)
## Basic Usage
@ -52,7 +52,7 @@ if (reserved.has("break")) {
}
```
## Case-Insensitive Lookup
## ASCII Case-Insensitive Lookup
```zig
const commands = std.StaticStringMapWithEql(
@ -86,6 +86,8 @@ defer map.deinit(allocator);
_ = map.get("two"); // 2
```
Runtime initialization copies the entry arrays and key slices, but not the bytes behind each key slice. Keep the key backing storage alive and unchanged for the lifetime of the map.
## Get Index
```zig

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
Old examples below may contain 0.15 `std.fs` or `std.io` patterns; translate them to `std.Io` before using in new code. The 0.16 release notes also call out path traversal sanitization in `tar.extract`.
The examples below use Zig 0.16's explicit `std.Io` APIs. `tar.extract` also sanitizes archive paths to prevent traversal outside the destination.
## Table of Contents
- [Module Structure](#module-structure)
@ -21,8 +21,8 @@ std.tar.Iterator // Iterate over entries in tar archive
std.tar.Writer // Create tar archives
std.tar.Diagnostics // Collect errors during extraction
std.tar.FileKind // .file, .directory, .sym_link
std.tar.PipeOptions // Options for pipeToFileSystem
std.tar.pipeToFileSystem() // Extract archive to directory
std.tar.ExtractOptions // Options for extract
std.tar.extract() // Extract archive to directory
```
## Reading Tar Archives
@ -36,8 +36,8 @@ const data = @embedFile("archive.tar");
var reader: std.Io.Reader = .fixed(data);
// Buffers must be provided by caller
var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var it: std.tar.Iterator = .init(&reader, .{
.file_name_buffer = &file_name_buffer,
@ -70,16 +70,16 @@ pub const File = struct {
### Reading File Contents
File content must be read before calling `next()` again:
Read or copy file content before calling `next()` again if the content is needed. If content remains unread, `next()` discards it while advancing rather than failing:
```zig
while (try it.next()) |file| {
if (file.kind == .file) {
// Option 1: Stream to writer
var buf: [1024]u8 = undefined;
var output_file = try dir.createFile(file.name, .{});
defer output_file.close();
var file_writer = output_file.writer(&buf);
var output_file = try dir.createFile(io, file.name, .{});
defer output_file.close(io);
var file_writer = output_file.writer(io, &buf);
try it.streamRemaining(file, &file_writer.interface);
try file_writer.interface.flush();
@ -104,7 +104,7 @@ pub const Options = struct {
## Extracting to Filesystem
### pipeToFileSystem
### extract
Extract entire archive to a directory:
@ -112,7 +112,7 @@ Extract entire archive to a directory:
const data = @embedFile("archive.tar");
var reader: std.Io.Reader = .fixed(data);
try std.tar.pipeToFileSystem(std.fs.cwd(), &reader, .{
try std.tar.extract(io, std.Io.Dir.cwd(), &reader, .{
.strip_components = 1, // remove leading path component
.mode_mode = .executable_bit_only,
.exclude_empty_directories = false,
@ -122,19 +122,19 @@ try std.tar.pipeToFileSystem(std.fs.cwd(), &reader, .{
### From File
```zig
const file = try std.fs.cwd().openFile("archive.tar", .{});
defer file.close();
const file = try std.Io.Dir.cwd().openFile(io, "archive.tar", .{});
defer file.close(io);
var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf);
var file_reader = file.reader(io, &buf);
try std.tar.pipeToFileSystem(output_dir, &file_reader.interface, .{});
try std.tar.extract(io, output_dir, &file_reader.interface, .{});
```
### PipeOptions
### ExtractOptions
```zig
pub const PipeOptions = struct {
pub const ExtractOptions = struct {
strip_components: u32 = 0, // directories to strip from paths
mode_mode: ModeMode = .executable_bit_only,
exclude_empty_directories: bool = false,
@ -180,24 +180,26 @@ try w.writeLink("latest", "v1.0", .{});
const tar_bytes = output.written();
```
`setRoot` first emits a directory entry for a non-empty root and then applies that prefix to subsequent entries.
### Writing from File
```zig
var output_file = try std.fs.cwd().createFile("archive.tar", .{});
defer output_file.close();
var output_file = try std.Io.Dir.cwd().createFile(io, "archive.tar", .{});
defer output_file.close(io);
var buf: [4096]u8 = undefined;
var file_writer = output_file.writer(&buf);
var file_writer = output_file.writer(io, &buf);
var w: std.tar.Writer = .{ .underlying_writer = &file_writer.interface };
// Write file from disk
var src_file = try std.fs.cwd().openFile("data.txt", .{});
defer src_file.close();
var src_file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
defer src_file.close(io);
var src_buf: [4096]u8 = undefined;
var src_reader = src_file.reader(&src_buf);
const stat = try src_file.stat();
var src_reader = src_file.reader(io, &src_buf);
const stat = try src_file.stat(io);
try w.writeFile("data.txt", &src_reader, stat.mtime);
try w.writeFileTimestamp("data.txt", &src_reader, stat.mtime);
try file_writer.interface.flush();
```
@ -226,12 +228,16 @@ pub fn writeFileBytes(w: *Writer, sub_path: []const u8, content: []const u8, opt
pub fn writeFileStream(w: *Writer, sub_path: []const u8, size: u64, reader: *std.Io.Reader, options: Options) WriteFileStreamError!void
// Write file from file reader
pub fn writeFile(w: *Writer, sub_path: []const u8, file_reader: *std.fs.File.Reader, stat_mtime: i128) WriteFileError!void
pub fn writeFile(w: *Writer, sub_path: []const u8, file_reader: *std.Io.File.Reader, mtime_seconds: u64) WriteFileError!void
// Convenience overload for std.Io.Timestamp
pub fn writeFileTimestamp(w: *Writer, sub_path: []const u8, file_reader: *std.Io.File.Reader, mtime: std.Io.Timestamp) WriteFileError!void
// Write symbolic link
pub fn writeLink(w: *Writer, sub_path: []const u8, link_name: []const u8, options: Options) Error!void
// Write two zero blocks (optional, per spec)
// Write two zero blocks. The stdlib recommends not calling this: readers must
// accept archives without them, and omitting them avoids unnecessary output.
pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void
```
@ -240,7 +246,7 @@ pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void
```zig
pub const Options = struct {
mode: u32 = 0, // POSIX mode (0 = default: 0o664 for files)
mtime: u64 = 0, // modification time (0 = current time)
mtime: u64 = 0, // seconds since POSIX epoch; zero is written as zero
};
```
@ -254,7 +260,7 @@ Collect errors instead of failing immediately:
var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator };
defer diagnostics.deinit();
std.tar.pipeToFileSystem(dir, &reader, .{
std.tar.extract(io, dir, &reader, .{
.diagnostics = &diagnostics,
}) catch |err| {
// Some errors are still fatal
@ -283,10 +289,12 @@ for (diagnostics.errors.items) |item| {
std.debug.print("Root dir: {s}, entries: {d}\n", .{ diagnostics.root_dir, diagnostics.entries });
```
### Diagnostics.Error Types
### Diagnostics.Error Variants
```zig
pub const Error = union(enum) {
```text
// Descriptive shape only: the unsupported_file_type payload contains a
// private header-kind enum, so std.tar.Header.Kind is not a public type name.
union(enum) {
unable_to_create_sym_link: struct {
code: anyerror,
file_name: []const u8,
@ -298,7 +306,7 @@ pub const Error = union(enum) {
},
unsupported_file_type: struct {
file_name: []const u8,
file_type: Header.Kind,
file_type: /* private archive header kind */,
},
components_outside_stripped_prefix: struct {
file_name: []const u8,
@ -311,13 +319,13 @@ pub const Error = union(enum) {
### Extract and Process Archive
```zig
fn extractTar(allocator: Allocator, tar_data: []const u8, dest: std.fs.Dir) !void {
fn extractTar(io: std.Io, allocator: Allocator, tar_data: []const u8, dest: std.Io.Dir) !void {
var reader: std.Io.Reader = .fixed(tar_data);
var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator };
defer diagnostics.deinit();
try std.tar.pipeToFileSystem(dest, &reader, .{
try std.tar.extract(io, dest, &reader, .{
.strip_components = 1,
.diagnostics = &diagnostics,
});
@ -337,8 +345,8 @@ fn listTar(allocator: Allocator, tar_data: []const u8) !void {
_ = allocator;
var reader: std.Io.Reader = .fixed(tar_data);
var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var it: std.tar.Iterator = .init(&reader, .{
.file_name_buffer = &file_name_buffer,
@ -365,7 +373,7 @@ fn listTar(allocator: Allocator, tar_data: []const u8) !void {
### Create Archive from Directory
```zig
fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []const u8) ![]u8 {
fn createTarFromDir(io: std.Io, allocator: Allocator, source_dir: std.Io.Dir, root_name: []const u8) ![]u8 {
var output: std.Io.Writer.Allocating = .init(allocator);
errdefer output.deinit();
@ -375,21 +383,21 @@ fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []c
var walker = try source_dir.walk(allocator);
defer walker.deinit();
while (try walker.next()) |entry| {
while (try walker.next(io)) |entry| {
switch (entry.kind) {
.directory => try w.writeDir(entry.path, .{}),
.file => {
var file = try entry.dir.openFile(entry.basename, .{});
defer file.close();
var file = try entry.dir.openFile(io, entry.basename, .{});
defer file.close(io);
var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf);
const stat = try file.stat();
try w.writeFile(entry.path, &file_reader, stat.mtime);
var file_reader = file.reader(io, &buf);
const stat = try file.stat(io);
try w.writeFileTimestamp(entry.path, &file_reader, stat.mtime);
},
.sym_link => {
var link_buf: [std.fs.max_path_bytes]u8 = undefined;
const target = try entry.dir.readLink(entry.basename, &link_buf);
try w.writeLink(entry.path, target, .{});
var link_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
const target_len = try entry.dir.readLink(io, entry.basename, &link_buf);
try w.writeLink(entry.path, link_buf[0..target_len], .{});
},
else => {}, // skip special files
}
@ -405,8 +413,8 @@ fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []c
fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocator) !?[]u8 {
var reader: std.Io.Reader = .fixed(tar_data);
var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
var it: std.tar.Iterator = .init(&reader, .{
.file_name_buffer = &file_name_buffer,
@ -427,10 +435,10 @@ fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocat
## Supported Features
**Formats**: POSIX ustar, GNU long name/link extensions, pax extended headers
**Formats**: A deliberately non-comprehensive reader/writer for POSIX ustar, GNU long name/link extensions, and selected PAX attributes (`path`, `linkpath`, and `size`). Global PAX headers are ignored.
**Entry types**: Regular files, directories, symbolic links
**Not supported**: Hard links, device nodes, FIFOs, sparse files (logged via diagnostics)
**Not supported**: Hard links, device nodes, FIFOs, sparse files, and other special entries. With diagnostics supplied, unsupported entries can be recorded; without diagnostics, extraction returns `error.TarUnsupportedHeader`.
**Path handling**: Automatic prefix/name splitting, GNU extended headers for paths > 256 bytes

View File

@ -53,7 +53,7 @@ try testing.expectEqualSlices(u32, &[_]u32{1, 2, 3}, result_slice);
// Sentinel-terminated slice equality
try testing.expectEqualSentinel(u8, 0, expected_cstr, actual_cstr);
// Deep equality (recursively compares structs, arrays, pointers)
// Deep equality (recursively compares supported structs, arrays, and pointers)
try testing.expectEqualDeep(expected_struct, actual_struct);
// Float comparison (absolute tolerance)
@ -68,7 +68,7 @@ try testing.expectApproxEqRel(@as(f64, 100.0), result, 0.01);
```zig
const Point = struct { x: i32, y: i32 };
// expectEqual - compares by value for primitives, by identity for pointers
// expectEqual - recursively compares structs/arrays, but pointer and slice identity
const p1 = Point{ .x = 1, .y = 2 };
const p2 = Point{ .x = 1, .y = 2 };
try testing.expectEqual(p1, p2); // OK - structs compared field-by-field
@ -78,11 +78,13 @@ const a = [_]u8{ 1, 2, 3 };
const b = [_]u8{ 1, 2, 3 };
// testing.expectEqual(&a, &b); // FAILS - different pointers
// expectEqualDeep - follows pointers, compares contents
// expectEqualDeep - follows single-item pointers and compares supported contents
try testing.expectEqualDeep(&a, &b); // OK - compares contents
try testing.expectEqualDeep("abc", "abc"); // OK
```
`expectEqualDeep` is not cycle-aware: self-referential values can recurse indefinitely. C pointers, many-item pointers, function pointers, and opaque pointers are compared by identity rather than dereferenced.
## Error Assertions
```zig
@ -164,7 +166,7 @@ std.debug.print("Deallocations: {}\n", .{failing.deallocations});
## Exhaustive Allocation Failure Testing
`checkAllAllocationFailures` tests that your code handles `OutOfMemory` at every allocation point without leaking:
`checkAllAllocationFailures` tests that your code handles `OutOfMemory` at every allocation point without leaking. The tested function must take an allocator as its first argument and return `!void`; reset any shared state between runs:
```zig
fn myFunction(allocator: std.mem.Allocator, size: usize) !void {
@ -190,11 +192,13 @@ test "no leaks on allocation failure" {
2. Runs N more times, failing allocation 0, then 1, then 2...
3. Verifies `OutOfMemory` is returned and no memory leaked
**Errors returned:**
**Harness-specific errors include:**
- `error.MemoryLeakDetected` - allocation failed but memory wasn't freed
- `error.SwallowedOutOfMemoryError` - `OutOfMemory` was caught but not propagated
- `error.NondeterministicMemoryUsage` - allocation count varies between runs
Other errors returned by the tested function are propagated unchanged.
## Temporary Directory
Create an isolated temp directory for file system tests:
@ -205,12 +209,18 @@ test "file operations" {
defer tmp.cleanup();
// Write and read files
var file = try tmp.dir.createFile("test.txt", .{});
defer file.close();
try file.writeAll("hello");
const io = std.testing.io;
{
const file = try tmp.dir.createFile(io, "test.txt", .{});
defer file.close(io);
var write_buf: [256]u8 = undefined;
var file_writer = file.writer(io, &write_buf);
try file_writer.interface.writeAll("hello");
try file_writer.interface.flush();
}
// Use tmp.dir for all operations
const content = try tmp.dir.readFileAlloc(std.testing.allocator, "test.txt", 1024);
const content = try tmp.dir.readFileAlloc(io, "test.txt", std.testing.allocator, .limited(1024));
defer std.testing.allocator.free(content);
try testing.expectEqualStrings("hello", content);
}
@ -237,10 +247,9 @@ comptime {
std.testing.refAllDecls(@This());
}
// Recursive version for nested types
comptime {
std.testing.refAllDeclsRecursive(@This());
}
// refAllDecls visits the immediate declarations of the supplied type.
// Zig 0.16 has no std.testing.refAllDeclsRecursive helper; recurse through
// selected nested types explicitly when that is part of the test's intent.
```
## Skip Tests
@ -265,7 +274,7 @@ test "skip if feature unavailable" {
```zig
test "with logging" {
// Only shown when test fails or with --verbose
// std.debug.print writes directly to stderr; it is not gated by log_level.
std.debug.print("Debug info: {}\n", .{value});
}
@ -294,8 +303,9 @@ test "fuzz parser" {
try std.testing.fuzz(
{}, // context (passed to test function)
struct {
fn testOne(_: void, input: []const u8) !void {
// This runs with many different inputs
fn testOne(_: void, smith: *std.testing.Smith) !void {
var storage: [4096]u8 = undefined;
const input = storage[0..smith.slice(&storage)];
_ = myParser.parse(input) catch |err| switch (err) {
error.InvalidInput => return, // expected
else => return err,
@ -376,6 +386,5 @@ test "arena for test allocations" {
zig build test # Run all tests
zig test src/lib.zig # Test single file
zig test --test-filter "name" # Filter by name substring
zig test -fsummary # Show test summary
zig test --verbose # Show debug output
zig test --help # List options supported by this Zig version
```

View File

@ -26,7 +26,6 @@ Useful thread utilities:
```zig
const id = std.Thread.getCurrentId();
const cpu_count = std.Thread.getCpuCount() catch 1;
std.Thread.sleep(10 * std.time.ns_per_ms);
std.Thread.yield() catch {};
```
@ -49,7 +48,7 @@ Release-note migration map:
| `std.Thread.RwLock` | `std.Io.RwLock` |
| `std.Thread.ResetEvent` | `std.Io.Event` |
| `std.Thread.WaitGroup` | `std.Io.Group` |
| `std.Thread.Futex` | `std.Io.Futex` |
| `std.Thread.Futex` operations | `io.futexWait` / `io.futexWaitTimeout` / `io.futexWaitUncancelable` / `io.futexWake` |
Lock-free atomics do not need `std.Io`.
@ -68,7 +67,9 @@ fn increment(io: std.Io) !void {
}
```
Use uncancelable locking for short critical sections where interruption would corrupt state or skip required cleanup:
Use uncancelable locking when waiting to acquire the lock must not be a
cancelation point. Cancelation does not interrupt an already-entered critical
section:
```zig
mutex.lockUncancelable(io);
@ -186,7 +187,8 @@ Rules:
## Application Guidance
- Queues, allocators, registries, timers, and worker systems that use blocking sync should accept/store `std.Io`.
- Use `lockUncancelable(io)` for queue integrity and allocator metadata updates when cancellation cannot safely interrupt the critical section.
- Use `lockUncancelable(io)` only when waiting for queue or allocator locks must
not observe cancelation; keep the acquired critical section short.
- Keep OS threads for code that is explicitly thread-owned, such as dedicated worker threads or external API callbacks.
- Prefer an application's existing scheduler for application work unless the standard `std.Io` task API is explicitly the better fit.

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
Release-note map:
- `std.time.Instant` -> `std.Io.Timestamp`
- `std.time.Timer` -> `std.Io.Timestamp`
- `std.time.timestamp` -> `std.Io.Timestamp.now`
Choose an explicit clock when migrating old time APIs. Use
`std.Io.Timestamp.now(io, .real)` for wall time and `.boot` or `.awake` for
elapsed-time measurements; `Timestamp` is not a one-for-one timer replacement.
- `{D}` duration formatting -> format `std.Io.Duration` with `{f}`
`std.time` still provides constants and calendar helpers such as `ns_per_ms`, `ns_per_s`, and `std.time.epoch`.
@ -79,7 +77,8 @@ const elapsed = start.untilNow(io);
## Sleeping
Use clock-aware durations/timestamps rather than `std.Thread.sleep` when the code should cooperate with the selected `std.Io` backend.
Use clock-aware durations/timestamps so sleeping cooperates with the selected
`std.Io` backend and propagates cancelation.
```zig
try std.Io.Clock.Duration{
@ -88,8 +87,6 @@ try std.Io.Clock.Duration{
}.sleep(io);
```
For low-level OS-thread code that deliberately blocks a thread and is not part of I/O task scheduling, `std.Thread.sleep` is still available.
## Resolution
Clock resolution may fail or return zero for unsupported clocks.
@ -112,6 +109,9 @@ const seconds: u64 = @intCast(now.toSeconds());
const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = seconds };
```
This conversion is only valid for non-negative Unix timestamps and truncates
sub-second precision. Guard pre-epoch values or retain a signed representation.
## Application Guidance
- Put common wall-clock timestamp reads behind a shared application helper when consistent clock selection matters.
@ -125,4 +125,4 @@ const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = seconds };
- Is the clock choice documented by usage (`.real` for wall time, monotonic clocks for elapsed time)?
- Is duration formatting using `{f}` with `std.Io.Duration`?
- Does the API receive or store `io` rather than constructing a fallback locally?
- Is `std.Thread.sleep` only used for deliberate OS-thread blocking?
- Does sleeping use a clock-aware `std.Io` API and handle `error.Canceled`?

View File

@ -24,6 +24,9 @@ var treap: MyTreap = .{};
Nodes are user-managed (intrusive design):
An inserted node must remain alive at a stable address until it is removed or
replaced; the treap stores raw parent/child pointers.
```zig
var nodes: [100]MyTreap.Node = undefined;
@ -55,7 +58,8 @@ if (entry.node) |node| {
// found, node.key == key
}
// Get entry for existing node (O(1) if you have the node)
// O(1), but node must currently belong to this same treap. Passing a stale,
// removed, or foreign node is illegal behavior.
var entry = treap.getEntryForExisting(node);
```
@ -74,7 +78,7 @@ entry.set(null);
```zig
var entry = treap.getEntryForExisting(old_node);
entry.set(&new_node); // replaces old with new (same key)
entry.set(&new_node); // copies the old entry's key/links into new_node
```
## Min/Max Access
@ -168,4 +172,5 @@ pub fn main() !void {
- No allocator needed (nodes are user-managed)
- Balancing uses randomized priorities (xorshift PRNG)
- `node.priority == 0` indicates node is not in treap
- Entry API allows atomic check-and-modify patterns
- Entry API provides a lookup-and-update slot; it does not add synchronization
or atomic memory operations.

View File

@ -1,6 +1,6 @@
# std.Tz - TZif Timezone Database Parsing (Zig 0.16.0)
Parse IANA Time Zone Database files (TZif format, RFC 8536). Used to look up UTC offsets, DST rules, and timezone abbreviations.
Parse IANA Time Zone Database files (TZif format, RFC 8536) into transitions, time types, leap seconds, and an optional POSIX footer. `std.Tz` stores this data; applications perform their own timestamp lookup and, if needed, interpret future rules from the footer.
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
@ -20,17 +20,15 @@ When reading timezone files in Zig 0.16, use `std.Io.Dir`/`std.Io.File` and expl
```zig
const std = @import("std");
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
fn load(io: std.Io, allocator: std.mem.Allocator) !void {
// Open system timezone file
const file = try std.fs.openFileAbsolute("/usr/share/zoneinfo/America/New_York", .{});
defer file.close();
const file = try std.Io.Dir.openFileAbsolute(io, "/usr/share/zoneinfo/America/New_York", .{});
defer file.close(io);
var read_buf: [4096]u8 = undefined;
var file_reader = file.reader(io, &read_buf);
// Parse TZif data
var tz = try std.Tz.parse(allocator, file.reader());
var tz = try std.Tz.parse(allocator, &file_reader.interface);
defer tz.deinit();
// Access timezone information
@ -47,9 +45,9 @@ const std = @import("std");
// Embed TZif file at compile time
const tokyo_tz = @embedFile("tz/asia_tokyo.tzif");
pub fn main() !void {
var stream = std.io.fixedBufferStream(tokyo_tz);
var tz = try std.Tz.parse(std.heap.page_allocator, stream.reader());
pub fn parseEmbedded(allocator: std.mem.Allocator) !void {
var reader: std.Io.Reader = .fixed(tokyo_tz);
var tz = try std.Tz.parse(allocator, &reader);
defer tz.deinit();
// Use timezone data...
@ -66,7 +64,7 @@ pub const Tz = struct {
leapseconds: []const Leapsecond,
footer: ?[]const u8, // POSIX TZ string for future dates
pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz
pub fn parse(allocator: std.mem.Allocator, reader: *std.Io.Reader) !Tz
pub fn deinit(self: *Tz) void
};
```
@ -115,7 +113,7 @@ pub const Leapsecond = struct {
```zig
fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 {
// Find the last transition before or at the given timestamp
var result: ?*const std.Timetype = null;
var result: ?*const std.Tz.Timetype = null;
for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) {
@ -134,8 +132,9 @@ fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 {
return 0;
}
// Usage
const offset = getUtcOffset(&tz, std.time.timestamp());
// Usage for a timestamp supplied by the caller
const unix_timestamp: i64 = 1_700_000_000;
const offset = getUtcOffset(&tz, unix_timestamp);
const local_time = unix_timestamp + offset;
```
@ -143,14 +142,16 @@ const local_time = unix_timestamp + offset;
```zig
fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool {
var active: ?*const std.Tz.Timetype = null;
for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) {
if (t.timetype.isDst()) return true;
active = t.timetype;
} else {
break;
}
}
return false;
if (active) |tt| return tt.isDst();
return tz.timetypes.len > 0 and tz.timetypes[0].isDst();
}
```
@ -158,7 +159,7 @@ fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool {
```zig
fn getTimezoneAbbrev(tz: *const std.Tz, unix_timestamp: i64) []const u8 {
var result: ?*const std.Timetype = null;
var result: ?*const std.Tz.Timetype = null;
for (tz.transitions) |t| {
if (t.ts <= unix_timestamp) {
@ -194,14 +195,16 @@ fn printTransitions(tz: *const std.Tz) void {
}
```
## Parse Errors
## Selected Parse Errors
| Error | Cause |
|-------|-------|
| `error.BadHeader` | Invalid TZif magic bytes (not "TZif") |
| `error.BadVersion` | Unsupported TZif version (only 0, 2, 3 supported) |
| `error.Malformed` | RFC 8536 validation failure |
| `error.OverlargeFooter` | POSIX TZ string exceeds 128 bytes |
| `error.OverlargeFooter` | Footer exceeded the active reader's capacity while scanning to its newline |
Allocation failures and `std.Io.Reader` failures such as `EndOfStream` or `ReadFailed` can also propagate.
## System Timezone Paths
@ -218,7 +221,7 @@ Common timezone identifiers:
## POSIX TZ Footer
Modern TZif files (v2+) include a POSIX TZ string in the footer for calculating offsets beyond the last transition:
Modern TZif files (v2+) may include a POSIX TZ string in the footer. `std.Tz` stores a non-empty footer but does not interpret it to calculate future offsets:
```zig
if (tz.footer) |posix_tz| {

View File

@ -49,14 +49,15 @@ while (it2.nextCodepointSlice()) |slice| {
// slice is []const u8: "h", "é", "l", "l", "o", " ", "世", "界"
}
// Peek ahead without advancing
const next3 = it.peek(3); // next 3 codepoints as UTF-8 bytes
// Peek ahead without advancing a fresh iterator
var peek_it = view.iterator();
const next3 = peek_it.peek(3); // next 3 codepoints as UTF-8 bytes
// Comptime-validated view
const view = unicode.Utf8View.initComptime("hello");
const comptime_view = unicode.Utf8View.initComptime("hello");
// Unchecked (when you know it's valid)
const view = unicode.Utf8View.initUnchecked(trusted_utf8);
const unchecked_view = unicode.Utf8View.initUnchecked(trusted_utf8);
```
## Encoding/Decoding Codepoints
@ -64,17 +65,17 @@ const view = unicode.Utf8View.initUnchecked(trusted_utf8);
```zig
// Encode codepoint to UTF-8
var buf: [4]u8 = undefined;
const len = try unicode.utf8Encode('é', &buf); // len = 2
// buf[0..len] contains UTF-8 bytes
const encoded_len = try unicode.utf8Encode('é', &buf); // len = 2
// buf[0..encoded_len] contains UTF-8 bytes
// Comptime encoding (returns fixed-size array)
const bytes = unicode.utf8EncodeComptime('世'); // [3]u8
// Get UTF-8 sequence length for a codepoint
const len = try unicode.utf8CodepointSequenceLength('世'); // 3
const codepoint_len = try unicode.utf8CodepointSequenceLength('世'); // 3
// Get sequence length from first byte
const len = try unicode.utf8ByteSequenceLength(0xE4); // 3 (for 3-byte sequence)
const sequence_len = try unicode.utf8ByteSequenceLength(0xE4); // 3 (for 3-byte sequence)
```
## UTF-8 ↔ UTF-16 Conversion
@ -109,23 +110,25 @@ defer allocator.free(utf8z);
// UTF-8 to UTF-16LE (caller provides buffer)
var utf16_buf: [128]u16 = undefined;
const len = try unicode.utf8ToUtf16Le(&utf16_buf, "hello");
const utf16 = utf16_buf[0..len];
const utf16_result = utf16_buf[0..len];
// UTF-16LE to UTF-8 (caller provides buffer)
var utf8_buf: [256]u8 = undefined;
const len = try unicode.utf16LeToUtf8(&utf8_buf, utf16_data);
const utf8 = utf8_buf[0..len];
const utf8_len = try unicode.utf16LeToUtf8(&utf8_buf, utf16_data);
const utf8_result = utf8_buf[0..utf8_len];
```
These functions assert that the destination buffer is large enough; insufficient capacity is not reported as a recoverable error. Compute or conservatively bound the required capacity first.
### ArrayList Conversion
```zig
var list = std.ArrayList(u16).empty;
defer list.deinit(allocator);
var list = std.array_list.Managed(u16).init(allocator);
defer list.deinit();
try unicode.utf8ToUtf16LeArrayList(&list, "hello");
var list8 = std.ArrayList(u8).empty;
defer list8.deinit(allocator);
var list8 = std.array_list.Managed(u8).init(allocator);
defer list8.deinit();
try unicode.utf16LeToUtf8ArrayList(&list8, utf16_data);
```
@ -166,7 +169,8 @@ while (try it.nextCodepoint()) |cp| {
WTF-8 is like UTF-8 but allows unpaired surrogates (for Windows compatibility).
```zig
// Validate WTF-8 (allows surrogates)
// Validate the accepted byte encoding. This does not reject paired surrogate
// halves, so acceptance alone does not establish well-formed WTF-8.
unicode.wtf8ValidateSlice(data) // bool
// WTF-8 iteration
@ -178,13 +182,17 @@ while (it.nextCodepoint()) |cp| {
// WTF-8 ↔ WTF-16 conversion
const wtf8 = try unicode.wtf16LeToWtf8Alloc(allocator, wtf16_data);
defer allocator.free(wtf8);
const wtf16 = try unicode.wtf8ToWtf16LeAlloc(allocator, wtf8_data);
defer allocator.free(wtf16);
// Convert WTF-8 to UTF-8 (lossy - replaces surrogates with U+FFFD)
const utf8 = try unicode.wtf8ToUtf8LossyAlloc(allocator, wtf8_data);
defer allocator.free(utf8);
// In-place lossy conversion
try unicode.wtf8ToUtf8Lossy(buffer, wtf8_data);
// In-place is supported when input and output are exactly the same slice.
// Otherwise output must be at least as long as input.
try unicode.wtf8ToUtf8Lossy(buffer, buffer);
```
## Formatting
@ -230,7 +238,9 @@ fn callWindowsApi(path: []const u8) !void {
}
```
### Grapheme-aware truncation
### Codepoint-counted truncation
This does not implement Unicode grapheme segmentation. It can split combining sequences, emoji ZWJ sequences, and other user-perceived characters made from multiple codepoints.
```zig
fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
const view = try unicode.Utf8View.init(s);
@ -246,7 +256,7 @@ fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
}
```
## Error Types
## Selected Error Types
| Error | Meaning |
|-------|---------|
@ -255,7 +265,7 @@ fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
| `Utf8InvalidStartByte` | Invalid first byte in sequence |
| `Utf8ExpectedContinuation` | Missing continuation byte |
| `Utf8OverlongEncoding` | Overlong encoding detected |
| `Utf8EncodesSurrogateHalf` | Surrogate in UTF-8 (use WTF-8) |
| `Utf8CannotEncodeSurrogateHalf` | Attempted to encode a surrogate as UTF-8 (use WTF-8 if intentional) |
| `CodepointTooLarge` | Codepoint > 0x10FFFF |
## Notes

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 and formatting conforming to RFC 3986, with percent-encoding/decoding and resolution support.
URI parsing and formatting that roughly adhere to RFC 3986, with percent-encoding/decoding and resolution support. The parser intentionally does not perform complete grammar or character-class validation.
## Table of Contents
- [Parsing URIs](#parsing-uris)
@ -64,6 +64,10 @@ const uri = std.Uri.parse(input) catch |err| switch (err) {
std.debug.print("Port not a valid u16\n", .{});
return err;
},
error.InvalidHostName => {
std.debug.print("Invalid host name\n", .{});
return err;
},
};
```
@ -81,8 +85,6 @@ const Uri = struct {
path: Component = Component.empty,
query: ?Component = null,
fragment: ?Component = null,
pub const host_name_max = 255;
};
```
@ -106,13 +108,14 @@ const Component = union(enum) {
### Getting Host
```zig
var buffer: [std.Uri.host_name_max]u8 = undefined;
var buffer: [std.Io.net.HostName.max_len]u8 = undefined;
const host = uri.getHost(&buffer) catch |err| switch (err) {
error.UriMissingHost => return error.NoHost,
error.UriHostTooLong => return error.HostTooLong,
};
```
The result is a `std.Io.net.HostName`. A URI host is validated during parsing, so the fixed buffer is sized to the hostname limit and `getHost` exposes only `error.UriMissingHost`.
With allocation:
```zig
@ -152,6 +155,8 @@ const raw = try component.toRaw(&buf); // "hello world"
const raw_alloc = try component.toRawMaybeAlloc(allocator); // "hello world"
```
`toRawMaybeAlloc` may return the component's original borrowed slice or arena-allocated decoded bytes. Treat the result as tied to both lifetimes and do not free it individually.
## Formatting URIs
### Full URI
@ -236,7 +241,7 @@ const decoded = std.Uri.percentDecodeInPlace(&buffer);
// decoded == "hello world!"
```
### Decode Backwards (Safe for Aliasing)
### Decode Backwards (Conditionally Safe for Aliasing)
```zig
const input = "%48%65%6C%6C%6F";
@ -245,6 +250,8 @@ const decoded = std.Uri.percentDecodeBackwards(&output, input);
// decoded == "Hello"
```
The output must be large enough. Aliasing is supported only when `output.ptr <= input.ptr`; there is no recoverable undersized-buffer error.
### Encode with Component
```zig
@ -264,7 +271,7 @@ var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
// Encode with custom character validation
std.Uri.Component.percentEncode(&writer, "custom data", struct {
try std.Uri.Component.percentEncode(&writer, "custom data", struct {
fn isValid(c: u8) bool {
return std.ascii.isAlphanumeric(c);
}
@ -338,103 +345,101 @@ const name = getQueryParam(uri, "name"); // "alice"
### Build URL with Query Parameters
```zig
fn buildUrl(allocator: Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 {
var result: std.ArrayList(u8) = .empty;
defer result.deinit(allocator);
fn buildUrl(allocator: std.mem.Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 {
var result: std.Io.Writer.Allocating = .init(allocator);
errdefer result.deinit();
try result.appendSlice(allocator, base);
try result.writer.writeAll(base);
for (params, 0..) |param, i| {
try result.append(allocator, if (i == 0) '?' else '&');
// Encode key
for (param[0]) |c| {
if (std.Uri.isUnreserved(c)) {
try result.append(allocator, c);
} else {
try result.appendSlice(allocator, try std.fmt.allocPrint(allocator, "%{X:0>2}", .{c}));
}
try result.writer.writeByte(if (i == 0) '?' else '&');
try (std.Uri.Component{ .raw = param[0] }).formatEscaped(&result.writer);
try result.writer.writeByte('=');
try (std.Uri.Component{ .raw = param[1] }).formatEscaped(&result.writer);
}
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
fn normalizeUri(allocator: Allocator, uri_str: []const u8) ![]u8 {
fn serializeUri(allocator: std.mem.Allocator, uri_str: []const u8) ![]u8 {
const uri = try std.Uri.parse(uri_str);
var buf: [4096]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
// Format with all components to normalize encoding
// Parsed percent-encoded components are preserved when formatted.
try uri.format(&writer);
return try allocator.dupe(u8, writer.buffered());
}
```
This is serialization, not general normalization: existing escape spelling is preserved. Formatting with all components also emits `/` for an empty included path.
### Validate URI
```zig
fn isValidUri(str: []const u8) bool {
fn isAcceptedByUriParser(str: []const u8) bool {
_ = std.Uri.parse(str) catch return false;
return true;
}
fn isValidHttpUri(str: []const u8) bool {
fn hasHttpSchemeAndHost(str: []const u8) bool {
const uri = std.Uri.parse(str) catch return false;
return std.mem.eql(u8, uri.scheme, "http") or std.mem.eql(u8, uri.scheme, "https");
return uri.host != null and
(std.mem.eql(u8, uri.scheme, "http") or std.mem.eql(u8, uri.scheme, "https"));
}
```
These remain lightweight parser/scheme checks, not complete URI or HTTP-URL validation.
### Join Path Segments
```zig
fn joinPath(allocator: Allocator, base_uri: std.Uri, segments: []const []const u8) !std.Uri {
var path: std.ArrayList(u8) = .empty;
defer path.deinit(allocator);
const OwnedUri = struct {
value: std.Uri,
path_storage: []u8,
// Start with base path (remove trailing slash if any)
const base_path = base_uri.path.percent_encoded;
if (base_path.len > 0 and base_path[base_path.len - 1] == '/') {
try path.appendSlice(allocator, base_path[0 .. base_path.len - 1]);
} else {
try path.appendSlice(allocator, base_path);
fn deinit(self: *OwnedUri, allocator: std.mem.Allocator) void {
allocator.free(self.path_storage);
self.* = undefined;
}
};
// Append segments
fn joinPath(allocator: std.mem.Allocator, base_uri: std.Uri, segments: []const []const u8) !OwnedUri {
var path: std.Io.Writer.Allocating = .init(allocator);
errdefer path.deinit();
// Preserve/encode the existing component according to path rules.
try base_uri.path.formatPath(&path.writer);
// Each segment is escaped as a segment, so '/' inside a segment becomes %2F.
for (segments) |seg| {
try path.append(allocator, '/');
try path.appendSlice(allocator, seg);
const current = path.written();
if (current.len == 0 or current[current.len - 1] != '/')
try path.writer.writeByte('/');
try (std.Uri.Component{ .raw = seg }).formatEscaped(&path.writer);
}
const owned_path = try path.toOwnedSlice();
var result = base_uri;
result.path = .{ .percent_encoded = try path.toOwnedSlice(allocator) };
result.path = .{ .percent_encoded = owned_path };
result.query = null;
result.fragment = null;
return result;
return .{ .value = result, .path_storage = owned_path };
}
```
The returned URI owns its assembled path; call `OwnedUri.deinit` after use.
### Extract Base URL
```zig
fn getBaseUrl(allocator: Allocator, uri: std.Uri) ![]u8 {
fn getBaseUrl(allocator: std.mem.Allocator, uri: std.Uri) ![]u8 {
var buf: [1024]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
@ -461,6 +466,7 @@ pub const ParseError = error{
UnexpectedCharacter, // Invalid character in URI component
InvalidFormat, // Malformed URI structure
InvalidPort, // Port not a valid u16
InvalidHostName, // Host did not pass std.Io.net.HostName validation
};
```
@ -477,7 +483,6 @@ pub const ResolveInPlaceError = ParseError || error{
```zig
// getHost errors
error.UriMissingHost // URI has no host component
error.UriHostTooLong // Host exceeds host_name_max (255)
// toRaw errors
error.NoSpaceLeft // Buffer too small for decoded string

View File

@ -2,6 +2,8 @@
Utilities for parsing, tokenizing, and working with Zig source code. Used for tooling, linters, formatters, and custom analysis.
These compiler-distributed utilities have no API-stability guarantee. Treat this page as a Zig 0.16-specific reference and expect tooling code to need updates across Zig releases.
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
When tooling reads/writes files or buffers output in Zig 0.16, use `std.Io.Reader`, `std.Io.Writer`, `std.Io.Dir`, and explicit `std.Io`. When searching source text, prefer `std.mem.find*` and `std.mem.cut*` APIs.
@ -32,7 +34,7 @@ pub fn analyzeSource(allocator: std.mem.Allocator, source: [:0]const u8) !void {
if (tree.errors.len > 0) {
for (tree.errors) |err| {
var buf: [256]u8 = undefined;
var w: std.io.Writer = .fixed(&buf);
var w: std.Io.Writer = .fixed(&buf);
try tree.renderError(err, &w);
std.debug.print("Error: {s}\n", .{w.buffered()});
}
@ -164,7 +166,7 @@ defer allocator.free(formatted);
// Or render to writer
var buf: [8192]u8 = undefined;
var writer = std.fs.File.stdout().writer(&buf);
var writer = std.Io.File.stdout().writer(io, &buf);
try tree.render(allocator, &writer.interface, .{});
try writer.interface.flush();
```
@ -195,8 +197,19 @@ const token_tag = tree.tokenTag(token_index);
for (tree.rootDecls()) |decl| {
switch (tree.nodeTag(decl)) {
.fn_decl => handleFunction(tree, decl),
.global_var_decl, .simple_var_decl => handleVariable(tree, decl),
.container_decl, .container_decl_two => handleStruct(tree, decl),
.global_var_decl, .simple_var_decl, .local_var_decl, .aligned_var_decl => {
// A normal top-level `const Name = struct { ... };` is a variable
// declaration. Inspect its initializer to find the container expression.
const var_decl = tree.fullVarDecl(decl).?;
if (var_decl.ast.init_node.unwrap()) |init_node| {
var container_buf: [2]std.zig.Ast.Node.Index = undefined;
if (tree.fullContainerDecl(&container_buf, init_node)) |container| {
handleStruct(tree, init_node, container);
} else {
handleVariable(tree, decl);
}
}
},
else => {},
}
}
@ -499,7 +512,7 @@ if (tree.errors.len > 0) {
// Format error message
var buf: [512]u8 = undefined;
var w: std.io.Writer = .fixed(&buf);
var w: std.Io.Writer = .fixed(&buf);
try tree.renderError(err, &w);
std.debug.print("{s}:{d}:{d}: error: {s}\n", .{
@ -530,7 +543,7 @@ var bundle = try wip_errors.toOwnedBundle("");
defer bundle.deinit(allocator);
// Render to stderr
bundle.renderToStdErr(.{ .ttyconf = .no_color });
try bundle.renderToStderr(io, .{}, .off);
// Or iterate errors
for (bundle.getMessages()) |msg_idx| {
@ -578,7 +591,7 @@ switch (result) {
```zig
// Format identifier, escaping if needed
var buf: [256]u8 = undefined;
var w: std.io.Writer = .fixed(&buf);
var w: std.Io.Writer = .fixed(&buf);
try w.print("{f}", .{std.zig.fmtId("while")}); // @"while"
try w.print("{f}", .{std.zig.fmtId("hello")}); // hello
try w.print("{f}", .{std.zig.fmtId("123abc")}); // @"123abc"
@ -596,7 +609,7 @@ std.zig.isValidId("a b") // false (contains space)
```zig
// Escape string for Zig string literal
var buf: [256]u8 = undefined;
var w: std.io.Writer = .fixed(&buf);
var w: std.Io.Writer = .fixed(&buf);
try w.print("\"{f}\"", .{std.zig.fmtString("hello\nworld")});
// Output: "hello\nworld"
@ -621,17 +634,18 @@ const hash = std.zig.hashSrc(source);
// Compare hashes
if (std.zig.srcHashEql(hash1, hash2)) {
// Sources are identical
// The 128-bit source hashes match (appropriate as a cache signal, but
// hash equality is not mathematical proof that the source bytes match).
}
```
### Read Source File
```zig
// Read and decode source file (handles UTF-16LE BOM)
const file = try std.fs.cwd().openFile("source.zig", .{});
defer file.close();
const file = try std.Io.Dir.cwd().openFile(io, "source.zig", .{});
defer file.close(io);
var reader = file.reader(&buf);
var reader = file.reader(io, &buf);
const source = try std.zig.readSourceFileToEndAlloc(allocator, &reader);
defer allocator.free(source);
```

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
Old examples below may contain 0.15 `std.fs` or `std.io` patterns; translate them to `std.Io` before using in new code.
The examples below use Zig 0.16's explicit `std.Io` file and directory APIs.
## Table of Contents
- [Module Structure](#module-structure)
@ -33,11 +33,11 @@ std.zip.CompressionMethod // .store, .deflate
Extract all files from a ZIP archive to a directory:
```zig
const file = try std.fs.cwd().openFile("archive.zip", .{});
defer file.close();
const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{});
defer file.close(io);
var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf);
var file_reader = file.reader(io, &buf);
try std.zip.extract(output_dir, &file_reader, .{});
```
@ -65,10 +65,12 @@ if (diagnostics.root_dir.len > 0) {
pub const ExtractOptions = struct {
allow_backslashes: bool = false, // normalize \ to / in filenames
diagnostics: ?*Diagnostics = null, // track extraction metadata
verify_checksums: bool = false, // TODO: not yet implemented
verify_checksums: bool = false, // true currently panics: TODO unimplemented
};
```
Leave `verify_checksums` false in Zig 0.16. Setting it true immediately panics, and normal extraction does not otherwise verify each entry's CRC-32 payload checksum.
## Iterating Over Entries
### Iterator API
@ -76,15 +78,15 @@ pub const ExtractOptions = struct {
For more control, iterate over entries individually:
```zig
const file = try std.fs.cwd().openFile("archive.zip", .{});
defer file.close();
const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{});
defer file.close(io);
var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf);
var file_reader = file.reader(io, &buf);
var iter = try std.zip.Iterator.init(&file_reader);
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
while (try iter.next()) |entry| {
// Read filename from archive
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
@ -99,12 +101,14 @@ while (try iter.next()) |entry| {
}
```
### Iterator.Entry Structure
### Iterator.Entry Fields
```zig
pub const Entry = struct {
```text
// Descriptive field inventory, not a declaration to copy: the concrete type
// of `flags` is private to std.zip.
struct {
version_needed_to_extract: u16,
flags: GeneralPurposeFlags,
flags: /* private general-purpose-flags type */,
compression_method: CompressionMethod, // .store or .deflate
last_modification_time: u16, // DOS time format
last_modification_date: u16, // DOS date format
@ -124,7 +128,7 @@ pub const Entry = struct {
```zig
var iter = try std.zip.Iterator.init(&file_reader);
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
while (try iter.next()) |entry| {
// Extract this entry to destination directory
try entry.extract(&file_reader, .{}, &filename_buf, output_dir);
@ -138,7 +142,7 @@ Extract only specific files:
```zig
var iter = try std.zip.Iterator.init(&file_reader);
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
while (try iter.next()) |entry| {
// Read filename first
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
@ -234,15 +238,15 @@ std.zip.end_locator64_sig // "PK\x06\x07"
### Extract ZIP to Directory
```zig
fn extractZip(allocator: Allocator, zip_path: []const u8, dest_path: []const u8) !void {
const file = try std.fs.cwd().openFile(zip_path, .{});
defer file.close();
fn extractZip(io: std.Io, allocator: std.mem.Allocator, zip_path: []const u8, dest_path: []const u8) !void {
const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{});
defer file.close(io);
var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf);
var file_reader = file.reader(io, &buf);
var dest = try std.fs.cwd().makeOpenPath(dest_path, .{});
defer dest.close();
var dest = try std.Io.Dir.cwd().createDirPathOpen(io, dest_path, .{});
defer dest.close(io);
var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator };
defer diagnostics.deinit();
@ -257,16 +261,16 @@ fn extractZip(allocator: Allocator, zip_path: []const u8, dest_path: []const u8)
### List ZIP Contents
```zig
fn listZip(zip_path: []const u8) !void {
const file = try std.fs.cwd().openFile(zip_path, .{});
defer file.close();
fn listZip(io: std.Io, zip_path: []const u8) !void {
const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{});
defer file.close(io);
var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf);
var file_reader = file.reader(io, &buf);
var iter = try std.zip.Iterator.init(&file_reader);
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
var total_size: u64 = 0;
var file_count: u64 = 0;
@ -299,13 +303,13 @@ fn listZip(zip_path: []const u8) !void {
```zig
fn extractFile(
file_reader: *std.fs.File.Reader,
file_reader: *std.Io.File.Reader,
target_name: []const u8,
dest: std.fs.Dir,
dest: std.Io.Dir,
) !bool {
var iter = try std.zip.Iterator.init(file_reader);
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
while (try iter.next()) |entry| {
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
const filename = filename_buf[0..entry.filename_len];
@ -323,12 +327,12 @@ fn extractFile(
### Check if File is ZIP
```zig
fn isZipFile(path: []const u8) !bool {
const file = std.fs.cwd().openFile(path, .{}) catch return false;
defer file.close();
fn isZipFile(io: std.Io, path: []const u8) !bool {
const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch return false;
defer file.close(io);
var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf);
var file_reader = file.reader(io, &buf);
_ = std.zip.EndRecord.findFile(&file_reader) catch return false;
return true;
@ -337,7 +341,7 @@ fn isZipFile(path: []const u8) !bool {
## Supported Features
**Formats**: ZIP, ZIP64 (large files > 4GB, > 65535 entries)
**Formats**: ZIP plus core single-disk ZIP64 records/extents. ZIP64 end-record extra data, unsupported versions, and multi-disk/locator variants are rejected, so this is not unrestricted ZIP64 compatibility.
**Compression**: Store (uncompressed), Deflate

View File

@ -1,6 +1,6 @@
# std.zon - ZON Parsing and Serialization
ZON ("Zig Object Notation") parsing and stringification. ZON's grammar is a subset of Zig's syntax. In Zig 0.16, examples that read/write files should use `std.Io.Dir`/`std.Io.File` and an explicit `std.Io`.
ZON ("Zig Object Notation") parsing and stringification. Its grammar is a subset of Zig's syntax except for the supported `nan` and `inf` literals. In Zig 0.16, file examples use `std.Io.Dir`/`std.Io.File` and an explicit `std.Io`.
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
@ -70,7 +70,7 @@ pub fn main() !void {
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const config = try std.zon.parse.fromSlice(Config, allocator, zon_str, null, .{});
const config = try std.zon.parse.fromSliceAlloc(Config, allocator, zon_str, null, .{});
defer std.zon.parse.free(allocator, config);
// config.name == "server"
@ -85,7 +85,7 @@ pub fn main() !void {
var diag: std.zon.parse.Diagnostics = .{};
defer diag.deinit(allocator);
const result = std.zon.parse.fromSlice(Config, allocator, zon_str, &diag, .{}) catch |err| {
const result = std.zon.parse.fromSliceAlloc(Config, allocator, zon_str, &diag, .{}) catch |err| {
// Print diagnostic errors
var errors = diag.iterateErrors();
while (errors.next()) |parse_err| {
@ -104,7 +104,7 @@ defer std.zon.parse.free(allocator, result);
### Parse Options
```zig
const result = try std.zon.parse.fromSlice(T, allocator, zon_str, diag, .{
const result = try std.zon.parse.fromSliceAlloc(T, allocator, zon_str, diag, .{
// Ignore unknown fields (default: false - errors on unknown)
.ignore_unknown_fields = true,
@ -128,10 +128,12 @@ const version = build_zon.version;
### Free Parsed Values
```zig
const result = try std.zon.parse.fromSlice(T, allocator, zon_str, null, .{});
const result = try std.zon.parse.fromSliceAlloc(T, allocator, zon_str, null, .{});
defer std.zon.parse.free(allocator, result);
```
Use `fromSlice` only when `T` contains no pointers; that result owns no allocations and needs no `free`. Use `fromSliceAlloc` for pointer-containing values such as structs with string slices, and release the result with `std.zon.parse.free`.
## Serializing to ZON
### Simple Serialization
@ -252,10 +254,12 @@ try tuple.end();
var container = try s.beginStruct(.{
.whitespace_style = .{ .wrap = true }, // Always wrap fields
// .whitespace_style = .{ .wrap = false }, // Never wrap (single line)
// .whitespace_style = .{ .fields = 2 }, // Auto-wrap if > 2 fields
// .whitespace_style = .{ .fields = 2 }, // caller says this container has 2 fields; no wrap
});
```
For `.fields = n`, the caller supplies the expected field count. Values greater than two select wrapped output; the serializer does not look ahead and count future fields.
### Nested Containers
```zig
@ -394,42 +398,40 @@ const Config = struct {
debug: bool = false,
};
fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config {
const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
fn loadConfig(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !Config {
const content = std.Io.Dir.cwd().readFileAllocOptions(
io,
path,
allocator,
.limited(1024 * 1024),
.of(u8),
0,
) catch |err| switch (err) {
error.FileNotFound => return Config{},
else => return err,
};
defer file.close();
const content = try file.readToEndAllocOptions(
allocator,
1024 * 1024,
null,
@alignOf(u8),
0, // null terminator
);
defer allocator.free(content);
return std.zon.parse.fromSlice(Config, allocator, content, null, .{
return std.zon.parse.fromSliceAlloc(Config, allocator, content, null, .{
.ignore_unknown_fields = true,
.free_on_error = true,
});
}
```
The caller owns a successfully loaded `Config` and should pass it to `std.zon.parse.free` when finished.
### Serialize to File
```zig
fn saveConfig(allocator: std.mem.Allocator, config: Config, path: []const u8) !void {
var aw: std.Io.Writer.Allocating = .init(allocator);
defer aw.deinit();
fn saveConfig(io: std.Io, config: Config, path: []const u8) !void {
const file = try std.Io.Dir.cwd().createFile(io, path, .{});
defer file.close(io);
var buffer: [4096]u8 = undefined;
var file_writer = file.writer(io, &buffer);
try std.zon.stringify.serialize(config, .{ .whitespace = true }, &aw.writer);
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
try file.writeAll(aw.written());
try std.zon.stringify.serialize(config, .{ .whitespace = true }, &file_writer.interface);
try file_writer.interface.flush();
}
```
@ -472,7 +474,7 @@ try std.zon.stringify.serialize(settings, .{
### Round-Trip ZON Data
```zig
fn roundTrip(comptime T: type, allocator: std.mem.Allocator, value: T) !T {
fn roundTripAlloc(comptime T: type, allocator: std.mem.Allocator, value: T) !T {
// Serialize
var aw: std.Io.Writer.Allocating = .init(allocator);
defer aw.deinit();
@ -484,6 +486,8 @@ fn roundTrip(comptime T: type, allocator: std.mem.Allocator, value: T) !T {
const terminated: [:0]const u8 = zon_str[0 .. zon_str.len - 1 :0];
// Parse back
return std.zon.parse.fromSlice(T, allocator, terminated, null, .{});
return std.zon.parse.fromSliceAlloc(T, allocator, terminated, null, .{});
}
```
The returned value may own allocations; the caller must eventually call `std.zon.parse.free(allocator, result)`. For a pointer-free `T`, a `fromSlice`-based variant can return a value that needs no cleanup.

View File

@ -1,6 +1,6 @@
# Zig Style Guide
Official coding conventions from the Zig language reference. These are implemented and enforced by `zig fmt`.
Coding conventions based on the Zig language reference. `zig fmt` enforces syntactic layout; naming, documentation, and API-design conventions still require human judgment. Interoperability and established external APIs can justify exceptions.
## Naming Conventions
@ -169,22 +169,22 @@ fn processRequest(
- **Omit redundant information** that's already clear from the name
- **Duplicate information** across similar functions (helps IDEs)
- Use **"assume"** for invariants that cause *unchecked* illegal behavior when violated
- Use **"assert"** for invariants that cause *safety-checked* illegal behavior when violated
- Use **"assume"** for unchecked preconditions whose violation may cause illegal behavior
- Use **"assert"** when the implementation actively checks an invariant and panics when it is violated
```zig
/// Reads a little-endian u32 from the buffer.
///
/// Caller must **assume** buffer has at least 4 bytes remaining.
/// This is not checked and will cause undefined behavior if violated.
/// Caller must provide at least 4 bytes. The slice expression performs a
/// bounds check in safety-enabled builds and panics if the buffer is shorter.
fn readU32Le(buf: []const u8) u32 {
return std.mem.readInt(u32, buf[0..4], .little);
}
/// Pops the last element from the list.
///
/// **Asserts** the list is not empty. In safe modes, returns an error
/// or panics if the list is empty.
/// **Asserts** the list is not empty. Assertion failure panics; this function
/// has no error return.
fn pop(self: *Self) T {
std.debug.assert(self.items.len > 0);
// ...
@ -197,7 +197,7 @@ fn pop(self: *Self) T {
- **LF** (`\n`, 0x0a) line endings (CRLF discouraged but tolerated)
- End files with a newline
- No hard tabs (spaces only)
- `zig fmt` enforces all these conventions
- `zig fmt` normalizes source layout; encoding, naming, and documentation rules are separate checks
## Applying the Style Guide

View File

@ -64,7 +64,6 @@ For ABI-sensitive bindings:
- `@Struct`
- `@Union`
- `@Enum`
- `@Opaque`
Migration rule:
@ -81,7 +80,9 @@ Practical notes:
- Runtime vector indexes are forbidden. Use scalar extraction patterns, compile-time indexes, or restructure the vector operation.
- Vectors and arrays no longer support in-memory coercion. Make conversions explicit with loads, stores, or element-wise construction.
- Unary float builtins forward result type. Code such as `const x: f64 = @sqrt(@floatFromInt(n));` now works as intended.
- `@floor`, `@ceil`, `@round`, and `@trunc` can convert floats to integer result types. `@intFromFloat` is now redundant with `@trunc` and is deprecated.
- `@floor`, `@ceil`, `@round`, and `@trunc` perform result-typed floating
operations. Use `@intFromFloat` for finite, in-range float-to-integer
conversion.
### Returning Local Addresses
@ -132,7 +133,8 @@ Important removals/renames:
- `std.fmt.format` is replaced by `std.Io.Writer.print`.
- `std.fmt.Formatter` is renamed to `std.fmt.Alt`.
- `std.fmt.FormatOptions` is renamed to `std.fmt.Options`.
- `std.fmt.bufPrintZ` is renamed to `std.fmt.bufPrintSentinel`.
- `std.fmt.bufPrintZ` remains as a deprecated zero-sentinel wrapper; use
`std.fmt.bufPrintSentinel` for new code.
- `std.DynLib` removed Windows support; use platform APIs (`LoadLibraryExW`, `GetProcAddress`) directly or through a local abstraction.
- `BitSet` and `EnumSet` use decl literals instead of `initEmpty` / `initFull`.
@ -145,9 +147,11 @@ Important error changes:
### I/O as an Interface
The core 0.16 rule: all input/output functionality requires an `std.Io` instance.
Anything that might block, interact with the outside world, depend on the OS, wait on concurrency, use entropy, or introduce nondeterminism belongs under the `Io` interface.
The core 0.16 direction is to route new blocking, OS-facing, and
nondeterministic APIs through an `std.Io` instance. Entropy, time, networking,
process, and file operations generally follow this design, but existing stdlib
APIs are not uniformly parameterized by `std.Io`; verify the installed
signature before applying the rule mechanically.
Main patterns:
@ -203,7 +207,8 @@ Map:
- `std.Thread.ResetEvent` -> `std.Io.Event`
- `std.Thread.WaitGroup` -> `std.Io.Group`
- `std.Thread.Futex` -> `std.Io.Futex`
- `std.Thread.Futex` operations -> `io.futexWait`, `io.futexWaitTimeout`,
`io.futexWaitUncancelable`, and `io.futexWake`
- `std.Thread.Mutex` -> `std.Io.Mutex`
- `std.Thread.Condition` -> `std.Io.Condition`
- `std.Thread.Semaphore` -> `std.Io.Semaphore`
@ -246,11 +251,10 @@ Use `io.randomSecure(...)` when fresh OS-backed cryptographic entropy is require
The old wall-clock/monotonic split is now routed through `std.Io` time types for clock operations that may depend on the runtime.
Migration highlights from the release notes:
- `std.time.Instant` -> `std.Io.Timestamp`
- `std.time.Timer` -> `std.Io.Timestamp`
- `std.time.timestamp` -> `std.Io.Timestamp.now`
Migration requires an explicit clock choice. Use
`std.Io.Timestamp.now(io, .real)` for wall-clock timestamps and `.boot` or
`.awake` for elapsed-time measurement; `Timestamp` is not a one-for-one timer
replacement.
Application preference:
@ -382,7 +386,9 @@ Several low-level stdlib wrappers were removed as part of moving blocking/nondet
### Allocators
`heap.ArenaAllocator` is now thread-safe and lock-free.
`heap.ArenaAllocator`'s allocator interface is thread-safe when its child
allocator is thread-safe. The stdlib does not promise unconditional lock-free
behavior.
`heap.ThreadSafe` allocator was removed. If shared allocation still needs protection, choose an allocator that is already thread-safe or place synchronization at the owner of the shared data.