saving
This commit is contained in:
parent
5e60be2add
commit
3d8375cf39
|
|
@ -0,0 +1,2 @@
|
||||||
|
scripts/
|
||||||
|
audit-results/
|
||||||
30
SKILL.md
30
SKILL.md
|
|
@ -11,7 +11,10 @@ Always load **[Zig 0.16 Release Notes Migration Reference](references/zig-0.16-r
|
||||||
|
|
||||||
## Critical: I/O Is an Interface
|
## Critical: I/O Is an Interface
|
||||||
|
|
||||||
Starting in Zig 0.16.0, input/output APIs require a `std.Io` instance. Treat anything that can block, touch the OS, use entropy/time/network/process/file APIs, or introduce nondeterminism as owned by `std.Io`.
|
Zig 0.16.0 routes many new blocking, OS-facing, and nondeterministic APIs
|
||||||
|
through a `std.Io` instance. For new APIs, treat entropy, time, networking,
|
||||||
|
process, and file operations as `std.Io` responsibilities. Existing stdlib APIs
|
||||||
|
are not uniformly parameterized this way, so check each installed signature.
|
||||||
|
|
||||||
Preferred app entry:
|
Preferred app entry:
|
||||||
|
|
||||||
|
|
@ -158,11 +161,11 @@ defer client.deinit();
|
||||||
|
|
||||||
### Time
|
### Time
|
||||||
|
|
||||||
0.16 routes time through `std.Io` types:
|
0.16 routes runtime-dependent time through `std.Io`. Choose an explicit
|
||||||
|
`std.Io.Clock` and pass `io`; for example, use
|
||||||
- `std.time.Instant` -> `std.Io.Timestamp`
|
`std.Io.Timestamp.now(io, .real)` for wall time and `.boot` or `.awake` for
|
||||||
- `std.time.Timer` -> `std.Io.Timestamp`
|
elapsed-time measurements. `std.Io.Timestamp` is not a one-for-one timer
|
||||||
- `std.time.timestamp` -> `std.Io.Timestamp.now`
|
replacement: preserve the old callsite's clock semantics during migration.
|
||||||
|
|
||||||
Applications may centralize timestamp reads behind a shared helper so clock selection and timestamp semantics remain consistent.
|
Applications may centralize timestamp reads behind a shared helper so clock selection and timestamp semantics remain consistent.
|
||||||
|
|
||||||
|
|
@ -190,7 +193,9 @@ Migration map:
|
||||||
- `std.Thread.RwLock` -> `std.Io.RwLock`
|
- `std.Thread.RwLock` -> `std.Io.RwLock`
|
||||||
- `std.Thread.ResetEvent` -> `std.Io.Event`
|
- `std.Thread.ResetEvent` -> `std.Io.Event`
|
||||||
- `std.Thread.WaitGroup` -> `std.Io.Group`
|
- `std.Thread.WaitGroup` -> `std.Io.Group`
|
||||||
- `std.Thread.Futex` -> `std.Io.Futex`
|
- `std.Thread.Futex` operations -> `io.futexWait`, `io.futexWaitTimeout`,
|
||||||
|
`io.futexWaitUncancelable`, and `io.futexWake` on a suitably aligned
|
||||||
|
four-byte value
|
||||||
|
|
||||||
Cancelable lock:
|
Cancelable lock:
|
||||||
|
|
||||||
|
|
@ -212,12 +217,14 @@ Do not replace these with custom spin loops. Use std primitives unless a measure
|
||||||
|
|
||||||
### Removed or Deprecated
|
### Removed or Deprecated
|
||||||
|
|
||||||
- `@Type` removed. Use `@Int`, `@Struct`, `@Union`, `@Enum`, `@Pointer`, `@Fn`, `@Tuple`, `@Opaque`, or `@EnumLiteral`.
|
- `@Type` removed. Use `@Int`, `@Struct`, `@Union`, `@Enum`, `@Pointer`,
|
||||||
|
`@Fn`, `@Tuple`, or `@EnumLiteral`. Declare opaque types with `opaque {}`.
|
||||||
- `@cImport` is deprecated long-term. Prefer `b.addTranslateC(...)` and import `translate_c.createModule()`.
|
- `@cImport` is deprecated long-term. Prefer `b.addTranslateC(...)` and import `translate_c.createModule()`.
|
||||||
- `async`/`await` keywords remain removed; use `std.Io` task APIs, an application scheduler, or explicit threads.
|
- `async`/`await` keywords remain removed; use `std.Io` task APIs, an application scheduler, or explicit threads.
|
||||||
- `usingnamespace` is removed; explicitly re-export names.
|
- `usingnamespace` is removed; explicitly re-export names.
|
||||||
- `@fence` is removed; use stronger atomic orderings or RMW operations.
|
- `@fence` is removed; use stronger atomic orderings or RMW operations.
|
||||||
- `@intFromFloat` is deprecated; use `@trunc` when truncating float to integer.
|
- `@intFromFloat` converts a finite, in-range float to an integer. `@trunc`
|
||||||
|
truncates a floating value while retaining a floating result type.
|
||||||
|
|
||||||
### Packed and Extern Layout
|
### Packed and Extern Layout
|
||||||
|
|
||||||
|
|
@ -253,7 +260,8 @@ try list.append(gpa, 42);
|
||||||
- Priority queues use `push`/`pop` terminology and `.empty` initialization.
|
- Priority queues use `push`/`pop` terminology and `.empty` initialization.
|
||||||
- `std.SegmentedList` removed.
|
- `std.SegmentedList` removed.
|
||||||
- `std.heap.ThreadSafe` removed.
|
- `std.heap.ThreadSafe` removed.
|
||||||
- `std.heap.ArenaAllocator` is thread-safe and lock-free.
|
- `std.heap.ArenaAllocator`'s allocator interface is thread-safe when its child
|
||||||
|
allocator is thread-safe. Do not assume a universal lock-free guarantee.
|
||||||
|
|
||||||
## Critical: Build System
|
## Critical: Build System
|
||||||
|
|
||||||
|
|
@ -362,6 +370,6 @@ Load these selectively:
|
||||||
- **[Zig Patterns](references/patterns.md)** - Idiomatic patterns. Treat 0.16 release-note reference as authoritative when examples conflict.
|
- **[Zig Patterns](references/patterns.md)** - Idiomatic patterns. Treat 0.16 release-note reference as authoritative when examples conflict.
|
||||||
- **[Code Review](references/code-review.md)** - Review checklist. Always apply the 0.16 release-note checks first.
|
- **[Code Review](references/code-review.md)** - Review checklist. Always apply the 0.16 release-note checks first.
|
||||||
|
|
||||||
General references remain available for specific modules: `std.mem`, `std.fmt`, `std.json`, `std.zon`, `std.crypto`, `std.http`, `std.net`, `std.compress`, `std.tar`, `std.zip`, `std.testing`, `std.debug`, `std.log`, `std.atomic`, `std.simd`, and other files under `references/`.
|
General references remain available for specific modules: `std.mem`, `std.fmt`, `std.json`, `std.zon`, `std.crypto`, `std.http`, `std.Io.net`, `std.compress`, `std.tar`, `std.zip`, `std.testing`, `std.debug`, `std.log`, `std.atomic`, `std.simd`, and other files under `references/`.
|
||||||
|
|
||||||
If any reference still contains a historical 0.14/0.15 example, the Zig 0.16 release-notes reference and the top-level rules in this file win.
|
If any reference still contains a historical 0.14/0.15 example, the Zig 0.16 release-notes reference and the top-level rules in this file win.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Zig Built-in Functions Reference
|
# Selected Zig Built-in Functions Reference
|
||||||
|
|
||||||
Built-in functions are compiler intrinsics prefixed with `@`. Parameters marked `comptime` must be compile-time known.
|
Built-in functions are compiler intrinsics prefixed with `@`. Parameters marked `comptime` must be compile-time known. This is a practical reference to commonly used built-ins, not an exhaustive inventory; consult the language reference when completeness matters.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Type Conversions](#type-conversions)
|
- [Type Conversions](#type-conversions)
|
||||||
|
|
@ -199,10 +199,10 @@ const x = @abs(@as(i32, -5)); // 5
|
||||||
|
|
||||||
### @min / @max
|
### @min / @max
|
||||||
```zig
|
```zig
|
||||||
@min(a: T, b: T) T
|
@min(a: T, b: T, ...) T
|
||||||
@max(a: T, b: T) T
|
@max(a: T, b: T, ...) T
|
||||||
```
|
```
|
||||||
Return minimum/maximum of two values.
|
Return the minimum/maximum of two or more values.
|
||||||
```zig
|
```zig
|
||||||
const m = @max(3, 7); // 7
|
const m = @max(3, 7); // 7
|
||||||
```
|
```
|
||||||
|
|
@ -546,13 +546,13 @@ Get type of a struct field.
|
||||||
|
|
||||||
### @fieldParentPtr
|
### @fieldParentPtr
|
||||||
```zig
|
```zig
|
||||||
@fieldParentPtr(field_ptr: anytype, comptime field_name: []const u8) anytype
|
@fieldParentPtr(comptime field_name: []const u8, field_ptr: anytype) anytype
|
||||||
```
|
```
|
||||||
Get pointer to containing struct from field pointer (for intrusive data structures).
|
Get pointer to containing struct from field pointer (for intrusive data structures).
|
||||||
```zig
|
```zig
|
||||||
const Node = struct { data: u32, hook: Hook };
|
const Node = struct { data: u32, hook: Hook };
|
||||||
fn getNode(hook: *Hook) *Node {
|
fn getNode(hook: *Hook) *Node {
|
||||||
return @fieldParentPtr(hook, "hook");
|
return @fieldParentPtr("hook", hook);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -629,7 +629,7 @@ fn method(self: *Self) void { ... }
|
||||||
```zig
|
```zig
|
||||||
@src() std.builtin.SourceLocation
|
@src() std.builtin.SourceLocation
|
||||||
```
|
```
|
||||||
Get current source location (file, line, column, fn name).
|
Get current source location (module, file, line, column, and function name).
|
||||||
|
|
||||||
### @inComptime
|
### @inComptime
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -771,7 +771,7 @@ if (unlikely_condition) {
|
||||||
// rarely executed
|
// rarely executed
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Hints: `.none`, `.likely`, `.unlikely`, `.cold`
|
Hints: `.none`, `.likely`, `.unlikely`, `.cold`, `.unpredictable`
|
||||||
|
|
||||||
### @breakpoint
|
### @breakpoint
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -836,7 +836,7 @@ Call function with modifier.
|
||||||
```zig
|
```zig
|
||||||
const result = @call(.always_inline, my_fn, .{ arg1, arg2 });
|
const result = @call(.always_inline, my_fn, .{ arg1, arg2 });
|
||||||
```
|
```
|
||||||
Modifiers: `.auto`, `.never_inline`, `.always_inline`, `.always_tail`, `.never_tail`, `.compile_time`
|
Modifiers: `.auto`, `.never_inline`, `.always_inline`, `.always_tail`, `.never_tail`, `.compile_time`, `.no_suspend`. The `.no_suspend` modifier asserts that the call will not suspend.
|
||||||
|
|
||||||
### @prefetch
|
### @prefetch
|
||||||
```zig
|
```zig
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ Minimal C-compatible library:
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
// Global state (opaque to C consumers)
|
// Global state (opaque to C consumers). This minimal example is not
|
||||||
|
// thread-safe; init must be called exactly once before get/set/deinit.
|
||||||
var context: ?*Context = null;
|
var context: ?*Context = null;
|
||||||
|
|
||||||
const Context = struct {
|
const Context = struct {
|
||||||
|
|
@ -36,6 +37,7 @@ const Context = struct {
|
||||||
|
|
||||||
/// Initialize the library. Returns 0 on success, -1 on failure.
|
/// Initialize the library. Returns 0 on success, -1 on failure.
|
||||||
export fn mylib_init() c_int {
|
export fn mylib_init() c_int {
|
||||||
|
if (context != null) return -1;
|
||||||
const gpa = std.heap.c_allocator;
|
const gpa = std.heap.c_allocator;
|
||||||
context = gpa.create(Context) catch return -1;
|
context = gpa.create(Context) catch return -1;
|
||||||
context.?.* = .{ .allocator = gpa, .value = 0 };
|
context.?.* = .{ .allocator = gpa, .value = 0 };
|
||||||
|
|
@ -76,12 +78,10 @@ pub fn build(b: *std.Build) void {
|
||||||
.root_source_file = b.path("src/lib.zig"),
|
.root_source_file = b.path("src/lib.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Link libc if using std.heap.c_allocator
|
|
||||||
lib.linkLibC();
|
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
|
|
||||||
// Install header alongside library
|
// Install header alongside library
|
||||||
|
|
@ -284,10 +284,9 @@ const lib = b.addLibrary(.{
|
||||||
.root_source_file = b.path("src/lib.zig"),
|
.root_source_file = b.path("src/lib.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.link_libc = true, // Required by c_allocator or direct libc calls.
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
lib.linkLibC(); // If using c_allocator or libc functions
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -301,11 +300,11 @@ const lib = b.addLibrary(.{
|
||||||
.root_source_file = b.path("src/lib.zig"),
|
.root_source_file = b.path("src/lib.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
}),
|
}),
|
||||||
.version = .{ .major = 1, .minor = 0, .patch = 0 },
|
.version = .{ .major = 1, .minor = 0, .patch = 0 },
|
||||||
});
|
});
|
||||||
|
|
||||||
lib.linkLibC();
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -406,7 +405,9 @@ const std = @import("std");
|
||||||
|
|
||||||
pub const Context = struct {
|
pub const Context = struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
error_msg: ?[]const u8 = null,
|
// Storage is owned by this context and remains valid until the next
|
||||||
|
// operation that changes the error or until mylib_destroy.
|
||||||
|
error_msg: ?[:0]const u8 = null,
|
||||||
callback: ?Callback = null,
|
callback: ?Callback = null,
|
||||||
|
|
||||||
const Callback = struct {
|
const Callback = struct {
|
||||||
|
|
@ -417,7 +418,9 @@ pub const Context = struct {
|
||||||
|
|
||||||
export fn mylib_create() ?*Context {
|
export fn mylib_create() ?*Context {
|
||||||
const allocator = std.heap.c_allocator;
|
const allocator = std.heap.c_allocator;
|
||||||
return allocator.create(Context) catch null;
|
const ctx = allocator.create(Context) catch return null;
|
||||||
|
ctx.* = .{ .allocator = allocator };
|
||||||
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn mylib_destroy(ctx: ?*Context) void {
|
export fn mylib_destroy(ctx: ?*Context) void {
|
||||||
|
|
@ -663,7 +666,7 @@ Name: MyLib
|
||||||
Functions:
|
Functions:
|
||||||
- Name: mylib_create
|
- Name: mylib_create
|
||||||
SwiftName: "MyLibContext.create()"
|
SwiftName: "MyLibContext.create()"
|
||||||
NullabilityOfRet: N # Non-null (returns Optional in Swift)
|
NullabilityOfRet: O # Nullable C pointer; imported as Optional in Swift
|
||||||
- Name: mylib_destroy
|
- Name: mylib_destroy
|
||||||
SwiftName: "MyLibContext.destroy(self:)"
|
SwiftName: "MyLibContext.destroy(self:)"
|
||||||
- Name: mylib_get_error
|
- Name: mylib_get_error
|
||||||
|
|
@ -802,43 +805,29 @@ export fn get_greeting() [*:0]const u8 {
|
||||||
return greeting.ptr;
|
return greeting.ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate string for caller to free
|
// Allocate a sentinel-terminated string. The caller must pass the same
|
||||||
|
// payload length to free_string; the sentinel is stored at index len.
|
||||||
export fn alloc_string(len: usize) ?[*:0]u8 {
|
export fn alloc_string(len: usize) ?[*:0]u8 {
|
||||||
const allocator = std.heap.c_allocator;
|
const allocator = std.heap.c_allocator;
|
||||||
const buf = allocator.allocSentinel(u8, len, 0) catch return null;
|
const buf = allocator.allocSentinel(u8, len, 0) catch return null;
|
||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn free_string(s: ?[*:0]u8) void {
|
export fn free_string(s: ?[*:0]u8, len: usize) void {
|
||||||
if (s) |ptr| {
|
if (s) |ptr| {
|
||||||
const allocator = std.heap.c_allocator;
|
const allocator = std.heap.c_allocator;
|
||||||
// Need to know length to free - typically tracked separately
|
allocator.free(ptr[0..len :0]);
|
||||||
// or use c_allocator which can query allocation size
|
|
||||||
_ = allocator;
|
|
||||||
_ = ptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Thread Safety
|
### Thread Safety
|
||||||
|
|
||||||
For thread-safe libraries, use atomics or mutexes:
|
For simple exported counters, atomics avoid the need to expose an I/O context across the C ABI. More complex synchronization in Zig 0.16 uses `std.Io.Mutex`, whose lock operations take an `std.Io` value; design that runtime context into the library rather than using the removed `std.Thread.Mutex` API.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
var global_mutex: std.Thread.Mutex = .{};
|
|
||||||
var shared_value: c_int = 0;
|
|
||||||
|
|
||||||
export fn thread_safe_increment() c_int {
|
|
||||||
global_mutex.lock();
|
|
||||||
defer global_mutex.unlock();
|
|
||||||
|
|
||||||
shared_value += 1;
|
|
||||||
return shared_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or use atomics for simple cases
|
|
||||||
var atomic_counter: std.atomic.Value(c_int) = .init(0);
|
var atomic_counter: std.atomic.Value(c_int) = .init(0);
|
||||||
|
|
||||||
export fn atomic_increment() c_int {
|
export fn atomic_increment() c_int {
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ Systematic code review checklist organized by detection confidence level. Work t
|
||||||
|--------|------|--------|
|
|--------|------|--------|
|
||||||
| `@intCast(val)` without bounds check | Runtime panic | No `std.math.cast` or prior validation |
|
| `@intCast(val)` without bounds check | Runtime panic | No `std.math.cast` or prior validation |
|
||||||
| `.?` unwrap | Runtime panic | Not guarded by `if` or `orelse` |
|
| `.?` unwrap | Runtime panic | Not guarded by `if` or `orelse` |
|
||||||
| `catch unreachable` on alloc/create | Runtime panic | Allocation can fail |
|
| Unjustified `catch unreachable` on alloc/create | Runtime panic | Allocation can fail and no capacity bound proves otherwise |
|
||||||
| `return &local_var` | Dangling pointer | Returns address of stack variable |
|
| `return &local_var` | Dangling pointer | Returns address of stack variable |
|
||||||
| `&packed_struct.field` | Undefined behavior | Pointer to packed struct field |
|
| `&packed_struct.field` | Undefined behavior | Pointer to packed struct field |
|
||||||
| pointer field in `packed struct`/`packed union` | Forbidden in 0.16 | Compile error / invalid layout | Store integer representation only if ABI requires it |
|
| pointer field in `packed struct`/`packed union` | Forbidden in 0.16 | Compile error / invalid layout | Store integer representation only if ABI requires it |
|
||||||
|
|
@ -395,7 +395,7 @@ fn getName(user: ?*User) []const u8 {
|
||||||
|
|
||||||
#### 1.6.3 Catch Unreachable on Allocation
|
#### 1.6.3 Catch Unreachable on Allocation
|
||||||
|
|
||||||
| Detect | `catch unreachable` after `alloc`/`create` calls |
|
| Detect | Unjustified `catch unreachable` after `alloc`/`create` calls |
|
||||||
|--------|--------------------------------------------------|
|
|--------|--------------------------------------------------|
|
||||||
| Risk | Runtime panic on OOM |
|
| Risk | Runtime panic on OOM |
|
||||||
|
|
||||||
|
|
@ -413,7 +413,7 @@ fn createBuffer(allocator: Allocator) !*Buffer {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Verify:** Search for `catch unreachable`, check if operation can fail.
|
**Verify:** Search for `catch unreachable` and check whether failure is genuinely impossible under a documented invariant. It can be appropriate after a successful reservation or with a fixed, proven capacity bound; ordinary allocation failure should be propagated.
|
||||||
|
|
||||||
#### 1.6.4 Returning Stack Pointer
|
#### 1.6.4 Returning Stack Pointer
|
||||||
|
|
||||||
|
|
@ -536,7 +536,7 @@ fn parseColor(byte: u8) Color {
|
||||||
**Right:**
|
**Right:**
|
||||||
```zig
|
```zig
|
||||||
fn parseColor(byte: u8) ?Color {
|
fn parseColor(byte: u8) ?Color {
|
||||||
return std.meta.intToEnum(Color, byte) catch null;
|
return std.enums.fromInt(Color, byte);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -570,10 +570,10 @@ pub fn main() !void {
|
||||||
|
|
||||||
**Verify:** Search for `DebugAllocator`, check for `defer.*deinit()`.
|
**Verify:** Search for `DebugAllocator`, check for `defer.*deinit()`.
|
||||||
|
|
||||||
#### 1.7.2 @ptrCast Size Mismatch
|
#### 1.7.2 Unsafe @ptrCast Access
|
||||||
|
|
||||||
| Detect | `@ptrCast` between types of different sizes |
|
| Detect | `@ptrCast` whose result may be under-aligned or address more memory than the source allocation provides |
|
||||||
|--------|---------------------------------------------|
|
|--------|------------------------------------------------------------------------------------------------|
|
||||||
| Risk | Memory corruption |
|
| Risk | Memory corruption |
|
||||||
|
|
||||||
**Wrong:**
|
**Wrong:**
|
||||||
|
|
@ -585,12 +585,14 @@ fn dangerous(ptr: *u32) *u64 {
|
||||||
|
|
||||||
**Right:**
|
**Right:**
|
||||||
```zig
|
```zig
|
||||||
fn reinterpret(ptr: *u32) *[4]u8 {
|
fn firstBytes(ptr: *u32) *align(@alignOf(u32)) [4]u8 {
|
||||||
return @ptrCast(ptr); // Same size
|
// The u32 object provides four accessible bytes and the result preserves
|
||||||
|
// the source pointer's alignment contract.
|
||||||
|
return @ptrCast(ptr);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Verify:** Check `@ptrCast` source and target sizes match.
|
**Verify:** Check the destination alignment, the number of bytes callers may access through the result, aliasing/lifetime constraints, and whether a byte copy would express the intent more safely. Equal pointee sizes alone do not make a cast valid.
|
||||||
|
|
||||||
#### 1.7.3 Packed Struct Field Pointer
|
#### 1.7.3 Packed Struct Field Pointer
|
||||||
|
|
||||||
|
|
@ -1311,13 +1313,13 @@ fn formatVersion(allocator: Allocator, major: u32, minor: u32) ![]u8 {
|
||||||
|
|
||||||
**Right (caller provides buffer):**
|
**Right (caller provides buffer):**
|
||||||
```zig
|
```zig
|
||||||
fn formatVersion(buf: []u8, major: u32, minor: u32) []u8 {
|
fn formatVersion(buf: []u8, major: u32, minor: u32) ![]u8 {
|
||||||
return std.fmt.bufPrint(buf, "{d}.{d}", .{ major, minor }) catch unreachable;
|
return std.fmt.bufPrint(buf, "{d}.{d}", .{ major, minor });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call site — buffer outlives the returned slice
|
// Call site — buffer outlives the returned slice
|
||||||
var buf: [32]u8 = undefined;
|
var buf: [32]u8 = undefined;
|
||||||
const version = formatVersion(&buf, 1, 2);
|
const version = try formatVersion(&buf, 1, 2);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.11 Comptime Optimization
|
### 3.11 Comptime Optimization
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ comptime {
|
||||||
|
|
||||||
### Container-Level Comptime
|
### Container-Level Comptime
|
||||||
|
|
||||||
Top-level declarations are implicitly comptime.
|
Top-level constants whose values must be compile-time known are evaluated on demand at compile time. This does not make every top-level declaration a `comptime` block or imply eager evaluation.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// These are computed at compile time automatically
|
// These are computed at compile time automatically
|
||||||
|
|
@ -187,7 +187,7 @@ fn sumComptime(comptime values: []const i32) i32 {
|
||||||
|
|
||||||
### inline for
|
### inline for
|
||||||
|
|
||||||
Loop unrolling with code generation. Body is duplicated per iteration. Can reference runtime values. Cannot use `break` to return values.
|
Loop unrolling with code generation. Body is duplicated per iteration and may reference runtime values. Like other loop expressions, an `inline for` can use `break` with a value when its control flow is valid.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn printFields(value: anytype) void {
|
fn printFields(value: anytype) void {
|
||||||
|
|
@ -219,7 +219,7 @@ fn eqlAny(comptime T: type, a: T, b: T) bool {
|
||||||
|
|
||||||
| Need | Use | Reason |
|
| Need | Use | Reason |
|
||||||
|------|-----|--------|
|
|------|-----|--------|
|
||||||
| Return value from loop | `comptime for` | Only comptime allows `break` with value |
|
| Evaluate the entire loop and its result at compile time | `comptime for` | All values and control flow must be comptime-known |
|
||||||
| Access runtime values in body | `inline for` | Comptime can't see runtime |
|
| Access runtime values in body | `inline for` | Comptime can't see runtime |
|
||||||
| Type-level computation only | `comptime for` | Clearer intent, no code gen |
|
| Type-level computation only | `comptime for` | Clearer intent, no code gen |
|
||||||
| Generate code per iteration | `inline for` | Each iteration = separate code |
|
| Generate code per iteration | `inline for` | Each iteration = separate code |
|
||||||
|
|
@ -250,6 +250,8 @@ const builtin = @import("builtin");
|
||||||
const native_endian = builtin.cpu.arch.endian();
|
const native_endian = builtin.cpu.arch.endian();
|
||||||
|
|
||||||
pub fn readIntBig(comptime T: type, bytes: []const u8) T {
|
pub fn readIntBig(comptime T: type, bytes: []const u8) T {
|
||||||
|
comptime std.debug.assert(@typeInfo(T) == .int);
|
||||||
|
std.debug.assert(bytes.len >= @sizeOf(T));
|
||||||
const value: T = @bitCast(bytes[0..@sizeOf(T)].*);
|
const value: T = @bitCast(bytes[0..@sizeOf(T)].*);
|
||||||
if (comptime native_endian == .big) {
|
if (comptime native_endian == .big) {
|
||||||
return value;
|
return value;
|
||||||
|
|
@ -261,15 +263,15 @@ pub fn readIntBig(comptime T: type, bytes: []const u8) T {
|
||||||
|
|
||||||
### Propagating Across Functions
|
### Propagating Across Functions
|
||||||
|
|
||||||
Use `inline fn` to propagate comptime conditions to call sites:
|
Comptime parameters specialize the function and make conditions that depend on them compile-time known. `inline fn` additionally requests call-site inlining; it is not required merely to eliminate a comptime-known branch:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// WITHOUT inline: branch exists at runtime
|
// The enabled branch is specialized at compile time.
|
||||||
fn maybeLog(comptime enabled: bool, msg: []const u8) void {
|
fn maybeLog(comptime enabled: bool, msg: []const u8) void {
|
||||||
if (enabled) std.debug.print("{s}\n", .{msg});
|
if (enabled) std.debug.print("{s}\n", .{msg});
|
||||||
}
|
}
|
||||||
|
|
||||||
// WITH inline: branch eliminated at each call site
|
// inline additionally requests call-site inlining.
|
||||||
inline fn maybeLogInline(comptime enabled: bool, msg: []const u8) void {
|
inline fn maybeLogInline(comptime enabled: bool, msg: []const u8) void {
|
||||||
if (comptime enabled) std.debug.print("{s}\n", .{msg});
|
if (comptime enabled) std.debug.print("{s}\n", .{msg});
|
||||||
}
|
}
|
||||||
|
|
@ -414,11 +416,15 @@ fn getTypeName(value: anytype) []const u8 {
|
||||||
fn typeFromName(name: []const u8) type { ... }
|
fn typeFromName(name: []const u8) type { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
### No I/O at Comptime
|
### Comptime I/O Boundary
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// NOT POSSIBLE
|
// Ordinary runtime filesystem I/O is not available at comptime.
|
||||||
const config = comptime std.fs.cwd().readFile("config.json");
|
// This is intentionally invalid; Zig 0.16 has no such comptime std.Io call:
|
||||||
|
// const config = comptime std.Io.Dir.cwd().readFileAlloc(...);
|
||||||
|
|
||||||
|
// Compiler-supported static input is available:
|
||||||
|
const embedded = @embedFile("config.json");
|
||||||
|
|
||||||
// Alternatives:
|
// Alternatives:
|
||||||
const config = @embedFile("config.json"); // Static embedding
|
const config = @embedFile("config.json"); // Static embedding
|
||||||
|
|
@ -448,7 +454,7 @@ fn addMethod(comptime T: type, comptime name: []const u8, impl: anytype) type {
|
||||||
| Type reflection | Yes | `@typeInfo`, `@TypeOf` |
|
| Type reflection | Yes | `@typeInfo`, `@TypeOf` |
|
||||||
| Generate types | Yes | Return struct from function |
|
| Generate types | Yes | Return struct from function |
|
||||||
| Add methods to types | No | Define in type definition |
|
| Add methods to types | No | Define in type definition |
|
||||||
| Read files | No | `@embedFile` or build.zig |
|
| Read files | Static inputs only | `@embedFile`; use build.zig for ordinary I/O |
|
||||||
| Syscalls | No | build.zig runs as program |
|
| Syscalls | No | build.zig runs as program |
|
||||||
| Parse strings to code | No | Parse to data structures |
|
| Parse strings to code | No | Parse to data structures |
|
||||||
| Host detection | No | Build system queries |
|
| Host detection | No | Build system queries |
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ Key 0.16 language changes:
|
||||||
|
|
||||||
### Primitive Types
|
### Primitive Types
|
||||||
```zig
|
```zig
|
||||||
// Integers (signed and unsigned, any bit width 1-65535)
|
// Integers (signed and unsigned, including arbitrary-width integer types)
|
||||||
i8, i16, i32, i64, i128, isize // signed
|
i8, i16, i32, i64, i128, isize // signed
|
||||||
u8, u16, u32, u64, u128, usize // unsigned
|
u8, u16, u32, u64, u128, usize // unsigned
|
||||||
i7, u24, i53 // arbitrary widths
|
i7, u24, i53 // arbitrary widths
|
||||||
|
|
@ -75,6 +75,7 @@ const z: u32 = @bitCast(float_val); // reinterpret bits
|
||||||
```zig
|
```zig
|
||||||
// Fixed-size arrays
|
// Fixed-size arrays
|
||||||
const arr: [5]u8 = .{ 1, 2, 3, 4, 5 };
|
const arr: [5]u8 = .{ 1, 2, 3, 4, 5 };
|
||||||
|
var mutable_arr: [5]u8 = .{ 1, 2, 3, 4, 5 };
|
||||||
const arr2 = [_]u8{ 1, 2, 3 }; // infer length
|
const arr2 = [_]u8{ 1, 2, 3 }; // infer length
|
||||||
const zeros = [_]u8{0} ** 100; // repeat pattern
|
const zeros = [_]u8{0} ** 100; // repeat pattern
|
||||||
|
|
||||||
|
|
@ -89,7 +90,7 @@ const len = arr.len;
|
||||||
// Iteration
|
// Iteration
|
||||||
for (arr) |elem| { ... }
|
for (arr) |elem| { ... }
|
||||||
for (arr, 0..) |elem, i| { ... } // with index
|
for (arr, 0..) |elem, i| { ... } // with index
|
||||||
for (&arr) |*elem| { elem.* = 0; } // mutable
|
for (&mutable_arr) |*elem| { elem.* = 0; } // mutable
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tuples
|
### Tuples
|
||||||
|
|
@ -220,7 +221,10 @@ for (a, b, c) |x, y, z| { ... }
|
||||||
// Mutable iteration
|
// Mutable iteration
|
||||||
for (&items) |*item| { item.* = new_value; }
|
for (&items) |*item| { item.* = new_value; }
|
||||||
|
|
||||||
// Range (comptime only for runtime, but works in comptime blocks)
|
// Runtime bounds work with an ordinary for loop.
|
||||||
|
for (start..end) |i| { ... }
|
||||||
|
|
||||||
|
// Use inline for when the range is comptime-known and should be unrolled.
|
||||||
inline for (0..10) |i| { ... }
|
inline for (0..10) |i| { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -257,7 +261,7 @@ fn example() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only runs on error return
|
// Only runs on error return
|
||||||
fn example() !void {
|
fn example() !*Resource {
|
||||||
const ptr = try allocate();
|
const ptr = try allocate();
|
||||||
errdefer free(ptr); // runs only if function returns error
|
errdefer free(ptr); // runs only if function returns error
|
||||||
try doSomething(ptr);
|
try doSomething(ptr);
|
||||||
|
|
@ -587,8 +591,12 @@ const ptr: [*]u8 = buffer.ptr;
|
||||||
const next = ptr + 1;
|
const next = ptr + 1;
|
||||||
const offset = ptr + n;
|
const offset = ptr + n;
|
||||||
|
|
||||||
// Single-item pointers do NOT support arithmetic
|
// A single-item pointer can only establish a one-element slice by itself.
|
||||||
// Use slicing instead:
|
const one_ptr: *u8 = ...;
|
||||||
|
const one_item = one_ptr[0..1];
|
||||||
|
|
||||||
|
// Slicing an arbitrary length requires a many-item pointer plus an external
|
||||||
|
// guarantee that at least n elements are accessible.
|
||||||
const slice = ptr[0..n];
|
const slice = ptr[0..n];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -650,15 +658,9 @@ fn isInteger(comptime T: type) bool {
|
||||||
return @typeInfo(T) == .int;
|
return @typeInfo(T) == .int;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fieldNames(comptime T: type) []const []const u8 {
|
fn fieldNames(comptime T: type) *const [std.meta.fields(T).len][:0]const u8 {
|
||||||
const info = @typeInfo(T);
|
// std.meta.fieldNames returns comptime-backed fixed storage.
|
||||||
if (info != .@"struct") @compileError("expected struct");
|
return std.meta.fieldNames(T);
|
||||||
|
|
||||||
var names: [info.@"struct".fields.len][]const u8 = undefined;
|
|
||||||
for (info.@"struct".fields, 0..) |field, i| {
|
|
||||||
names[i] = field.name;
|
|
||||||
}
|
|
||||||
return &names;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,8 @@ Comprehensive patterns for writing idiomatic Zig code. Zig 0.16.0 changes I/O ow
|
||||||
#### Allocator Setup
|
#### Allocator Setup
|
||||||
```zig
|
```zig
|
||||||
// Debug allocator (development - detects leaks, use-after-free)
|
// Debug allocator (development - detects leaks, use-after-free)
|
||||||
// Note: GeneralPurposeAllocator is now an alias for DebugAllocator
|
// DebugAllocator is the development allocator used here. Do not rely on the
|
||||||
|
// old GeneralPurposeAllocator name being an alias.
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
@ -501,21 +502,21 @@ comptime const x, const y = .{ 1, 2 };
|
||||||
**When to use:** Multiple return values, array element extraction, SIMD vector unpacking.
|
**When to use:** Multiple return values, array element extraction, SIMD vector unpacking.
|
||||||
|
|
||||||
#### Hashed Mappings Storage
|
#### Hashed Mappings Storage
|
||||||
Use multiple `AutoArrayHashMapUnmanaged` fields when storing complex interned data.
|
Use multiple `std.array_hash_map.Auto` fields when storing complex interned data. The older root-level `AutoArrayHashMapUnmanaged` name is deprecated in Zig 0.16.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// From llvm/Builder.zig - demonstrating the pattern of parallel maps for interned data
|
// From llvm/Builder.zig - demonstrating the pattern of parallel maps for interned data
|
||||||
string_map: std.AutoArrayHashMapUnmanaged(void, void),
|
string_map: std.array_hash_map.Auto(void, void),
|
||||||
string_indices: std.ArrayListUnmanaged(u32),
|
string_indices: std.ArrayListUnmanaged(u32),
|
||||||
string_bytes: std.ArrayListUnmanaged(u8),
|
string_bytes: std.ArrayListUnmanaged(u8),
|
||||||
|
|
||||||
types: std.AutoArrayHashMapUnmanaged(String, Type),
|
types: std.array_hash_map.Auto(String, Type),
|
||||||
type_map: std.AutoArrayHashMapUnmanaged(void, void),
|
type_map: std.array_hash_map.Auto(void, void),
|
||||||
type_items: std.ArrayListUnmanaged(Type.Item),
|
type_items: std.ArrayListUnmanaged(Type.Item),
|
||||||
type_extra: std.ArrayListUnmanaged(u32),
|
type_extra: std.ArrayListUnmanaged(u32),
|
||||||
|
|
||||||
attributes: std.AutoArrayHashMapUnmanaged(Attribute.Storage, void),
|
attributes: std.array_hash_map.Auto(Attribute.Storage, void),
|
||||||
attributes_map: std.AutoArrayHashMapUnmanaged(void, void),
|
attributes_map: std.array_hash_map.Auto(void, void),
|
||||||
attributes_indices: std.ArrayListUnmanaged(u32),
|
attributes_indices: std.ArrayListUnmanaged(u32),
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -751,19 +752,19 @@ try stdout.print("{f}", .{version});
|
||||||
**When to use:** Any type that needs custom string representation.
|
**When to use:** Any type that needs custom string representation.
|
||||||
|
|
||||||
#### Custom Type JSON
|
#### Custom Type JSON
|
||||||
Implement `jsonParse`, `jsonParseFromValue`, and `jsonStringify` for JSON support.
|
The example below implements token-source parsing with `jsonParse`. Complete custom JSON integration may also require `jsonParseFromValue` and `jsonStringify`, depending on the APIs callers use.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn ArrayHashMap(comptime T: type) type {
|
pub fn ArrayHashMap(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
map: std.StringArrayHashMapUnmanaged(T) = .empty,
|
map: std.array_hash_map.String(T) = .empty,
|
||||||
|
|
||||||
pub fn jsonParse(
|
pub fn jsonParse(
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
source: anytype,
|
source: anytype,
|
||||||
options: ParseOptions,
|
options: ParseOptions,
|
||||||
) !@This() {
|
) !@This() {
|
||||||
var map: std.StringArrayHashMapUnmanaged(T) = .empty;
|
var map: std.array_hash_map.String(T) = .empty;
|
||||||
errdefer map.deinit(allocator);
|
errdefer map.deinit(allocator);
|
||||||
|
|
||||||
if (.object_begin != try source.next()) return error.UnexpectedToken;
|
if (.object_begin != try source.next()) return error.UnexpectedToken;
|
||||||
|
|
@ -1096,11 +1097,9 @@ pub const Node = enum(u32) {
|
||||||
return @enumFromInt(@intFromEnum(r.start) + i);
|
return @enumFromInt(@intFromEnum(r.start) + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all nodes in range.
|
// A Range stores indices, not node values or an address. Use at() to
|
||||||
pub fn slice(r: Range) []const Node {
|
// iterate, or pass the owning node storage to an accessor that returns
|
||||||
// Note: requires nodes stored contiguously
|
// the corresponding data slice.
|
||||||
return @ptrCast(@as([*]const u32, @ptrFromInt(@intFromEnum(r.start)))[0..r.len]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1121,6 +1120,7 @@ When individual node deletion is needed, maintain a freelist stack:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub const NodePool = struct {
|
pub const NodePool = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
nodes: std.ArrayListUnmanaged(Node.Data),
|
nodes: std.ArrayListUnmanaged(Node.Data),
|
||||||
/// Head of freelist, or none if no free slots.
|
/// Head of freelist, or none if no free slots.
|
||||||
free_head: OptionalNode = .none,
|
free_head: OptionalNode = .none,
|
||||||
|
|
@ -1133,7 +1133,7 @@ pub const NodePool = struct {
|
||||||
}
|
}
|
||||||
// Allocate new slot
|
// Allocate new slot
|
||||||
const index: Node = @enumFromInt(self.nodes.items.len);
|
const index: Node = @enumFromInt(self.nodes.items.len);
|
||||||
try self.nodes.append(undefined);
|
try self.nodes.append(self.allocator, undefined);
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1356,7 +1356,7 @@ pub fn internString(state: *State, gpa: Allocator, bytes: []const u8) !String {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Real-world example from HashMap.grow:**
|
**Abbreviated pattern based on HashMap growth logic** (helper declarations such as `new_cap`, `old_capacity`, and `Self.allocate` are intentionally omitted):
|
||||||
```zig
|
```zig
|
||||||
fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void {
|
fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void {
|
||||||
var map: Self = .{};
|
var map: Self = .{};
|
||||||
|
|
@ -1388,7 +1388,7 @@ fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) All
|
||||||
- String/symbol interning (hash table + byte array)
|
- String/symbol interning (hash table + byte array)
|
||||||
- Any operation where failure after partial mutation leaves invalid state
|
- Any operation where failure after partial mutation leaves invalid state
|
||||||
|
|
||||||
**Key insight:** `ensureUnusedCapacity` is magic—it contains all the failure modes but changes nothing. Reservation failures are safe to retry; partial mutations are not.
|
**Key insight:** `ensureUnusedCapacity` concentrates the fallible reservation step. On error it leaves the container unchanged; on success it may allocate, rehash, change capacity, and invalidate pointers. Reservation failures are safe to retry; partial logical mutations are not.
|
||||||
|
|
||||||
### IV. Performance Patterns
|
### IV. Performance Patterns
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,12 @@ Zig has no default allocator. Functions that need heap memory accept an `Allocat
|
||||||
|-----------|----------|-------------|
|
|-----------|----------|-------------|
|
||||||
| `std.testing.allocator` | Unit tests (leak detection) | No |
|
| `std.testing.allocator` | Unit tests (leak detection) | No |
|
||||||
| `std.heap.FixedBufferAllocator` | Stack-based, bounded size known | Optional |
|
| `std.heap.FixedBufferAllocator` | Stack-based, bounded size known | Optional |
|
||||||
| `std.heap.ArenaAllocator` | Batch free, CLI apps, request handlers | No |
|
| `std.heap.ArenaAllocator` | Batch free, CLI apps, request handlers | Allocator interface only, when child is thread-safe |
|
||||||
| `std.heap.page_allocator` | Backing for other allocators | Yes |
|
| `std.heap.page_allocator` | Backing for other allocators | Yes |
|
||||||
| `std.heap.c_allocator` | Linking libc, interop | Yes |
|
| `std.heap.c_allocator` | Linking libc, interop | Yes |
|
||||||
| `std.heap.raw_c_allocator` | Libc arena backing (no alignment overhead) | Yes |
|
|
||||||
| `std.heap.DebugAllocator` | Debug builds, leak/corruption detection | Configurable |
|
| `std.heap.DebugAllocator` | Debug builds, leak/corruption detection | Configurable |
|
||||||
| `std.heap.smp_allocator` | ReleaseFast production multithreaded | Yes |
|
| `std.heap.smp_allocator` | ReleaseFast production multithreaded | Yes |
|
||||||
| `std.heap.MemoryPool` | High-frequency same-type allocations | No |
|
| `std.heap.MemoryPool` | High-frequency same-type allocations | No |
|
||||||
| `std.heap.ThreadSafeAllocator` | Wrap non-thread-safe allocator | Yes |
|
|
||||||
| `std.heap.StackFallbackAllocator` | Stack buffer with heap fallback | Depends |
|
| `std.heap.StackFallbackAllocator` | Stack buffer with heap fallback | Depends |
|
||||||
| `std.heap.wasm_allocator` | WebAssembly targets | Yes |
|
| `std.heap.wasm_allocator` | WebAssembly targets | Yes |
|
||||||
|
|
||||||
|
|
@ -156,7 +154,7 @@ slice = try allocator.realloc(slice, new_len);
|
||||||
7. **Many same-type objects?** Use `MemoryPool(T)` for fast create/destroy
|
7. **Many same-type objects?** Use `MemoryPool(T)` for fast create/destroy
|
||||||
8. **Debug build?** Use `DebugAllocator` for leak/corruption detection
|
8. **Debug build?** Use `DebugAllocator` for leak/corruption detection
|
||||||
9. **ReleaseFast production?** Use `std.heap.smp_allocator`
|
9. **ReleaseFast production?** Use `std.heap.smp_allocator`
|
||||||
10. **Linking libc?** Use `c_allocator` or `raw_c_allocator` (as arena backing)
|
10. **Linking libc?** Use `c_allocator`
|
||||||
|
|
||||||
## Common Allocators
|
## Common Allocators
|
||||||
|
|
||||||
|
|
@ -187,7 +185,7 @@ allocator.free(data);
|
||||||
fba.reset();
|
fba.reset();
|
||||||
```
|
```
|
||||||
|
|
||||||
**Thread-safe variant** (allocate only - no resize/free):
|
**Thread-safe variant:** allocation is lock-free. Resize, remap, and free are supported, but only the most recent allocation can be expanded or release arena space; shrinking an older allocation succeeds without reclaiming its bytes.
|
||||||
```zig
|
```zig
|
||||||
const ts_allocator = fba.threadSafeAllocator();
|
const ts_allocator = fba.threadSafeAllocator();
|
||||||
```
|
```
|
||||||
|
|
@ -231,9 +229,9 @@ while (running) {
|
||||||
- `.retain_capacity` - Keep allocated pages for reuse (faster)
|
- `.retain_capacity` - Keep allocated pages for reuse (faster)
|
||||||
- `.{ .retain_with_limit = N }` - Retain up to N bytes
|
- `.{ .retain_with_limit = N }` - Retain up to N bytes
|
||||||
|
|
||||||
**Query current usage:**
|
**Query retained capacity:**
|
||||||
```zig
|
```zig
|
||||||
const bytes_used = arena.queryCapacity(); // Excludes internal overhead
|
const retained_bytes = arena.queryCapacity(); // Excludes internal overhead
|
||||||
```
|
```
|
||||||
|
|
||||||
**State optimization** - store just the state to save memory:
|
**State optimization** - store just the state to save memory:
|
||||||
|
|
@ -305,47 +303,34 @@ const allocator = std.heap.page_allocator;
|
||||||
Fast allocator for many objects of the same type. Outperforms general-purpose allocators when allocating/freeing objects in rapid succession:
|
Fast allocator for many objects of the same type. Outperforms general-purpose allocators when allocating/freeing objects in rapid succession:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var pool = std.heap.MemoryPool(MyStruct).init(std.heap.page_allocator);
|
const allocator = std.heap.page_allocator;
|
||||||
defer pool.deinit();
|
var pool: std.heap.MemoryPool(MyStruct) = .empty;
|
||||||
|
defer pool.deinit(allocator);
|
||||||
|
|
||||||
// Allocate objects (very fast)
|
// Allocate objects (very fast)
|
||||||
const obj1 = try pool.create();
|
const obj1 = try pool.create(allocator);
|
||||||
const obj2 = try pool.create();
|
const obj2 = try pool.create(allocator);
|
||||||
|
|
||||||
// Free returns to pool for reuse (not to backing allocator)
|
// Free returns to pool for reuse (not to backing allocator)
|
||||||
pool.destroy(obj1);
|
pool.destroy(obj1);
|
||||||
|
|
||||||
// Reuses freed slot
|
// Reuses freed slot
|
||||||
const obj3 = try pool.create(); // likely same address as obj1
|
const obj3 = try pool.create(allocator); // likely same address as obj1
|
||||||
|
|
||||||
// Reset all - batch destroy without individual frees
|
// Reset all - batch destroy without individual frees
|
||||||
_ = pool.reset(.retain_capacity);
|
_ = pool.reset(allocator, .retain_capacity);
|
||||||
```
|
```
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
```zig
|
```zig
|
||||||
// Pre-allocate slots
|
// Pre-allocate slots
|
||||||
var pool = try std.heap.MemoryPool(T).initPreheated(allocator, 100);
|
var pool = try std.heap.MemoryPool(T).initCapacity(allocator, 100);
|
||||||
|
|
||||||
// Custom alignment
|
// Custom alignment
|
||||||
var pool = std.heap.MemoryPoolAligned(T, .@"64").init(allocator);
|
var pool: std.heap.memory_pool.Aligned(T, .@"64") = .empty;
|
||||||
|
|
||||||
// Non-growable (fixed capacity)
|
// Non-growable (fixed capacity)
|
||||||
var pool = try std.heap.MemoryPoolExtra(T, .{ .growable = false }).initPreheated(allocator, 50);
|
var pool = try std.heap.memory_pool.Extra(T, .{ .growable = false }).initCapacity(allocator, 50);
|
||||||
```
|
|
||||||
|
|
||||||
### ThreadSafeAllocator
|
|
||||||
|
|
||||||
Wraps any allocator with mutex for thread safety:
|
|
||||||
|
|
||||||
```zig
|
|
||||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
var ts = std.heap.ThreadSafeAllocator{
|
|
||||||
.child_allocator = arena.allocator(),
|
|
||||||
};
|
|
||||||
const allocator = ts.allocator(); // Safe to use from multiple threads
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### StackFallbackAllocator
|
### StackFallbackAllocator
|
||||||
|
|
@ -363,18 +348,6 @@ const small = try allocator.alloc(u8, 100);
|
||||||
const large = try allocator.alloc(u8, 10000);
|
const large = try allocator.alloc(u8, 10000);
|
||||||
```
|
```
|
||||||
|
|
||||||
### raw_c_allocator
|
|
||||||
|
|
||||||
Direct malloc/free without alignment overhead. Use as `ArenaAllocator` backing when linking libc:
|
|
||||||
|
|
||||||
```zig
|
|
||||||
// More efficient than c_allocator when wrapping with ArenaAllocator
|
|
||||||
var arena = std.heap.ArenaAllocator.init(std.heap.raw_c_allocator);
|
|
||||||
defer arena.deinit();
|
|
||||||
```
|
|
||||||
|
|
||||||
Requires linking libc. Does not support custom alignment - asserts alignment <= `@alignOf(std.c.max_align_t)`.
|
|
||||||
|
|
||||||
### Wasm Allocator
|
### Wasm Allocator
|
||||||
|
|
||||||
Optimized for WebAssembly. Uses `@wasmMemoryGrow`:
|
Optimized for WebAssembly. Uses `@wasmMemoryGrow`:
|
||||||
|
|
@ -498,7 +471,7 @@ fn process(allocator: Allocator) void {
|
||||||
|
|
||||||
Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
- `std.heap.ArenaAllocator` is thread-safe and lock-free in Zig 0.16.
|
- `std.heap.ArenaAllocator.allocator()` is thread-safe when its child allocator is thread-safe. Concurrent lifecycle, reset, state, and capacity-query operations are outside that guarantee; do not treat “lock-free” as a universal public contract.
|
||||||
- `std.heap.ThreadSafe` allocator was removed.
|
- `std.heap.ThreadSafe` allocator was removed.
|
||||||
- Allocators that perform blocking synchronization or file/entropy/time work should accept/store `std.Io` at initialization rather than constructing a local backend.
|
- Allocators that perform blocking synchronization or file/entropy/time work should accept/store `std.Io` at initialization rather than constructing a local backend.
|
||||||
- Keep allocator naming by memory contract (`gpa`, `arena`, `scratch`) and expose stored `io` through a small accessor only when callsites need it.
|
- Keep allocator naming by memory contract (`gpa`, `arena`, `scratch`) and expose stored `io` through a small accessor only when callsites need it.
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@ Zig 0.16 removed the managed array hash map aliases:
|
||||||
- `std.ArrayHashMap` removed.
|
- `std.ArrayHashMap` removed.
|
||||||
- `std.AutoArrayHashMap` removed.
|
- `std.AutoArrayHashMap` removed.
|
||||||
- `std.StringArrayHashMap` removed.
|
- `std.StringArrayHashMap` removed.
|
||||||
- `std.AutoArrayHashMapUnmanaged` -> `std.array_hash_map.Auto`
|
- The unmanaged root aliases still exist but are deprecated; prefer `std.array_hash_map.Auto`, `.String`, and `.Custom`.
|
||||||
- `std.StringArrayHashMapUnmanaged` -> `std.array_hash_map.String`
|
|
||||||
- `std.ArrayHashMapUnmanaged` -> `std.array_hash_map.Custom`
|
|
||||||
|
|
||||||
Old examples below may use removed names; translate them to the `std.array_hash_map` names before using them in Zig 0.16 code.
|
All examples below use the Zig 0.16 unmanaged API: initialize with `.empty`, pass an allocator to operations that may allocate, and pass it again to `deinit`/`clearAndFree`.
|
||||||
|
|
||||||
A hash map that preserves insertion order and stores keys/values in contiguous arrays. Combines hash table lookup with array-like iteration.
|
A hash map that preserves insertion order and stores keys/values in contiguous arrays. Combines hash table lookup with array-like iteration.
|
||||||
|
|
||||||
|
|
@ -26,23 +24,22 @@ A hash map that preserves insertion order and stores keys/values in contiguous a
|
||||||
|
|
||||||
| Type | Description |
|
| Type | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `AutoArrayHashMap(K, V)` | Auto-hashing for common key types |
|
| `std.array_hash_map.Auto(K, V)` | Auto-hashing for common key types |
|
||||||
| `ArrayHashMap(K, V, Ctx, store_hash)` | Custom hash/equal context |
|
| `std.array_hash_map.Custom(K, V, Ctx, store_hash)` | Custom hash/equal context |
|
||||||
| `StringArrayHashMap(V)` | String keys |
|
| `std.array_hash_map.String(V)` | String keys |
|
||||||
| `ArrayHashMapUnmanaged(...)` | No stored allocator |
|
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
var map = std.AutoArrayHashMap(u32, []const u8).init(allocator);
|
var map: std.array_hash_map.Auto(u32, []const u8) = .empty;
|
||||||
defer map.deinit();
|
defer map.deinit(allocator);
|
||||||
|
|
||||||
// Insert
|
// Insert
|
||||||
try map.put(1, "one");
|
try map.put(allocator, 1, "one");
|
||||||
try map.put(2, "two");
|
try map.put(allocator, 2, "two");
|
||||||
try map.put(3, "three");
|
try map.put(allocator, 3, "three");
|
||||||
|
|
||||||
// Lookup
|
// Lookup
|
||||||
if (map.get(2)) |value| {
|
if (map.get(2)) |value| {
|
||||||
|
|
@ -58,9 +55,9 @@ if (map.contains(1)) {
|
||||||
## Insertion Order Preserved
|
## Insertion Order Preserved
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
try map.put(10, "ten");
|
try map.put(allocator, 10, "ten");
|
||||||
try map.put(5, "five");
|
try map.put(allocator, 5, "five");
|
||||||
try map.put(15, "fifteen");
|
try map.put(allocator, 15, "fifteen");
|
||||||
|
|
||||||
// Iteration is in insertion order: 10, 5, 15
|
// Iteration is in insertion order: 10, 5, 15
|
||||||
var it = map.iterator();
|
var it = map.iterator();
|
||||||
|
|
@ -101,13 +98,13 @@ if (map.fetchSwapRemove(key)) |kv| {
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Get existing or insert new
|
// Get existing or insert new
|
||||||
const result = try map.getOrPut(key);
|
const result = try map.getOrPut(allocator, key);
|
||||||
if (!result.found_existing) {
|
if (!result.found_existing) {
|
||||||
result.value_ptr.* = "new_value";
|
result.value_ptr.* = "new_value";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get or put with default value
|
// Get or put with default value
|
||||||
const result2 = try map.getOrPutValue(key, "default");
|
const result2 = try map.getOrPutValue(allocator, key, "default");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Index-Based Operations
|
## Index-Based Operations
|
||||||
|
|
@ -125,24 +122,24 @@ if (map.getIndex(key)) |idx| {
|
||||||
## Capacity Management
|
## Capacity Management
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
try map.ensureTotalCapacity(100);
|
try map.ensureTotalCapacity(allocator, 100);
|
||||||
try map.ensureUnusedCapacity(10);
|
try map.ensureUnusedCapacity(allocator, 10);
|
||||||
|
|
||||||
const cap = map.capacity();
|
const cap = map.capacity();
|
||||||
const len = map.count();
|
const len = map.count();
|
||||||
|
|
||||||
map.clearRetainingCapacity();
|
map.clearRetainingCapacity();
|
||||||
map.clearAndFree();
|
map.clearAndFree(allocator);
|
||||||
```
|
```
|
||||||
|
|
||||||
## String Keys
|
## String Keys
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var map = std.StringArrayHashMap(i32).init(allocator);
|
var map: std.array_hash_map.String(i32) = .empty;
|
||||||
defer map.deinit();
|
defer map.deinit(allocator);
|
||||||
|
|
||||||
try map.put("apple", 1);
|
try map.put(allocator, "apple", 1);
|
||||||
try map.put("banana", 2);
|
try map.put(allocator, "banana", 2);
|
||||||
|
|
||||||
// Keys are stored by reference, not copied
|
// Keys are stored by reference, not copied
|
||||||
// Make sure string lifetime exceeds map usage
|
// Make sure string lifetime exceeds map usage
|
||||||
|
|
@ -164,16 +161,16 @@ const CaseInsensitiveContext = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var map = std.ArrayHashMap(
|
var map: std.array_hash_map.Custom(
|
||||||
[]const u8,
|
[]const u8,
|
||||||
i32,
|
i32,
|
||||||
CaseInsensitiveContext,
|
CaseInsensitiveContext,
|
||||||
true, // store_hash for better performance
|
true, // store_hash for better performance
|
||||||
).initContext(allocator, .{});
|
) = .empty;
|
||||||
defer map.deinit();
|
defer map.deinit(allocator);
|
||||||
|
|
||||||
try map.put("Hello", 1);
|
try map.putContext(allocator, "Hello", 1, .{});
|
||||||
_ = map.get("HELLO"); // finds it!
|
_ = map.getContext("HELLO", .{}); // finds it!
|
||||||
```
|
```
|
||||||
|
|
||||||
## Complete Example: Word Counter
|
## Complete Example: Word Counter
|
||||||
|
|
@ -185,13 +182,14 @@ pub fn main() !void {
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
var counts = std.StringArrayHashMap(u32).init(gpa.allocator());
|
const allocator = gpa.allocator();
|
||||||
defer counts.deinit();
|
var counts: std.array_hash_map.String(u32) = .empty;
|
||||||
|
defer counts.deinit(allocator);
|
||||||
|
|
||||||
const words = [_][]const u8{ "apple", "banana", "apple", "cherry", "banana", "apple" };
|
const words = [_][]const u8{ "apple", "banana", "apple", "cherry", "banana", "apple" };
|
||||||
|
|
||||||
for (words) |word| {
|
for (words) |word| {
|
||||||
const result = try counts.getOrPut(word);
|
const result = try counts.getOrPut(allocator, word);
|
||||||
if (result.found_existing) {
|
if (result.found_existing) {
|
||||||
result.value_ptr.* += 1;
|
result.value_ptr.* += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -221,13 +219,13 @@ pub fn main() !void {
|
||||||
| orderedRemove | N/A | O(n) |
|
| orderedRemove | N/A | O(n) |
|
||||||
| Iteration order | Undefined | Insertion order |
|
| Iteration order | Undefined | Insertion order |
|
||||||
| Key/value arrays | No | Yes |
|
| Key/value arrays | No | Yes |
|
||||||
| Memory layout | Scattered | Contiguous |
|
| Sequential iteration storage | Not exposed as direct key/value arrays | Direct key/value arrays |
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Iteration order equals insertion order
|
- Iteration order equals insertion order
|
||||||
- `swapRemove` is O(1) but changes order
|
- `swapRemove` is O(1) but changes order
|
||||||
- `orderedRemove` preserves order but is O(n)
|
- `orderedRemove` preserves order but is O(n)
|
||||||
- Use `store_hash=true` when `eql` is expensive
|
- `store_hash=true` trades memory for avoiding some repeated hash work; benchmark it with the actual key/context workload
|
||||||
- Keys/values are stored in `MultiArrayList` (cache-friendly)
|
- Keys/values are stored in `MultiArrayList` (cache-friendly)
|
||||||
- Pointer stability only guaranteed with pre-allocated capacity
|
- Any modification invalidates iterators. Growth/rehash can invalidate key/value pointers; consult the API's pointer-locking rules rather than assuming a capacity reservation guarantees stability across all mutations.
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,19 @@ Dynamic array (vector) that grows as needed.
|
||||||
## Initialization
|
## Initialization
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// CRITICAL: Use .empty, not .{}
|
// Use the supported explicit empty initializer.
|
||||||
var list: std.ArrayList(u32) = .empty;
|
var list: std.ArrayList(u32) = .empty;
|
||||||
defer list.deinit(allocator);
|
defer list.deinit(allocator);
|
||||||
|
|
||||||
// With pre-allocated capacity
|
// With pre-allocated capacity
|
||||||
var list = try std.ArrayList(u32).initCapacity(allocator, 100);
|
var reserved = try std.ArrayList(u32).initCapacity(allocator, 100);
|
||||||
|
defer reserved.deinit(allocator);
|
||||||
|
|
||||||
// From existing slice (takes ownership)
|
// From existing slice (takes ownership)
|
||||||
var list = std.ArrayList(u32).fromOwnedSlice(existing_slice);
|
var list = std.ArrayList(u32).fromOwnedSlice(existing_slice);
|
||||||
|
|
||||||
// Fixed buffer (no allocator needed for operations)
|
// Fixed buffer: use bounded/no-allocator operations only. Passing an allocator
|
||||||
|
// to a method on an initBuffer list is illegal.
|
||||||
var buffer: [8]i32 = undefined;
|
var buffer: [8]i32 = undefined;
|
||||||
var stack = std.ArrayList(i32).initBuffer(&buffer);
|
var stack = std.ArrayList(i32).initBuffer(&buffer);
|
||||||
```
|
```
|
||||||
|
|
@ -36,7 +38,8 @@ list.appendSliceAssumeCapacity(&[_]u32{1, 2, 3});
|
||||||
// Access items
|
// Access items
|
||||||
const items = list.items; // []T slice
|
const items = list.items; // []T slice
|
||||||
const first = list.items[0];
|
const first = list.items[0];
|
||||||
const last = list.getLast(); // returns ?T
|
const last = list.getLast(); // returns T; asserts if empty
|
||||||
|
const maybe_last = list.getLastOrNull(); // returns ?T
|
||||||
const popped = list.pop(); // returns ?T, removes last
|
const popped = list.pop(); // returns ?T, removes last
|
||||||
|
|
||||||
// Insert at index
|
// Insert at index
|
||||||
|
|
@ -44,8 +47,8 @@ try list.insert(allocator, 2, value);
|
||||||
try list.insertSlice(allocator, 2, slice);
|
try list.insertSlice(allocator, 2, slice);
|
||||||
|
|
||||||
// Remove
|
// Remove
|
||||||
const removed = list.orderedRemove(index); // O(n), preserves order
|
const removed_ordered = list.orderedRemove(index); // O(n), preserves order
|
||||||
const removed = list.swapRemove(index); // O(1), doesn't preserve order
|
const removed_swapped = list.swapRemove(index); // O(1), changes order
|
||||||
```
|
```
|
||||||
|
|
||||||
## Capacity Management
|
## Capacity Management
|
||||||
|
|
@ -57,7 +60,8 @@ try list.ensureUnusedCapacity(allocator, 10);
|
||||||
// Ensure total capacity is at least N
|
// Ensure total capacity is at least N
|
||||||
try list.ensureTotalCapacity(allocator, 100);
|
try list.ensureTotalCapacity(allocator, 100);
|
||||||
|
|
||||||
// Shrink to fit
|
// Attempt to shrink capacity. If resizing runs out of memory, the list keeps
|
||||||
|
// excess capacity while still reducing its logical length.
|
||||||
list.shrinkAndFree(allocator, list.items.len);
|
list.shrinkAndFree(allocator, list.items.len);
|
||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
|
|
@ -74,6 +78,7 @@ defer allocator.free(owned);
|
||||||
|
|
||||||
// Get null-terminated slice
|
// Get null-terminated slice
|
||||||
const z_str = try list.toOwnedSliceSentinel(allocator, 0);
|
const z_str = try list.toOwnedSliceSentinel(allocator, 0);
|
||||||
|
defer allocator.free(z_str);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Iteration
|
## Iteration
|
||||||
|
|
@ -123,14 +128,18 @@ When inserting into multiple containers or when partial mutation would corrupt s
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// BAD - partial failure leaves invalid state
|
// BAD - partial failure leaves invalid state
|
||||||
fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Allocator, value: u32) !void {
|
fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMapUnmanaged(u32, usize), gpa: Allocator, value: u32) !void {
|
||||||
try list.append(gpa, value); // Can fail
|
try list.append(gpa, value); // Can fail
|
||||||
try map.put(gpa, value, list.items.len); // If this fails, list has orphan entry!
|
try map.put(gpa, value, list.items.len - 1); // If this fails, list has orphan entry!
|
||||||
}
|
}
|
||||||
|
|
||||||
// GOOD - reserve first, then mutate
|
// GOOD - reserve first, then mutate
|
||||||
fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Allocator, value: u32) !void {
|
fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMapUnmanaged(u32, usize), gpa: Allocator, value: u32) !void {
|
||||||
// Phase 1: Reserve (fallible, but no mutation)
|
if (map.contains(value)) return error.DuplicateItem;
|
||||||
|
const index = list.items.len;
|
||||||
|
|
||||||
|
// Phase 1: Reserve. Logical contents are unchanged, but capacity may grow
|
||||||
|
// and existing element pointers may be invalidated.
|
||||||
try list.ensureUnusedCapacity(gpa, 1);
|
try list.ensureUnusedCapacity(gpa, 1);
|
||||||
try map.ensureUnusedCapacity(gpa, 1);
|
try map.ensureUnusedCapacity(gpa, 1);
|
||||||
|
|
||||||
|
|
@ -138,12 +147,13 @@ fn addItem(list: *std.ArrayList(u32), map: *std.AutoHashMap(u32, usize), gpa: Al
|
||||||
|
|
||||||
// Phase 3: Mutate (infallible)
|
// Phase 3: Mutate (infallible)
|
||||||
list.appendAssumeCapacity(value);
|
list.appendAssumeCapacity(value);
|
||||||
map.getOrPutAssumeCapacity(value).value_ptr.* = list.items.len;
|
map.putAssumeCapacityNoClobber(value, index);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key methods:**
|
**Key methods:**
|
||||||
- `ensureUnusedCapacity(gpa, n)` - Reserve space for n more items (can fail, doesn't mutate)
|
- `ensureUnusedCapacity(gpa, n)` - Reserve space for n more items. It can fail
|
||||||
|
and can reallocate, but it does not append or remove logical elements.
|
||||||
- `appendAssumeCapacity(item)` - Append without allocation (cannot fail, asserts capacity)
|
- `appendAssumeCapacity(item)` - Append without allocation (cannot fail, asserts capacity)
|
||||||
- `appendSliceAssumeCapacity(items)` - Append slice without allocation
|
- `appendSliceAssumeCapacity(items)` - Append slice without allocation
|
||||||
|
|
||||||
|
|
@ -160,6 +170,8 @@ var arr = std.BoundedArray(u8, 64){};
|
||||||
// NEW
|
// NEW
|
||||||
var buffer: [64]u8 = undefined;
|
var buffer: [64]u8 = undefined;
|
||||||
var arr = std.ArrayList(u8).initBuffer(&buffer);
|
var arr = std.ArrayList(u8).initBuffer(&buffer);
|
||||||
// Note: Operations will panic if capacity exceeded
|
// Bounded operations report capacity exhaustion.
|
||||||
try arr.appendBounded(value); // returns error.OutOfMemory if full
|
try arr.appendBounded(value); // error.OutOfMemory if full
|
||||||
|
// Assume-capacity operations assert; allocator-taking operations are illegal
|
||||||
|
// for an initBuffer list.
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
# std.ascii
|
# std.ascii
|
||||||
|
|
||||||
7-bit ASCII character classification and manipulation. For Unicode handling, use `std.unicode`.
|
7-bit ASCII character classification and manipulation. Use `std.unicode` for
|
||||||
|
UTF encoding, decoding, validation, and codepoint iteration; it is not a
|
||||||
|
drop-in replacement for Unicode-aware classification, case conversion, or case
|
||||||
|
folding.
|
||||||
|
|
||||||
## Character Classification
|
## Character Classification
|
||||||
|
|
||||||
|
|
@ -16,9 +19,11 @@ ascii.isHex('F') // A-F, a-f, 0-9
|
||||||
ascii.isUpper('A') // A-Z
|
ascii.isUpper('A') // A-Z
|
||||||
ascii.isLower('a') // a-z
|
ascii.isLower('a') // a-z
|
||||||
ascii.isWhitespace(' ') // space, \t, \n, \r, \v, \f
|
ascii.isWhitespace(' ') // space, \t, \n, \r, \v, \f
|
||||||
ascii.isPrint('!') // printable (not control)
|
ascii.isPrint('!') // printable 7-bit ASCII and not control
|
||||||
ascii.isControl('\n') // control characters (0x00-0x1F, 0x7F)
|
ascii.isControl('\n') // control characters (0x00-0x1F, 0x7F)
|
||||||
ascii.isAscii(c) // c < 128
|
ascii.isAscii(c) // c < 128
|
||||||
|
ascii.isGraphical('!') // printable ASCII excluding space
|
||||||
|
ascii.isPunctuation('!') // ASCII punctuation
|
||||||
```
|
```
|
||||||
|
|
||||||
## Case Conversion
|
## Case Conversion
|
||||||
|
|
@ -29,16 +34,17 @@ ascii.toUpper('a') // 'A'
|
||||||
ascii.toLower('A') // 'a'
|
ascii.toLower('A') // 'a'
|
||||||
|
|
||||||
// Strings - to buffer
|
// Strings - to buffer
|
||||||
var buf: [100]u8 = undefined;
|
var lower_buf: [100]u8 = undefined;
|
||||||
const lower = ascii.lowerString(&buf, "HeLLo"); // "hello"
|
var upper_buf: [100]u8 = undefined;
|
||||||
const upper = ascii.upperString(&buf, "HeLLo"); // "HELLO"
|
const lower = ascii.lowerString(&lower_buf, "HeLLo"); // "hello"
|
||||||
|
const upper = ascii.upperString(&upper_buf, "HeLLo"); // "HELLO"
|
||||||
|
|
||||||
// Strings - allocating
|
// Strings - allocating
|
||||||
const lower = try ascii.allocLowerString(allocator, "HeLLo");
|
const allocated_lower = try ascii.allocLowerString(allocator, "HeLLo");
|
||||||
defer allocator.free(lower); // "hello"
|
defer allocator.free(allocated_lower); // "hello"
|
||||||
|
|
||||||
const upper = try ascii.allocUpperString(allocator, "HeLLo");
|
const allocated_upper = try ascii.allocUpperString(allocator, "HeLLo");
|
||||||
defer allocator.free(upper); // "HELLO"
|
defer allocator.free(allocated_upper); // "HELLO"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Case-Insensitive Comparison
|
## Case-Insensitive Comparison
|
||||||
|
|
@ -52,7 +58,7 @@ ascii.startsWithIgnoreCase("Hello World", "hello") // true
|
||||||
ascii.endsWithIgnoreCase("Hello World", "WORLD") // true
|
ascii.endsWithIgnoreCase("Hello World", "WORLD") // true
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
ascii.indexOfIgnoreCase("Hello World", "world") // ?usize = 6
|
ascii.findIgnoreCase("Hello World", "world") // ?usize = 6
|
||||||
|
|
||||||
// Lexicographical order
|
// Lexicographical order
|
||||||
ascii.orderIgnoreCase("abc", "ABC") // .eq
|
ascii.orderIgnoreCase("abc", "ABC") // .eq
|
||||||
|
|
@ -130,6 +136,7 @@ fn isAsciiString(s: []const u8) bool {
|
||||||
```zig
|
```zig
|
||||||
// Use ascii.lowerString to normalize keys
|
// Use ascii.lowerString to normalize keys
|
||||||
var buf: [64]u8 = undefined;
|
var buf: [64]u8 = undefined;
|
||||||
|
if (user_input.len > buf.len) return error.InputTooLong;
|
||||||
const normalized = ascii.lowerString(&buf, user_input);
|
const normalized = ascii.lowerString(&buf, user_input);
|
||||||
if (map.get(normalized)) |value| {
|
if (map.get(normalized)) |value| {
|
||||||
// found
|
// found
|
||||||
|
|
@ -140,5 +147,7 @@ if (map.get(normalized)) |value| {
|
||||||
|
|
||||||
- All functions handle bytes > 127 gracefully (return `false` for classification)
|
- All functions handle bytes > 127 gracefully (return `false` for classification)
|
||||||
- Functions use `u8` not `u7` for convenience
|
- Functions use `u8` not `u7` for convenience
|
||||||
- For Unicode text, use `std.unicode` instead
|
- ASCII case/comparison helpers are not Unicode-aware; `std.unicode` covers UTF
|
||||||
|
encoding/decoding rather than locale-aware casing
|
||||||
- `lowerString`/`upperString` assert output buffer is large enough
|
- `lowerString`/`upperString` assert output buffer is large enough
|
||||||
|
- Normalize stored keys and lookup keys with the same convention
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,9 @@ Lock-free atomics do not need `std.Io`. Blocking synchronization should use `std
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
std.atomic.Value(T) // Atomic wrapper for T (integers, enums, floats, bools, pointers)
|
std.atomic.Value(T) // Atomic wrapper for T (integers, enums, floats, bools, pointers)
|
||||||
|
std.atomic.Mutex // Non-blocking/spinning atomic mutex
|
||||||
std.atomic.spinLoopHint() // CPU hint for spin-wait loops
|
std.atomic.spinLoopHint() // CPU hint for spin-wait loops
|
||||||
std.atomic.cache_line // CPU cache line size (comptime constant)
|
std.atomic.cache_line // Target-based cache-line estimate (comptime constant)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Atomic Value Wrapper
|
## Atomic Value Wrapper
|
||||||
|
|
@ -179,16 +180,16 @@ fn spinWait(flag: *std.atomic.Value(bool)) void {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Architecture-specific behavior:
|
Representative architecture-specific behavior (the implementation contains additional target/feature cases):
|
||||||
- **x86/x86_64**: `pause` instruction
|
- **x86/x86_64**: `pause` instruction
|
||||||
- **AArch64**: `isb` instruction
|
- **AArch64**: `isb` instruction
|
||||||
- **ARM**: `yield` instruction (v6k+)
|
- **ARM**: feature-dependent yield/spin hint
|
||||||
- **RISC-V**: `pause` (Zihintpause extension)
|
- **RISC-V**: `pause` (Zihintpause extension)
|
||||||
- **Others**: No-op
|
- **Some unsupported targets**: No-op
|
||||||
|
|
||||||
## Cache Line Size
|
## Cache Line Size
|
||||||
|
|
||||||
`cache_line` is the CPU cache line size, used to prevent false sharing:
|
`cache_line` is a target-based estimate of cache-line size, useful when reducing false sharing. It is not a runtime query of the current CPU:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const cache_line = std.atomic.cache_line; // 64, 128, etc.
|
const cache_line = std.atomic.cache_line; // 64, 128, etc.
|
||||||
|
|
@ -205,7 +206,7 @@ const ThreadCounters = struct {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Typical values by architecture:
|
Representative values selected by the stdlib's target table (not a complete hardware guarantee):
|
||||||
- x86_64, AArch64: 128 bytes (big cores)
|
- x86_64, AArch64: 128 bytes (big cores)
|
||||||
- ARM, MIPS: 32 bytes
|
- ARM, MIPS: 32 bytes
|
||||||
- Most others: 64 bytes
|
- Most others: 64 bytes
|
||||||
|
|
@ -300,18 +301,20 @@ fn Stack(comptime T: type) type {
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var initialized = std.atomic.Value(bool).init(false);
|
var initialized = std.atomic.Value(bool).init(false);
|
||||||
var init_mutex: std.Thread.Mutex = .{};
|
var init_mutex: std.Io.Mutex = .init;
|
||||||
var global_resource: ?*Resource = null;
|
var global_resource: ?*Resource = null;
|
||||||
|
|
||||||
fn getResource() *Resource {
|
fn getResource(io: std.Io) *Resource {
|
||||||
// Fast path: already initialized
|
// Fast path: already initialized
|
||||||
if (initialized.load(.acquire)) {
|
if (initialized.load(.acquire)) {
|
||||||
return global_resource.?;
|
return global_resource.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slow path: initialize with lock
|
// Slow path: initialize with lock
|
||||||
init_mutex.lock();
|
// This variant deliberately makes initialization uncancelable. Use
|
||||||
defer init_mutex.unlock();
|
// `try init_mutex.lock(io)` in a cancelable function instead.
|
||||||
|
init_mutex.lockUncancelable(io);
|
||||||
|
defer init_mutex.unlock(io);
|
||||||
|
|
||||||
if (!initialized.load(.acquire)) {
|
if (!initialized.load(.acquire)) {
|
||||||
global_resource = initializeResource();
|
global_resource = initializeResource();
|
||||||
|
|
@ -412,5 +415,5 @@ const Barrier = struct {
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- **[std.Thread](std-thread.md)** - Higher-level synchronization (Mutex, RwLock, Condition, Semaphore)
|
- **[std.Io synchronization](std-io.md)** - Blocking `Mutex`, `RwLock`, `Condition`, `Semaphore`, `Event`, and futex methods
|
||||||
- **[std.Thread.Futex](std-thread.md)** - OS-level blocking primitives
|
- **[std.Thread](std-thread.md)** - Thread creation, joining, and thread-local facilities
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ For examples that write encoded output to files/stdout, use `std.Io.Writer` and
|
||||||
|
|
||||||
| Codec | Use Case |
|
| Codec | Use Case |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| `standard` | Standard Base64 with `=` padding (email, MIME) |
|
| `standard` | Standard RFC 4648 Base64 with `=` padding; MIME framing and line wrapping are separate |
|
||||||
| `standard_no_pad` | Standard Base64 without padding |
|
| `standard_no_pad` | Standard Base64 without padding |
|
||||||
| `url_safe` | URL-safe Base64 with `=` padding |
|
| `url_safe` | URL-safe Base64 with `=` padding |
|
||||||
| `url_safe_no_pad` | URL-safe Base64 without padding (JWT, URLs) |
|
| `url_safe_no_pad` | URL-safe Base64 without padding (JWT, URLs) |
|
||||||
|
|
@ -63,7 +63,7 @@ const encoded = "SGVs bG8s\nIFdv cmxk IQ=="; // with spaces and newlines
|
||||||
const decoder = base64.standard.decoderWithIgnore(" \n");
|
const decoder = base64.standard.decoderWithIgnore(" \n");
|
||||||
|
|
||||||
var buf: [100]u8 = undefined;
|
var buf: [100]u8 = undefined;
|
||||||
const max_size = try decoder.calcSizeUpperBound(encoded.len);
|
const max_size = decoder.calcSizeUpperBound(encoded.len);
|
||||||
const decoded_len = try decoder.decode(buf[0..max_size], encoded);
|
const decoded_len = try decoder.decode(buf[0..max_size], encoded);
|
||||||
const decoded = buf[0..decoded_len];
|
const decoded = buf[0..decoded_len];
|
||||||
// "Hello, World!"
|
// "Hello, World!"
|
||||||
|
|
@ -71,6 +71,8 @@ const decoded = buf[0..decoded_len];
|
||||||
|
|
||||||
## Streaming Encoding
|
## Streaming Encoding
|
||||||
|
|
||||||
|
This fragment assumes a caller-provided `io: std.Io` and `data: []const u8`:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var writer = std.Io.File.stdout().writer(io, &buf);
|
var writer = std.Io.File.stdout().writer(io, &buf);
|
||||||
|
|
@ -110,6 +112,7 @@ base64.standard.Decoder.decode(dest, source) catch |err| switch (err) {
|
||||||
### Encode binary data for JSON/URLs
|
### Encode binary data for JSON/URLs
|
||||||
```zig
|
```zig
|
||||||
fn encodeForUrl(data: []const u8, buf: []u8) []const u8 {
|
fn encodeForUrl(data: []const u8, buf: []u8) []const u8 {
|
||||||
|
std.debug.assert(buf.len >= std.base64.url_safe_no_pad.Encoder.calcSize(data.len));
|
||||||
return std.base64.url_safe_no_pad.Encoder.encode(buf, data);
|
return std.base64.url_safe_no_pad.Encoder.encode(buf, data);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -126,11 +129,12 @@ fn decodeJwtPayload(payload: []const u8, buf: []u8) ![]u8 {
|
||||||
|
|
||||||
### Handle multi-line Base64 (PEM format)
|
### Handle multi-line Base64 (PEM format)
|
||||||
```zig
|
```zig
|
||||||
fn decodePem(pem_data: []const u8, buf: []u8) ![]u8 {
|
fn decodePemBody(base64_body: []const u8, buf: []u8) ![]u8 {
|
||||||
// Skip header/footer, decode with newline ignoring
|
// The caller must extract and validate the PEM header/footer first. This
|
||||||
|
// helper only decodes the Base64 body while ignoring line breaks.
|
||||||
const decoder = std.base64.standard.decoderWithIgnore("\n\r");
|
const decoder = std.base64.standard.decoderWithIgnore("\n\r");
|
||||||
const max = try decoder.calcSizeUpperBound(pem_data.len);
|
const max = decoder.calcSizeUpperBound(base64_body.len);
|
||||||
const len = try decoder.decode(buf[0..max], pem_data);
|
const len = try decoder.decode(buf[0..max], base64_body);
|
||||||
return buf[0..len];
|
return buf[0..len];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -141,4 +145,4 @@ fn decodePem(pem_data: []const u8, buf: []u8) ![]u8 {
|
||||||
- URL-safe uses `-` and `_` which are safe in URLs
|
- URL-safe uses `-` and `_` which are safe in URLs
|
||||||
- Padding (`=`) makes length divisible by 4
|
- Padding (`=`) makes length divisible by 4
|
||||||
- `calcSizeForSlice` gives exact size; `calcSizeUpperBound` gives max (ignores padding)
|
- `calcSizeForSlice` gives exact size; `calcSizeUpperBound` gives max (ignores padding)
|
||||||
- All codecs use little-endian byte order internally
|
- Base64 operates on byte slices; integer endianness is not part of its public contract
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ Densely stored sets of integers with efficient set operations (union, intersecti
|
||||||
|
|
||||||
| Type | Size | Allocation |
|
| Type | Size | Allocation |
|
||||||
|------|------|------------|
|
|------|------|------------|
|
||||||
| `IntegerBitSet(N)` | Compile-time, N <= 128 | None (single integer) |
|
| `IntegerBitSet(N)` | Compile-time, backed by one integer of N bits | None (single integer) |
|
||||||
| `ArrayBitSet(usize, N)` | Compile-time, any N | None (array) |
|
| `ArrayBitSet(usize, N)` | Compile-time, any N | None (array) |
|
||||||
| `StaticBitSet(N)` | Compile-time | Auto-selects Integer or Array |
|
| `StaticBitSet(N)` | Compile-time | Uses Integer when N <= `@bitSizeOf(usize)`, otherwise Array |
|
||||||
| `DynamicBitSet` | Runtime | Allocator (managed) |
|
| `DynamicBitSet` | Runtime | Allocator (managed) |
|
||||||
| `DynamicBitSetUnmanaged` | Runtime | Allocator (unmanaged) |
|
| `DynamicBitSetUnmanaged` | Runtime | Allocator (unmanaged) |
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ const std = @import("std");
|
||||||
// StaticBitSet auto-selects best implementation
|
// StaticBitSet auto-selects best implementation
|
||||||
const Flags = std.StaticBitSet(64);
|
const Flags = std.StaticBitSet(64);
|
||||||
|
|
||||||
var flags = Flags.initEmpty();
|
var flags: Flags = .empty;
|
||||||
flags.set(5);
|
flags.set(5);
|
||||||
flags.set(10);
|
flags.set(10);
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ bits.set(100);
|
||||||
|
|
||||||
// Resize dynamically
|
// Resize dynamically
|
||||||
try bits.resize(2000, false); // false = new bits are 0
|
try bits.resize(2000, false); // false = new bits are 0
|
||||||
try bits.resize(2000, true); // true = new bits are 1
|
try bits.resize(2500, true); // true = newly added bits are 1
|
||||||
|
|
||||||
// Clone
|
// Clone
|
||||||
var copy = try bits.clone(allocator);
|
var copy = try bits.clone(allocator);
|
||||||
|
|
@ -62,8 +62,8 @@ defer copy.deinit();
|
||||||
## Set Operations
|
## Set Operations
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var a = Flags.initEmpty();
|
var a: Flags = .empty;
|
||||||
var b = Flags.initEmpty();
|
var b: Flags = .empty;
|
||||||
a.set(1); a.set(2);
|
a.set(1); a.set(2);
|
||||||
b.set(2); b.set(3);
|
b.set(2); b.set(3);
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ if (a.supersetOf(b)) {
|
||||||
## Iteration
|
## Iteration
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var flags = Flags.initEmpty();
|
var flags: Flags = .empty;
|
||||||
flags.set(1); flags.set(5); flags.set(10);
|
flags.set(1); flags.set(5); flags.set(10);
|
||||||
|
|
||||||
// Iterate set bits (ascending order by default)
|
// Iterate set bits (ascending order by default)
|
||||||
|
|
@ -135,7 +135,7 @@ if (flags.findLastSet()) |index| {
|
||||||
// index of highest set bit
|
// index of highest set bit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find and toggle (atomic-like)
|
// Find the first set bit and remove it from this set (not an atomic operation)
|
||||||
if (flags.toggleFirstSet()) |index| {
|
if (flags.toggleFirstSet()) |index| {
|
||||||
// returns index and unsets the bit
|
// returns index and unsets the bit
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +171,7 @@ const Permission = enum(u8) {
|
||||||
admin = 4,
|
admin = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Permissions = std.StaticBitSet(8);
|
const Permissions = std.StaticBitSet(@typeInfo(Permission).@"enum".fields.len);
|
||||||
|
|
||||||
fn hasPermission(perms: Permissions, p: Permission) bool {
|
fn hasPermission(perms: Permissions, p: Permission) bool {
|
||||||
return perms.isSet(@intFromEnum(p));
|
return perms.isSet(@intFromEnum(p));
|
||||||
|
|
@ -186,14 +186,14 @@ fn revoke(perms: *Permissions, p: Permission) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
var user_perms = Permissions.initEmpty();
|
var user_perms: Permissions = .empty;
|
||||||
grant(&user_perms, .read);
|
grant(&user_perms, .read);
|
||||||
grant(&user_perms, .write);
|
grant(&user_perms, .write);
|
||||||
|
|
||||||
var admin_perms = Permissions.initFull();
|
const admin_perms: Permissions = .full;
|
||||||
|
|
||||||
// Check if user has all admin permissions
|
// Check if user has all admin permissions
|
||||||
if (user_perms.subsetOf(admin_perms)) {
|
if (admin_perms.subsetOf(user_perms)) {
|
||||||
// user can do everything admin can (not in this case)
|
// user can do everything admin can (not in this case)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,6 +206,6 @@ pub fn main() void {
|
||||||
|
|
||||||
- `StaticBitSet` is zero-allocation, copyable by value
|
- `StaticBitSet` is zero-allocation, copyable by value
|
||||||
- `DynamicBitSet` requires allocation, call `deinit()`
|
- `DynamicBitSet` requires allocation, call `deinit()`
|
||||||
- `initFull()` creates set with all bits set
|
- `.full` creates a static set with all bits set; dynamic sets use their initialization functions
|
||||||
- Iteration order is index order, not insertion order
|
- Iteration order is index order, not insertion order
|
||||||
- Use `std.enums.EnumSet` for enum-based bit flags
|
- Use `std.enums.EnumSet` for enum-based bit flags
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ const std = @import("std");
|
||||||
var map = std.BufMap.init(allocator);
|
var map = std.BufMap.init(allocator);
|
||||||
defer map.deinit(); // frees all stored strings
|
defer map.deinit(); // frees all stored strings
|
||||||
|
|
||||||
// Put (copies both key and value)
|
// Put copies a new key and value. Replacing an existing key retains the stored
|
||||||
|
// key allocation and copies only the new value.
|
||||||
try map.put("HOME", "/Users/alice");
|
try map.put("HOME", "/Users/alice");
|
||||||
try map.put("PATH", "/usr/bin");
|
try map.put("PATH", "/usr/bin");
|
||||||
|
|
||||||
|
|
@ -26,10 +27,8 @@ if (map.get("HOME")) |home| {
|
||||||
std.debug.print("home: {s}\n", .{home});
|
std.debug.print("home: {s}\n", .{home});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get pointer (invalidated on resize)
|
// Replace through the public ownership-aware operation.
|
||||||
if (map.getPtr("PATH")) |path_ptr| {
|
try map.put("PATH", "/new/path");
|
||||||
path_ptr.* = try map.copy("/new/path"); // update in place
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove (frees both key and value)
|
// Remove (frees both key and value)
|
||||||
map.remove("PATH");
|
map.remove("PATH");
|
||||||
|
|
@ -44,8 +43,12 @@ const n = map.count();
|
||||||
// putMove takes ownership instead of copying
|
// putMove takes ownership instead of copying
|
||||||
const key = try allocator.dupe(u8, "MY_KEY");
|
const key = try allocator.dupe(u8, "MY_KEY");
|
||||||
const value = try allocator.dupe(u8, "my_value");
|
const value = try allocator.dupe(u8, "my_value");
|
||||||
try map.putMove(key, value);
|
map.putMove(key, value) catch |err| {
|
||||||
// Don't free key/value - map owns them now
|
allocator.free(key);
|
||||||
|
allocator.free(value);
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
// On success, the map owns both buffers.
|
||||||
```
|
```
|
||||||
|
|
||||||
## BufMap: Iteration
|
## BufMap: Iteration
|
||||||
|
|
@ -169,8 +172,13 @@ pub fn main() !void {
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- All strings are copied on insert/put, freed on remove/deinit
|
- A new insertion copies both strings. Replacing an existing key retains its
|
||||||
- Use `putMove` to transfer ownership instead of copying
|
stored key and replaces the owned value.
|
||||||
- `get()` returns borrowed slice - don't store long-term
|
- `putMove` transfers ownership only on success; on error the caller retains it.
|
||||||
- Iteration order is not insertion order (hash map)
|
- A slice from `get()` is invalidated by replacing/removing its key or
|
||||||
|
deinitializing the map.
|
||||||
|
- A pointer from `getPtr()` is invalidated by resize, removal of that entry, or
|
||||||
|
deinitialization. Replacing the value updates the existing slot.
|
||||||
|
- Iteration order is arbitrary; do not rely on insertion order, and do not
|
||||||
|
modify a `BufMap` or `BufSet` while an iterator is live.
|
||||||
- For non-owning string maps, use `std.StringHashMap`
|
- For non-owning string maps, use `std.StringHashMap`
|
||||||
|
|
|
||||||
|
|
@ -457,7 +457,7 @@ const zlib = b.dependency("zlib", .{
|
||||||
exe.root_module.addImport("zlib", zlib.module("zlib"));
|
exe.root_module.addImport("zlib", zlib.module("zlib"));
|
||||||
|
|
||||||
// Get artifact from dependency
|
// Get artifact from dependency
|
||||||
exe.linkLibrary(zlib.artifact("z"));
|
exe.root_module.linkLibrary(zlib.artifact("z"));
|
||||||
|
|
||||||
// Get path from dependency
|
// Get path from dependency
|
||||||
const include_path = zlib.path("include");
|
const include_path = zlib.path("include");
|
||||||
|
|
@ -495,8 +495,8 @@ const dawn_dep = switch (target.result.os.tag) {
|
||||||
|
|
||||||
if (dawn_dep) |dep| {
|
if (dawn_dep) |dep| {
|
||||||
// Dependency is available, use normally
|
// Dependency is available, use normally
|
||||||
exe.addLibraryPath(dep.path("lib"));
|
exe.root_module.addLibraryPath(dep.path("lib"));
|
||||||
exe.linkSystemLibrary("dawn");
|
exe.root_module.linkSystemLibrary("dawn", .{});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -542,7 +542,7 @@ run_step.dependOn(&run_cmd.step);
|
||||||
const cmd = b.addSystemCommand(&.{ "git", "describe", "--tags" });
|
const cmd = b.addSystemCommand(&.{ "git", "describe", "--tags" });
|
||||||
|
|
||||||
// Capture output
|
// Capture output
|
||||||
const version = cmd.captureStdOut();
|
const version = cmd.captureStdOut(.{});
|
||||||
|
|
||||||
// Use output as file
|
// Use output as file
|
||||||
const version_file = b.addInstallFile(version, "version.txt");
|
const version_file = b.addInstallFile(version, "version.txt");
|
||||||
|
|
@ -705,7 +705,7 @@ const config_h = b.addConfigHeader(.{
|
||||||
.VERSION_STRING = "1.0.0",
|
.VERSION_STRING = "1.0.0",
|
||||||
});
|
});
|
||||||
|
|
||||||
exe.addConfigHeader(config_h);
|
exe.root_module.addConfigHeader(config_h);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Code Generation with Zig Tool
|
### Code Generation with Zig Tool
|
||||||
|
|
@ -729,9 +729,8 @@ exe.root_module.addAnonymousImport("schema", .{
|
||||||
|
|
||||||
## C/C++ Integration
|
## C/C++ Integration
|
||||||
|
|
||||||
> **Note:** Compile-level methods like `exe.addCSourceFiles()`, `exe.linkSystemLibrary()`,
|
> **Note:** Zig 0.16 configures compilation and linking on the artifact's `root_module`.
|
||||||
> `exe.addIncludePath()`, `exe.linkLibC()` are **deprecated** (to be removed after 0.15.0).
|
> Older Compile-level methods are no longer the API shown here.
|
||||||
> Use `exe.root_module.*` equivalents shown below.
|
|
||||||
|
|
||||||
### Adding C Sources
|
### Adding C Sources
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -762,11 +761,11 @@ exe.root_module.linkSystemLibrary("pthread", .{});
|
||||||
exe.root_module.linkSystemLibrary("ssl", .{});
|
exe.root_module.linkSystemLibrary("ssl", .{});
|
||||||
|
|
||||||
// Static library file
|
// Static library file
|
||||||
exe.addObjectFile(b.path("lib/libfoo.a"));
|
exe.root_module.addObjectFile(b.path("lib/libfoo.a"));
|
||||||
|
|
||||||
// Library search path
|
// Library search path
|
||||||
exe.addLibraryPath(b.path("lib"));
|
exe.root_module.addLibraryPath(b.path("lib"));
|
||||||
exe.addRPath(b.path("lib"));
|
exe.root_module.addRPath(b.path("lib"));
|
||||||
|
|
||||||
// Link libc (set via createModule options or directly)
|
// Link libc (set via createModule options or directly)
|
||||||
exe.root_module.link_libc = true;
|
exe.root_module.link_libc = true;
|
||||||
|
|
@ -835,7 +834,7 @@ const cwd_path: std.Build.LazyPath = .{ .cwd_relative = "/absolute/path" };
|
||||||
exe.root_module.root_source_file = b.path("src/main.zig");
|
exe.root_module.root_source_file = b.path("src/main.zig");
|
||||||
|
|
||||||
// As include path
|
// As include path
|
||||||
exe.addIncludePath(dep.path("include"));
|
exe.root_module.addIncludePath(dep.path("include"));
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
b.installFile(generated_file, "share/output.txt");
|
b.installFile(generated_file, "share/output.txt");
|
||||||
|
|
@ -849,16 +848,20 @@ LazyPath is central to how data flows between build steps. Understanding its var
|
||||||
```zig
|
```zig
|
||||||
const LazyPath = union(enum) {
|
const LazyPath = union(enum) {
|
||||||
// Path relative to build root (most common)
|
// Path relative to build root (most common)
|
||||||
src_path: struct { root: ?*Build, sub_path: []const u8 },
|
src_path: struct { owner: *Build, sub_path: []const u8 },
|
||||||
|
|
||||||
// Output from a build step (e.g., compiled binary, generated file)
|
// Output from a build step (e.g., compiled binary, generated file)
|
||||||
generated: struct { file: *GeneratedFile, sub_path: ?[]const u8 },
|
generated: struct {
|
||||||
|
file: *const GeneratedFile,
|
||||||
|
up: usize = 0,
|
||||||
|
sub_path: []const u8 = "",
|
||||||
|
},
|
||||||
|
|
||||||
// Absolute or CWD-relative path (use sparingly)
|
// Absolute or CWD-relative path (use sparingly)
|
||||||
cwd_relative: []const u8,
|
cwd_relative: []const u8,
|
||||||
|
|
||||||
// Path inside a dependency package
|
// Path inside a dependency package
|
||||||
dependency: struct { dep: *Dependency, sub_path: []const u8 },
|
dependency: struct { dependency: *Dependency, sub_path: []const u8 },
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -902,10 +905,12 @@ const base = b.path("src/modules/parser");
|
||||||
const parent = base.dirname(); // "src/modules"
|
const parent = base.dirname(); // "src/modules"
|
||||||
|
|
||||||
// Concatenate subpath
|
// Concatenate subpath
|
||||||
const file = base.join("lexer.zig"); // "src/modules/parser/lexer.zig"
|
const file = try base.join(b.allocator, "lexer.zig");
|
||||||
|
// Convenience form with the build allocator (panics on OOM):
|
||||||
|
const same_file = base.path(b, "lexer.zig");
|
||||||
|
|
||||||
// Chain operations
|
// Chain operations
|
||||||
const sibling = base.dirname().join("utils/helpers.zig"); // "src/modules/utils/helpers.zig"
|
const sibling = try base.dirname().join(b.allocator, "utils/helpers.zig");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build Allocation
|
## Build Allocation
|
||||||
|
|
@ -1123,13 +1128,5 @@ fmt_step.dependOn(&fmt.step);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clean Step
|
### Clean Step
|
||||||
```zig
|
|
||||||
const clean_step = b.step("clean", "Clean build artifacts");
|
|
||||||
|
|
||||||
clean_step.dependOn(&b.addRemoveDirTree(b.path("zig-out")).step);
|
Zig 0.16 does not provide `Build.addRemoveDirTree`. Prefer keeping generated outputs in normal build/install locations and clean them outside the running build graph. If a project adds an explicit platform-specific removal command, treat it as an external command with its own portability and concurrent-build hazards; do not delete the active cache from a build step.
|
||||||
|
|
||||||
// Note: zig-cache deletion may fail on Windows while build is running
|
|
||||||
if (@import("builtin").os.tag != .windows) {
|
|
||||||
clean_step.dependOn(&b.addRemoveDirTree(b.path(".zig-cache")).step);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,13 @@ Use `std.c` when:
|
||||||
Prefer higher-level alternatives when available:
|
Prefer higher-level alternatives when available:
|
||||||
```zig
|
```zig
|
||||||
// High-level (recommended)
|
// High-level (recommended)
|
||||||
const file = try std.fs.cwd().openFile("data.txt", .{});
|
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
|
||||||
|
|
||||||
// POSIX-level
|
// Checked OS-level wrappers live under std.posix on supported targets. Their
|
||||||
const fd = try std.posix.open("data.txt", .{}, 0);
|
// signatures and flag types are target-aware and report Zig errors.
|
||||||
|
|
||||||
// C-level (direct libc, lowest level)
|
// C-level std.c bindings are raw libc calls: check the return code and errno.
|
||||||
const fd = std.c.open("data.txt", .{}, 0);
|
const fd = std.c.open("data.txt", flags, mode);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fundamental Types
|
## Fundamental Types
|
||||||
|
|
@ -94,21 +94,11 @@ c.pid_t // Process ID (platform-specific)
|
||||||
```zig
|
```zig
|
||||||
const c = std.c;
|
const c = std.c;
|
||||||
|
|
||||||
// Platform-specific stat structure
|
// Stat is selected for the compilation target. Its field names and layout are
|
||||||
|
// not a portable record; inspect `c.Stat` for the selected target before direct
|
||||||
|
// field access, or prefer a higher-level std.Io API.
|
||||||
const stat: c.Stat = undefined;
|
const stat: c.Stat = undefined;
|
||||||
// Fields (vary by platform):
|
_ = stat;
|
||||||
stat.dev // Device ID
|
|
||||||
stat.ino // Inode number
|
|
||||||
stat.mode // File mode
|
|
||||||
stat.nlink // Number of hard links
|
|
||||||
stat.uid // Owner UID
|
|
||||||
stat.gid // Owner GID
|
|
||||||
stat.size // File size in bytes
|
|
||||||
stat.atim // Last access time (timespec)
|
|
||||||
stat.mtim // Last modification time
|
|
||||||
stat.ctim // Last status change time
|
|
||||||
stat.blksize // Preferred block size
|
|
||||||
stat.blocks // Number of 512-byte blocks
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### I/O Vectors
|
### I/O Vectors
|
||||||
|
|
@ -273,10 +263,10 @@ c.sockaddr // Generic socket address
|
||||||
.family // Address family (sa_family_t)
|
.family // Address family (sa_family_t)
|
||||||
.data // Address data
|
.data // Address data
|
||||||
|
|
||||||
c.sockaddr_in // IPv4 address (from std.posix)
|
c.sockaddr.in // IPv4 address, when exported for the selected target
|
||||||
c.sockaddr_in6 // IPv6 address
|
c.sockaddr.in6 // IPv6 address, when exported for the selected target
|
||||||
c.sockaddr_un // Unix domain socket
|
c.sockaddr.un // Unix domain socket, on applicable targets
|
||||||
c.sockaddr_storage // Large enough for any address
|
c.sockaddr.storage // Generic storage, on applicable targets
|
||||||
|
|
||||||
c.socklen_t // Socket address length type
|
c.socklen_t // Socket address length type
|
||||||
c.sa_family_t // Address family type
|
c.sa_family_t // Address family type
|
||||||
|
|
@ -307,10 +297,10 @@ c.SOCK // Socket types
|
||||||
.CLOEXEC // Set close-on-exec
|
.CLOEXEC // Set close-on-exec
|
||||||
.NONBLOCK // Non-blocking
|
.NONBLOCK // Non-blocking
|
||||||
|
|
||||||
c.SOL // Socket level for options
|
c.SOL // Target-specific socket option levels
|
||||||
.SOCKET // Socket-level options
|
.SOCKET // Widely available socket-level option value
|
||||||
.IP, .IPV6 // IP-level options
|
// Other members vary by target. Protocol numbers such as IP/TCP/UDP are
|
||||||
.TCP, .UDP // Protocol-level options
|
// not a portable generic `SOL` member set.
|
||||||
|
|
||||||
c.SO // Socket options (SOL_SOCKET level)
|
c.SO // Socket options (SOL_SOCKET level)
|
||||||
.REUSEADDR, .REUSEPORT
|
.REUSEADDR, .REUSEPORT
|
||||||
|
|
@ -455,8 +445,7 @@ c.MAP // mmap flags
|
||||||
.SHARED // Share changes
|
.SHARED // Share changes
|
||||||
.PRIVATE // Private copy-on-write
|
.PRIVATE // Private copy-on-write
|
||||||
.FIXED // Use exact address
|
.FIXED // Use exact address
|
||||||
.ANONYMOUS // No file backing (Linux/BSD)
|
.ANONYMOUS // No file backing on targets that export this spelling
|
||||||
.ANON // Alias for ANONYMOUS
|
|
||||||
.NORESERVE // Don't reserve swap
|
.NORESERVE // Don't reserve swap
|
||||||
.STACK // Stack mapping
|
.STACK // Stack mapping
|
||||||
// Platform-specific flags
|
// Platform-specific flags
|
||||||
|
|
@ -714,6 +703,8 @@ c.port_event // Port event structure
|
||||||
|
|
||||||
## Libc Function Bindings
|
## Libc Function Bindings
|
||||||
|
|
||||||
|
`std.c` declarations are selected by target OS, architecture, libc, and sometimes libc version. The names below are an orientation guide, not a universally callable inventory: verify that each declaration is non-`void` for the selected target and link libc when required.
|
||||||
|
|
||||||
### File Operations
|
### File Operations
|
||||||
```zig
|
```zig
|
||||||
c.close // Close file descriptor
|
c.close // Close file descriptor
|
||||||
|
|
@ -792,49 +783,47 @@ c.getcontext // Get current context (some platforms)
|
||||||
|
|
||||||
### darwin (macOS/iOS)
|
### darwin (macOS/iOS)
|
||||||
```zig
|
```zig
|
||||||
const darwin = std.c.darwin; // Internal, re-exported via std.c
|
const c = std.c; // Darwin declarations below are public direct re-exports.
|
||||||
|
|
||||||
// Mach types and functions
|
// Mach types and functions
|
||||||
darwin.mach_port_t
|
c.mach_port_t
|
||||||
darwin.mach_task_self()
|
c.mach_task_self()
|
||||||
darwin.mach_msg()
|
c.mach_msg()
|
||||||
darwin.mach_host_self()
|
c.mach_host_self()
|
||||||
darwin.mach_timebase_info()
|
c.mach_timebase_info()
|
||||||
darwin.mach_absolute_time()
|
c.mach_absolute_time()
|
||||||
|
|
||||||
// Exception handling
|
// Exception handling
|
||||||
darwin.EXC, darwin.EXCEPTION
|
c.EXC, c.EXCEPTION
|
||||||
darwin.task_set_exception_ports()
|
c.task_set_exception_ports()
|
||||||
darwin.task_get_exception_ports()
|
c.task_get_exception_ports()
|
||||||
|
|
||||||
// Thread state
|
// Thread state
|
||||||
darwin.thread_state
|
c.thread_state
|
||||||
darwin.thread_get_state()
|
c.thread_get_state()
|
||||||
darwin.thread_set_state()
|
c.thread_set_state()
|
||||||
|
|
||||||
// VM operations
|
// VM operations
|
||||||
darwin.mach_vm_read()
|
c.mach_vm_read()
|
||||||
darwin.mach_vm_write()
|
c.mach_vm_write()
|
||||||
darwin.mach_vm_protect()
|
c.mach_vm_protect()
|
||||||
darwin.mach_vm_region()
|
c.mach_vm_region()
|
||||||
|
|
||||||
// Dispatch/GCD semaphores
|
// Public Dispatch/GCD aliases (only names exported by this namespace)
|
||||||
darwin.dispatch_semaphore_create()
|
c.dispatch
|
||||||
darwin.dispatch_semaphore_wait()
|
|
||||||
darwin.dispatch_semaphore_signal()
|
|
||||||
|
|
||||||
// Unfair locks
|
// Unfair locks
|
||||||
darwin.os_unfair_lock
|
c.os_unfair_lock
|
||||||
darwin.os_unfair_lock_lock()
|
c.os_unfair_lock_lock()
|
||||||
darwin.os_unfair_lock_unlock()
|
c.os_unfair_lock_unlock()
|
||||||
|
|
||||||
// Process spawning
|
// Process spawning
|
||||||
darwin.posix_spawn()
|
c.posix_spawn()
|
||||||
darwin.posix_spawn_file_actions_*
|
c.posix_spawn_file_actions_*
|
||||||
|
|
||||||
// File copy
|
// File copy
|
||||||
darwin.fcopyfile()
|
c.fcopyfile()
|
||||||
darwin.COPYFILE
|
c.COPYFILE
|
||||||
```
|
```
|
||||||
|
|
||||||
### freebsd
|
### freebsd
|
||||||
|
|
@ -944,7 +933,7 @@ extern "c" fn c_function(fd: std.c.fd_t, buf: [*]u8, len: std.c.size_t) std.c.ss
|
||||||
pub fn wrapper(fd: std.posix.fd_t, buf: []u8) !usize {
|
pub fn wrapper(fd: std.posix.fd_t, buf: []u8) !usize {
|
||||||
const result = c_function(fd, buf.ptr, buf.len);
|
const result = c_function(fd, buf.ptr, buf.len);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
const err = std.posix.errno(std.c._errno().*);
|
const err: std.c.E = @enumFromInt(std.c._errno().*);
|
||||||
return std.posix.unexpectedErrno(err);
|
return std.posix.unexpectedErrno(err);
|
||||||
}
|
}
|
||||||
return @intCast(result);
|
return @intCast(result);
|
||||||
|
|
@ -963,7 +952,7 @@ fn platformSpecificCall() void {
|
||||||
},
|
},
|
||||||
.macos, .ios => {
|
.macos, .ios => {
|
||||||
// Darwin uses different types
|
// Darwin uses different types
|
||||||
const port = std.c.darwin.mach_port_t;
|
const port = std.c.mach_port_t;
|
||||||
},
|
},
|
||||||
.windows => {
|
.windows => {
|
||||||
// Windows uses HANDLE
|
// Windows uses HANDLE
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Compression and decompression algorithms. Zig 0.16 adds Deflate compression, sim
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
Old examples below may use removed `std.io.GenericReader` or `fixedBufferStream` patterns. Translate them to `std.Io.Reader` / `std.Io.Writer` before using them in Zig 0.16 code.
|
The examples below use Zig 0.16's concrete `std.Io.Reader` / `std.Io.Writer` interfaces rather than removed generic-reader or fixed-buffer-stream patterns.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Module Structure](#module-structure)
|
- [Module Structure](#module-structure)
|
||||||
|
|
@ -80,13 +80,15 @@ var output: std.Io.Writer.Allocating = .init(allocator);
|
||||||
defer output.deinit();
|
defer output.deinit();
|
||||||
|
|
||||||
var buffer: [flate.max_window_len]u8 = undefined;
|
var buffer: [flate.max_window_len]u8 = undefined;
|
||||||
var compress: flate.Compress = .init(&output.writer, &buffer, .{
|
var compress: flate.Compress = try .init(
|
||||||
.level = .default,
|
&output.writer,
|
||||||
.container = .gzip,
|
&buffer,
|
||||||
});
|
.gzip,
|
||||||
|
.default,
|
||||||
|
);
|
||||||
|
|
||||||
try compress.writer.writeAll(data);
|
try compress.writer.writeAll(data);
|
||||||
try compress.end();
|
try compress.finish();
|
||||||
|
|
||||||
const compressed = output.written();
|
const compressed = output.written();
|
||||||
```
|
```
|
||||||
|
|
@ -94,7 +96,10 @@ const compressed = output.written();
|
||||||
### Compression Levels
|
### Compression Levels
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const Level = enum {
|
const Options = struct {
|
||||||
|
// Options are parameter sets rather than an enum. Levels 1 through 9 are
|
||||||
|
// available; these public constants select common presets:
|
||||||
|
level_1: Options,
|
||||||
level_4, // Fastest
|
level_4, // Fastest
|
||||||
level_5,
|
level_5,
|
||||||
level_6, // Default
|
level_6, // Default
|
||||||
|
|
@ -102,7 +107,7 @@ const Level = enum {
|
||||||
level_8,
|
level_8,
|
||||||
level_9, // Best compression
|
level_9, // Best compression
|
||||||
|
|
||||||
fast, // Alias for level_4
|
fastest, // Alias for level_1
|
||||||
default, // Alias for level_6
|
default, // Alias for level_6
|
||||||
best, // Alias for level_9
|
best, // Alias for level_9
|
||||||
};
|
};
|
||||||
|
|
@ -110,12 +115,7 @@ const Level = enum {
|
||||||
|
|
||||||
### Huffman-Only Compression
|
### Huffman-Only Compression
|
||||||
|
|
||||||
For faster compression without LZ77 match searching:
|
`flate.Compress.Huffman` exposes public initialization and a writer that skips LZ77 match searching. However, in the installed Zig 0.16 source its end-of-stream `finish` method is private. External code therefore cannot complete the container lifecycle through the public API alone. Treat it as an implementation-facing type in this release rather than a standalone archive-writing recipe; use `flate.Compress` when a complete public compression lifecycle is required.
|
||||||
|
|
||||||
```zig
|
|
||||||
const HuffmanEncoder = flate.HuffmanEncoder;
|
|
||||||
// Used internally for Huffman-only encoding (bigger output, faster compression)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Zstandard
|
## Zstandard
|
||||||
|
|
||||||
|
|
@ -171,38 +171,38 @@ LZMA decompression with streaming reader interface.
|
||||||
```zig
|
```zig
|
||||||
const lzma = std.compress.lzma;
|
const lzma = std.compress.lzma;
|
||||||
|
|
||||||
var decompress = try lzma.decompress(allocator, reader);
|
var decoder_buffer = try allocator.alloc(u8, 4096);
|
||||||
|
errdefer allocator.free(decoder_buffer);
|
||||||
|
var decompress: lzma.Decompress = try .initOptions(
|
||||||
|
&reader,
|
||||||
|
allocator,
|
||||||
|
decoder_buffer,
|
||||||
|
.{},
|
||||||
|
128 * 1024 * 1024,
|
||||||
|
);
|
||||||
defer decompress.deinit();
|
defer decompress.deinit();
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
_ = try decompress.reader.streamRemaining(&output.writer);
|
||||||
while (true) {
|
|
||||||
const n = try decompress.read(&buf);
|
|
||||||
if (n == 0) break;
|
|
||||||
// Process buf[0..n]
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### With Options
|
### With Options
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var decompress = try lzma.decompressWithOptions(allocator, reader, .{
|
var decompress: lzma.Decompress = try .initOptions(
|
||||||
.memlimit = 128 * 1024 * 1024, // 128 MB memory limit
|
&reader,
|
||||||
});
|
allocator,
|
||||||
|
decoder_buffer,
|
||||||
|
.{ .allow_incomplete = false },
|
||||||
|
128 * 1024 * 1024,
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Decompress Type
|
### Decompress Type
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn Decompress(comptime ReaderType: type) type {
|
// Decompress embeds `reader: std.Io.Reader` and owns the caller-supplied
|
||||||
return struct {
|
// buffer after init. `deinit` frees that buffer unless `takeBuffer` first
|
||||||
pub const Reader = std.io.GenericReader(*Self, Error, read);
|
// reclaims it. `initParams` and `initOptions` are the construction paths.
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, source: ReaderType, params: Params, memlimit: ?usize) !Self;
|
|
||||||
pub fn deinit(self: *Self) void;
|
|
||||||
pub fn reader(self: *Self) Reader;
|
|
||||||
pub fn read(self: *Self, output: []u8) Error!usize;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## LZMA2
|
## LZMA2
|
||||||
|
|
@ -214,11 +214,12 @@ LZMA2 decompression (improved LZMA with better streaming support).
|
||||||
```zig
|
```zig
|
||||||
const lzma2 = std.compress.lzma2;
|
const lzma2 = std.compress.lzma2;
|
||||||
|
|
||||||
var output = std.ArrayList(u8).empty;
|
var input: std.Io.Reader = .fixed(compressed_data);
|
||||||
defer output.deinit(allocator);
|
var output: std.Io.Writer.Allocating = .init(allocator);
|
||||||
|
defer output.deinit();
|
||||||
var stream = std.io.fixedBufferStream(compressed_data);
|
var decode = try lzma2.Decode.init(allocator);
|
||||||
try lzma2.decompress(allocator, stream.reader(), output.writer(allocator));
|
defer decode.deinit(allocator);
|
||||||
|
_ = try decode.decompress(&input, &output);
|
||||||
```
|
```
|
||||||
|
|
||||||
## XZ
|
## XZ
|
||||||
|
|
@ -230,20 +231,17 @@ XZ format decompression (LZMA2 in a container with checksums).
|
||||||
```zig
|
```zig
|
||||||
const xz = std.compress.xz;
|
const xz = std.compress.xz;
|
||||||
|
|
||||||
var decompress = try xz.decompress(allocator, reader);
|
var decoder_buffer = try allocator.alloc(u8, 4096);
|
||||||
|
errdefer allocator.free(decoder_buffer);
|
||||||
|
var decompress: xz.Decompress = try .init(&reader, allocator, decoder_buffer);
|
||||||
defer decompress.deinit();
|
defer decompress.deinit();
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
_ = try decompress.reader.streamRemaining(&output.writer);
|
||||||
while (true) {
|
|
||||||
const n = try decompress.read(&buf);
|
|
||||||
if (n == 0) break;
|
|
||||||
// Process buf[0..n]
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Check Types
|
### Check Types
|
||||||
|
|
||||||
XZ supports multiple integrity check types:
|
XZ parses these integrity-check identifiers, but Zig 0.16 does not implement full XZ block-check verification. Do not treat successful decoding as verification of CRC32, CRC64, or SHA-256 block checks:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub const Check = enum(u4) {
|
pub const Check = enum(u4) {
|
||||||
|
|
@ -312,23 +310,24 @@ fn decompressZstd(allocator: Allocator, compressed: []const u8) ![]u8 {
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn decompressToFile(
|
fn decompressToFile(
|
||||||
|
io: std.Io,
|
||||||
input_path: []const u8,
|
input_path: []const u8,
|
||||||
output_path: []const u8,
|
output_path: []const u8,
|
||||||
container: std.compress.flate.Container,
|
container: std.compress.flate.Container,
|
||||||
) !void {
|
) !void {
|
||||||
const flate = std.compress.flate;
|
const flate = std.compress.flate;
|
||||||
|
|
||||||
const input_file = try std.fs.cwd().openFile(input_path, .{});
|
const input_file = try std.Io.Dir.cwd().openFile(io, input_path, .{});
|
||||||
defer input_file.close();
|
defer input_file.close(io);
|
||||||
|
|
||||||
const output_file = try std.fs.cwd().createFile(output_path, .{});
|
const output_file = try std.Io.Dir.cwd().createFile(io, output_path, .{});
|
||||||
defer output_file.close();
|
defer output_file.close(io);
|
||||||
|
|
||||||
var input_buf: [4096]u8 = undefined;
|
var input_buf: [4096]u8 = undefined;
|
||||||
var input_reader = input_file.reader(&input_buf);
|
var input_reader = input_file.reader(io, &input_buf);
|
||||||
|
|
||||||
var output_buf: [4096]u8 = undefined;
|
var output_buf: [4096]u8 = undefined;
|
||||||
var output_writer = output_file.writer(&output_buf);
|
var output_writer = output_file.writer(io, &output_buf);
|
||||||
|
|
||||||
var decompress: flate.Decompress = .init(&input_reader.interface, container, &.{});
|
var decompress: flate.Decompress = .init(&input_reader.interface, container, &.{});
|
||||||
_ = try decompress.reader.streamRemaining(&output_writer.interface);
|
_ = try decompress.reader.streamRemaining(&output_writer.interface);
|
||||||
|
|
@ -437,7 +436,7 @@ pub const Error = error{
|
||||||
|
|
||||||
**DEFLATE (flate)**:
|
**DEFLATE (flate)**:
|
||||||
- Decompression: gzip, zlib, raw deflate
|
- Decompression: gzip, zlib, raw deflate
|
||||||
- Compression: gzip, zlib, raw deflate (levels 4-9)
|
- Compression: gzip, zlib, raw deflate (levels 1-9)
|
||||||
- Streaming with history buffer
|
- Streaming with history buffer
|
||||||
|
|
||||||
**Zstandard (zstd)**:
|
**Zstandard (zstd)**:
|
||||||
|
|
@ -453,5 +452,5 @@ pub const Error = error{
|
||||||
|
|
||||||
**XZ**:
|
**XZ**:
|
||||||
- Decompression only
|
- Decompression only
|
||||||
- CRC32/CRC64/SHA256 integrity checks
|
- CRC32/CRC64/SHA256 check IDs are parsed; full block-check verification is incomplete
|
||||||
- Multiple block support
|
- Multiple block support
|
||||||
|
|
|
||||||
|
|
@ -114,9 +114,11 @@ Blake3.hash("data", &digest, .{});
|
||||||
var keyed: [Blake3.digest_length]u8 = undefined;
|
var keyed: [Blake3.digest_length]u8 = undefined;
|
||||||
Blake3.hash("data", &keyed, .{ .key = key });
|
Blake3.hash("data", &keyed, .{ .key = key });
|
||||||
|
|
||||||
// Key derivation
|
// Key derivation uses the dedicated KDF mode, not keyed-hash options.
|
||||||
var derived: [32]u8 = undefined;
|
var derived: [32]u8 = undefined;
|
||||||
Blake3.hash("material", &derived, .{ .context = "my app v1 key derivation" });
|
var kdf = Blake3.initKdf("my app v1 key derivation", .{});
|
||||||
|
kdf.update("material");
|
||||||
|
kdf.final(&derived);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Blake2
|
### Blake2
|
||||||
|
|
@ -149,7 +151,8 @@ Aes256Gcm.encrypt(&ciphertext, &tag, plaintext, associated_data, nonce, key);
|
||||||
// Decryption
|
// Decryption
|
||||||
var decrypted: [ciphertext.len]u8 = undefined;
|
var decrypted: [ciphertext.len]u8 = undefined;
|
||||||
try Aes256Gcm.decrypt(&decrypted, &ciphertext, tag, associated_data, nonce, key);
|
try Aes256Gcm.decrypt(&decrypted, &ciphertext, tag, associated_data, nonce, key);
|
||||||
// Returns error.AuthenticationFailed if tag doesn't verify
|
// Returns error.AuthenticationFailed if the tag doesn't verify. Treat the
|
||||||
|
// contents of `decrypted` as invalid and discard them on any failure.
|
||||||
```
|
```
|
||||||
|
|
||||||
Key constants:
|
Key constants:
|
||||||
|
|
@ -231,13 +234,13 @@ const hash = SipHash.hash(key, data);
|
||||||
const Ed25519 = std.crypto.sign.Ed25519;
|
const Ed25519 = std.crypto.sign.Ed25519;
|
||||||
|
|
||||||
// Generate key pair
|
// Generate key pair
|
||||||
const kp = Ed25519.KeyPair.generate();
|
const kp = Ed25519.KeyPair.generate(io);
|
||||||
|
|
||||||
// Sign message
|
// Sign message
|
||||||
const sig = kp.sign(message, null);
|
const sig = kp.sign(message, null);
|
||||||
|
|
||||||
// Verify signature
|
// Verify signature
|
||||||
try kp.public_key.verify(sig, message);
|
try sig.verify(message, kp.public_key);
|
||||||
// Returns error.SignatureVerificationFailed on failure
|
// Returns error.SignatureVerificationFailed on failure
|
||||||
|
|
||||||
// Incremental signing (large messages)
|
// Incremental signing (large messages)
|
||||||
|
|
@ -258,7 +261,7 @@ Key lengths:
|
||||||
const EcdsaP256Sha256 = std.crypto.sign.ecdsa.EcdsaP256Sha256;
|
const EcdsaP256Sha256 = std.crypto.sign.ecdsa.EcdsaP256Sha256;
|
||||||
|
|
||||||
// Generate key pair
|
// Generate key pair
|
||||||
const kp = EcdsaP256Sha256.KeyPair.generate();
|
const kp = EcdsaP256Sha256.KeyPair.generate(io);
|
||||||
|
|
||||||
// Sign
|
// Sign
|
||||||
const sig = try kp.sign(message, null);
|
const sig = try kp.sign(message, null);
|
||||||
|
|
@ -277,8 +280,8 @@ Available: `EcdsaP256Sha256`, `EcdsaP256Sha3_256`, `EcdsaP384Sha384`, `EcdsaP384
|
||||||
const X25519 = std.crypto.dh.X25519;
|
const X25519 = std.crypto.dh.X25519;
|
||||||
|
|
||||||
// Generate key pairs for Alice and Bob
|
// Generate key pairs for Alice and Bob
|
||||||
const alice = X25519.KeyPair.generate();
|
const alice = X25519.KeyPair.generate(io);
|
||||||
const bob = X25519.KeyPair.generate();
|
const bob = X25519.KeyPair.generate(io);
|
||||||
|
|
||||||
// Compute shared secret
|
// Compute shared secret
|
||||||
const alice_shared = try X25519.scalarmult(alice.secret_key, bob.public_key);
|
const alice_shared = try X25519.scalarmult(alice.secret_key, bob.public_key);
|
||||||
|
|
@ -293,13 +296,13 @@ std.crypto.hash.sha2.Sha256.hash(&alice_shared, &key, .{});
|
||||||
### ML-KEM (Post-Quantum)
|
### ML-KEM (Post-Quantum)
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const MlKem768 = std.crypto.kem.ml_kem.MlKem768;
|
const MLKem768 = std.crypto.kem.ml_kem.MLKem768;
|
||||||
|
|
||||||
// Key generation
|
// Key generation
|
||||||
const kp = MlKem768.KeyPair.generate();
|
const kp = MLKem768.KeyPair.generate(io);
|
||||||
|
|
||||||
// Encapsulation (sender)
|
// Encapsulation (sender)
|
||||||
const encaps = kp.public_key.encaps(null);
|
const encaps = kp.public_key.encaps(io);
|
||||||
const shared_secret = encaps.shared_secret;
|
const shared_secret = encaps.shared_secret;
|
||||||
const ciphertext = encaps.ciphertext;
|
const ciphertext = encaps.ciphertext;
|
||||||
|
|
||||||
|
|
@ -308,7 +311,7 @@ const decaps_secret = try kp.secret_key.decaps(ciphertext);
|
||||||
// shared_secret == decaps_secret
|
// shared_secret == decaps_secret
|
||||||
```
|
```
|
||||||
|
|
||||||
Available: `MlKem512`, `MlKem768`, `MlKem1024`
|
Available: `MLKem512`, `MLKem768`, `MLKem1024`
|
||||||
|
|
||||||
## Key Derivation
|
## Key Derivation
|
||||||
|
|
||||||
|
|
@ -354,18 +357,23 @@ try argon2.kdf(
|
||||||
.p = 4, // parallelism
|
.p = 4, // parallelism
|
||||||
},
|
},
|
||||||
.argon2id, // mode: argon2i, argon2d, or argon2id
|
.argon2id, // mode: argon2i, argon2d, or argon2id
|
||||||
|
io,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use preset parameters
|
// Use preset parameters
|
||||||
try argon2.kdf(allocator, &hash, password, salt, argon2.Params.interactive_2id, .argon2id);
|
try argon2.kdf(allocator, &hash, password, salt, argon2.Params.interactive_2id, .argon2id, io);
|
||||||
|
|
||||||
// PHC string format (for storage)
|
// PHC string format (for storage)
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
const encoded = try argon2.strHash(password, salt, .interactive_2id, .argon2id, &buf);
|
const encoded = try argon2.strHash(password, .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.params = .interactive_2id,
|
||||||
|
.mode = .argon2id,
|
||||||
|
}, &buf, io);
|
||||||
// Returns: "$argon2id$v=19$m=65536,t=3,p=4$..."
|
// Returns: "$argon2id$v=19$m=65536,t=3,p=4$..."
|
||||||
|
|
||||||
// Verify PHC-encoded hash
|
// Verify PHC-encoded hash
|
||||||
try argon2.strVerify(encoded, password, null);
|
try argon2.strVerify(encoded, password, .{ .allocator = allocator }, io);
|
||||||
```
|
```
|
||||||
|
|
||||||
Parameter presets:
|
Parameter presets:
|
||||||
|
|
@ -400,11 +408,14 @@ try scrypt.kdf(allocator, &hash, password, salt, scrypt.Params.interactive);
|
||||||
const bcrypt = std.crypto.pwhash.bcrypt;
|
const bcrypt = std.crypto.pwhash.bcrypt;
|
||||||
|
|
||||||
// Hash password
|
// Hash password
|
||||||
var hash: [bcrypt.hash_length]u8 = undefined;
|
var hash: [128]u8 = undefined;
|
||||||
try bcrypt.strHash(password, .{ .rounds = 10 }, &hash);
|
const hash_str = try bcrypt.strHash(password, .{
|
||||||
|
.params = .owasp,
|
||||||
|
.encoding = .phc,
|
||||||
|
}, &hash, io);
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
try bcrypt.strVerify(hash_str, password);
|
try bcrypt.strVerify(hash_str, password, .{ .silently_truncate_password = false });
|
||||||
```
|
```
|
||||||
|
|
||||||
### PBKDF2
|
### PBKDF2
|
||||||
|
|
@ -414,21 +425,21 @@ const pbkdf2 = std.crypto.pwhash.pbkdf2;
|
||||||
const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
|
const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
|
||||||
|
|
||||||
var key: [32]u8 = undefined;
|
var key: [32]u8 = undefined;
|
||||||
pbkdf2(HmacSha256, &key, password, salt, 100000); // 100k iterations
|
try pbkdf2(&key, password, salt, 100000, HmacSha256); // 100k iterations
|
||||||
```
|
```
|
||||||
|
|
||||||
## Secure Random
|
## Secure Random
|
||||||
|
|
||||||
Thread-local cryptographically secure PRNG:
|
Use the caller-provided `std.Io` entropy interface. `io.random` is the process CSPRNG and may use a less-secure seeding fallback on platforms where fresh system entropy is unavailable; use `io.randomSecure` when that fallback is unacceptable.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const random = std.crypto.random;
|
|
||||||
|
|
||||||
// Random bytes
|
// Random bytes
|
||||||
var key: [32]u8 = undefined;
|
var key: [32]u8 = undefined;
|
||||||
random.bytes(&key);
|
io.random(&key);
|
||||||
|
|
||||||
// Random integers
|
// Adapt std.Io to the general std.Random convenience API.
|
||||||
|
var source: std.Random.IoSource = .{ .io = io };
|
||||||
|
const random = source.interface();
|
||||||
const n = random.int(u64);
|
const n = random.int(u64);
|
||||||
const bounded = random.intRangeLessThan(u32, 0, 100); // [0, 100)
|
const bounded = random.intRangeLessThan(u32, 0, 100); // [0, 100)
|
||||||
|
|
||||||
|
|
@ -535,10 +546,10 @@ Aes256Gcm.encrypt(&ct, &tag, pt, ad, nonce, key);
|
||||||
```zig
|
```zig
|
||||||
// For symmetric keys
|
// For symmetric keys
|
||||||
var key: [32]u8 = undefined;
|
var key: [32]u8 = undefined;
|
||||||
std.crypto.random.bytes(&key);
|
io.random(&key);
|
||||||
|
|
||||||
// For asymmetric keys
|
// For asymmetric keys
|
||||||
const kp = std.crypto.sign.Ed25519.KeyPair.generate();
|
const kp = std.crypto.sign.Ed25519.KeyPair.generate(io);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Nonce Management
|
### Nonce Management
|
||||||
|
|
@ -553,7 +564,7 @@ counter += 1;
|
||||||
// Option 2: Random (safe with XChaCha's 24-byte nonce)
|
// Option 2: Random (safe with XChaCha's 24-byte nonce)
|
||||||
const XChaCha = std.crypto.aead.chacha_poly.XChaCha20Poly1305;
|
const XChaCha = std.crypto.aead.chacha_poly.XChaCha20Poly1305;
|
||||||
var nonce: [XChaCha.nonce_length]u8 = undefined;
|
var nonce: [XChaCha.nonce_length]u8 = undefined;
|
||||||
std.crypto.random.bytes(&nonce);
|
io.random(&nonce);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Secure Password Storage
|
### Secure Password Storage
|
||||||
|
|
@ -563,11 +574,15 @@ const argon2 = std.crypto.pwhash.argon2;
|
||||||
|
|
||||||
// Registration: hash and store
|
// Registration: hash and store
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
const hash_str = try argon2.strHash(password, null, .interactive_2id, .argon2id, &buf);
|
const hash_str = try argon2.strHash(password, .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.params = .interactive_2id,
|
||||||
|
.mode = .argon2id,
|
||||||
|
}, &buf, io);
|
||||||
// Store hash_str in database
|
// Store hash_str in database
|
||||||
|
|
||||||
// Login: verify
|
// Login: verify
|
||||||
argon2.strVerify(stored_hash, password, null) catch |err| {
|
argon2.strVerify(stored_hash, password, .{ .allocator = allocator }, io) catch |err| {
|
||||||
if (err == error.PasswordVerificationFailed) {
|
if (err == error.PasswordVerificationFailed) {
|
||||||
// Invalid password
|
// Invalid password
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Debugging utilities: panic handling, assertions, stack traces, hex dumps, and va
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
Zig 0.16 reworked debug information and expanded target support for segfault handling/unwinding. When debug output writes to stderr/stdout directly, use `std.Io.File.stderr().writer(io, &buf)` / `std.Io.File.stdout().writer(io, &buf)` rather than old `std.io` or `std.fs.File` APIs.
|
Zig 0.16 reworked debug information and expanded target support for segfault handling/unwinding. For application-owned output, create a writer from the caller's `std.Io` (for example `std.Io.File.stderr().writer(io, &buf)`). Low-level `std.debug.print` and stack-dump helpers intentionally use the configured debug I/O path instead.
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ std.debug.print("loading...", .{});
|
||||||
|
|
||||||
## Format Specifiers
|
## Format Specifiers
|
||||||
|
|
||||||
Format string syntax: `{[arg]:[fill][alignment][width][.precision][specifier]}`
|
Format string syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}`. Named arguments use square brackets, for example `{[name]s}`.
|
||||||
|
|
||||||
### Type Specifiers
|
### Type Specifiers
|
||||||
|
|
||||||
|
|
@ -99,11 +99,11 @@ std.debug.print("{x:.4}\n", .{@as(f32, 1.0)}); // "0x1.0000p0"
|
||||||
std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a"
|
std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a"
|
||||||
|
|
||||||
// Named (with struct)
|
// Named (with struct)
|
||||||
std.debug.print("{name}: {value}\n", .{ .name = "x", .value = 42 });
|
std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 });
|
||||||
|
|
||||||
// Runtime width/precision
|
// Runtime width/precision
|
||||||
std.debug.print("{d:[width]}\n", .{ .width = 5, 42 });
|
std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = 5 });
|
||||||
std.debug.print("{d:.[precision]}\n", .{ .precision = 2, 3.14159 });
|
std.debug.print("{[value]d:.[precision]}\n", .{ .value = 3.14159, .precision = 2 });
|
||||||
```
|
```
|
||||||
|
|
||||||
### Escape Braces
|
### Escape Braces
|
||||||
|
|
@ -177,47 +177,32 @@ Panic prints message + stack trace to stderr, then aborts.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Print current stack trace to stderr
|
// Print current stack trace to stderr
|
||||||
std.debug.dumpCurrentStackTrace(null);
|
std.debug.dumpCurrentStackTrace(.{});
|
||||||
|
|
||||||
// Skip frames until this address
|
// Skip frames until this address
|
||||||
std.debug.dumpCurrentStackTrace(@returnAddress());
|
std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dump to Writer
|
### Dump to Writer
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var stderr = std.Io.File.stderr().writer(io, &buf);
|
var locked = try io.lockStderr(&buf, null);
|
||||||
try std.debug.dumpCurrentStackTraceToWriter(null, &stderr.interface);
|
defer io.unlockStderr();
|
||||||
|
try std.debug.writeCurrentStackTrace(.{}, locked.terminal());
|
||||||
```
|
```
|
||||||
|
|
||||||
### Capture Stack Trace
|
### Capture Stack Trace
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var addrs: [32]usize = undefined;
|
var addrs: [32]usize = undefined;
|
||||||
var trace: std.builtin.StackTrace = .{
|
const trace = std.debug.captureCurrentStackTrace(.{}, &addrs);
|
||||||
.instruction_addresses = &addrs,
|
|
||||||
.index = 0,
|
|
||||||
};
|
|
||||||
std.debug.captureStackTrace(@returnAddress(), &trace);
|
|
||||||
|
|
||||||
// Later: print captured trace
|
// Later: print captured trace
|
||||||
std.debug.dumpStackTrace(trace);
|
std.debug.dumpStackTrace(&trace);
|
||||||
```
|
```
|
||||||
|
|
||||||
### StackIterator
|
`StackUnwindOptions` can also carry a target-specific `cpu_context.Native` pointer and opt into unsafe fallback unwinding. The stack iterator itself is an implementation detail; use `captureCurrentStackTrace`, `writeCurrentStackTrace`, and `dumpCurrentStackTrace` as the public interface.
|
||||||
|
|
||||||
Walk the stack manually:
|
|
||||||
|
|
||||||
```zig
|
|
||||||
var it = std.debug.StackIterator.init(@returnAddress(), null);
|
|
||||||
defer it.deinit();
|
|
||||||
|
|
||||||
while (it.next()) |return_address| {
|
|
||||||
const addr = return_address -| 1;
|
|
||||||
std.debug.print("0x{x}\n", .{addr});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Hex Dump
|
## Hex Dump
|
||||||
|
|
||||||
|
|
@ -229,13 +214,12 @@ std.debug.dumpHex(data);
|
||||||
// Output:
|
// Output:
|
||||||
// 7fff5fbff8a0 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 00 01 02 Hello, World!...
|
// 7fff5fbff8a0 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 00 01 02 Hello, World!...
|
||||||
|
|
||||||
// Dump to writer
|
// Fallible dump to an existing terminal abstraction
|
||||||
var buf: [256]u8 = undefined;
|
try std.debug.dumpHexFallible(terminal, data);
|
||||||
var aw: std.io.Writer.Allocating = .init(allocator);
|
|
||||||
defer aw.deinit();
|
|
||||||
try std.debug.dumpHexFallible(&aw.writer, .no_color, data);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`dumpHexFallible` accepts `std.Io.Terminal`, not a bare writer, because it uses terminal color/mode operations. Use `.no_color` when constructing a terminal for a non-terminal sink.
|
||||||
|
|
||||||
Output format:
|
Output format:
|
||||||
- Address (lowercase hex)
|
- Address (lowercase hex)
|
||||||
- 16 bytes per line (uppercase hex)
|
- 16 bytes per line (uppercase hex)
|
||||||
|
|
@ -282,7 +266,7 @@ if (MyTrace.enabled) {
|
||||||
std.debug.print("trace: {}", .{trace});
|
std.debug.print("trace: {}", .{trace});
|
||||||
```
|
```
|
||||||
|
|
||||||
In release builds (`enabled = false`), all trace operations are no-ops with zero size.
|
The predefined `std.debug.Trace` is enabled only in Debug mode. A custom `ConfigurableTrace` follows its explicit `is_enabled` argument; when disabled, its operations are no-ops and its storage is zero-sized.
|
||||||
|
|
||||||
## SafetyLock
|
## SafetyLock
|
||||||
|
|
||||||
|
|
@ -304,8 +288,8 @@ fn checkNotLocked() void {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- In Debug/ReleaseSafe: actively tracks lock state
|
- `SafetyLock` follows runtime-safety mode: active in Debug and ReleaseSafe, and a no-op in ReleaseFast and ReleaseSmall.
|
||||||
- In ReleaseFast/ReleaseSmall: all methods are no-ops
|
- `Trace` has the separate Debug-only default described above.
|
||||||
|
|
||||||
## Source Location
|
## Source Location
|
||||||
|
|
||||||
|
|
@ -327,7 +311,7 @@ const unknown = SourceLocation.invalid;
|
||||||
```zig
|
```zig
|
||||||
const Symbol = std.debug.Symbol;
|
const Symbol = std.debug.Symbol;
|
||||||
|
|
||||||
// Symbol with resolved source location
|
// Resolved fields are optional because debug information may be incomplete.
|
||||||
const sym: Symbol = .{
|
const sym: Symbol = .{
|
||||||
.name = "myFunction",
|
.name = "myFunction",
|
||||||
.compile_unit_name = "main.zig",
|
.compile_unit_name = "main.zig",
|
||||||
|
|
@ -335,7 +319,7 @@ const sym: Symbol = .{
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unknown symbol
|
// Unknown symbol
|
||||||
const unknown: Symbol = .{}; // name = "???", compile_unit_name = "???"
|
const unknown: Symbol = .unknown; // all three fields are null
|
||||||
```
|
```
|
||||||
|
|
||||||
## Segfault Handling
|
## Segfault Handling
|
||||||
|
|
@ -356,23 +340,9 @@ const enabled = std.debug.default_enable_segfault_handler;
|
||||||
|
|
||||||
**Note:** `maybeEnableSegfaultHandler()` is called automatically by the runtime if `std.options.enable_segfault_handler` is true.
|
**Note:** `maybeEnableSegfaultHandler()` is called automatically by the runtime if `std.options.enable_segfault_handler` is true.
|
||||||
|
|
||||||
## Thread Context
|
## CPU Context Unwinding
|
||||||
|
|
||||||
Platform-specific CPU register state for stack unwinding:
|
Signal handlers that already receive a native CPU context can pass it through `std.debug.StackUnwindOptions.context` to `captureCurrentStackTrace`, `writeCurrentStackTrace`, or `dumpCurrentStackTrace`. There are no public `std.debug.ThreadContext`, `getContext`, `copyContext`, or `dumpStackTraceFromBase` helpers in Zig 0.16; native context capture is target- and signal-handler-specific under `std.debug.cpu_context`.
|
||||||
|
|
||||||
```zig
|
|
||||||
const ThreadContext = std.debug.ThreadContext;
|
|
||||||
|
|
||||||
var ctx: ThreadContext = undefined;
|
|
||||||
if (std.debug.getContext(&ctx)) {
|
|
||||||
// ctx now contains register state
|
|
||||||
std.debug.dumpStackTraceFromBase(&ctx, stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy context (handles internal pointers)
|
|
||||||
var ctx_copy: ThreadContext = undefined;
|
|
||||||
std.debug.copyContext(&original_ctx, &ctx_copy);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Valgrind Detection
|
## Valgrind Detection
|
||||||
|
|
||||||
|
|
@ -389,28 +359,22 @@ if (std.debug.inValgrind()) {
|
||||||
// Get debug info for current executable
|
// Get debug info for current executable
|
||||||
const info = try std.debug.getSelfDebugInfo();
|
const info = try std.debug.getSelfDebugInfo();
|
||||||
|
|
||||||
// Get symbol at address
|
// Public methods are target-specific through SelfInfo. A broadly available
|
||||||
const symbol = try info.getSymbolAtAddress(allocator, address);
|
// operation is resolving the owning module name; returned storage is owned by
|
||||||
defer if (symbol.source_location) |sl| allocator.free(sl.file_name);
|
// SelfInfo rather than by the caller.
|
||||||
|
const module_name = try info.getModuleName(io, address);
|
||||||
std.debug.print("{s}:{d}: {s}\n", .{
|
std.debug.print("module: {s}\n", .{module_name});
|
||||||
symbol.source_location.?.file_name,
|
|
||||||
symbol.source_location.?.line,
|
|
||||||
symbol.name,
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Whether runtime safety checks are enabled
|
// Whether runtime safety checks are enabled
|
||||||
std.debug.runtime_safety // true in Debug/ReleaseSafe
|
std.debug.runtime_safety // deprecated; reflects stdlib mode, not necessarily the caller's module
|
||||||
|
|
||||||
// Whether platform can produce stack traces
|
// Whether platform can produce stack traces
|
||||||
std.debug.sys_can_stack_trace // false on WASM, MIPS, etc.
|
std.debug.sys_can_stack_trace // false on WASM, MIPS, etc.
|
||||||
|
|
||||||
// Whether platform has ucontext_t
|
|
||||||
std.debug.have_ucontext
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Submodules
|
## Submodules
|
||||||
|
|
@ -420,8 +384,8 @@ std.debug.have_ucontext
|
||||||
| `std.debug.Dwarf` | DWARF debug info parser |
|
| `std.debug.Dwarf` | DWARF debug info parser |
|
||||||
| `std.debug.Pdb` | Windows PDB debug info parser |
|
| `std.debug.Pdb` | Windows PDB debug info parser |
|
||||||
| `std.debug.SelfInfo` | Debug info for current executable |
|
| `std.debug.SelfInfo` | Debug info for current executable |
|
||||||
| `std.debug.MemoryAccessor` | Safe memory access for unwinding |
|
|
||||||
| `std.debug.Coverage` | Code coverage support |
|
| `std.debug.Coverage` | Code coverage support |
|
||||||
|
| `std.debug.cpu_context` | Target-specific native CPU context definitions |
|
||||||
|
|
||||||
## FullPanic
|
## FullPanic
|
||||||
|
|
||||||
|
|
@ -447,24 +411,22 @@ fn myPanicFn(msg: []const u8, ret_addr: ?usize) noreturn {
|
||||||
For multi-line debug output without interleaving:
|
For multi-line debug output without interleaving:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Lock stderr and clear any progress indicators
|
// Lock stderr and clear any progress indicators. The returned object exposes
|
||||||
std.debug.lockStdErr();
|
// a file writer and terminal; unlock performs the final flush automatically.
|
||||||
defer std.debug.unlockStdErr();
|
var lock_buf: [256]u8 = undefined;
|
||||||
|
const locked = std.debug.lockStderr(&lock_buf);
|
||||||
|
defer std.debug.unlockStderr();
|
||||||
|
|
||||||
// Safe to write multiple lines
|
// Safe to write multiple lines
|
||||||
var buf: [256]u8 = undefined;
|
try locked.file_writer.interface.writeAll("Line 1\n");
|
||||||
var stderr = std.Io.File.stderr().writer(io, &buf);
|
try locked.file_writer.interface.writeAll("Line 2\n");
|
||||||
try stderr.interface.writeAll("Line 1\n");
|
|
||||||
try stderr.interface.writeAll("Line 2\n");
|
|
||||||
try stderr.interface.flush();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or with a writer:
|
The matching function is spelled `unlockStderr`:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const writer = std.debug.lockStderrWriter(&buf);
|
const locked = std.debug.lockStderr(&buf);
|
||||||
defer std.debug.unlockStderrWriter();
|
defer std.debug.unlockStderr();
|
||||||
|
try locked.file_writer.interface.print("Complex output: {}\n", .{value});
|
||||||
try writer.print("Complex output: {}\n", .{value});
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ const Color = enum { red, green, blue, yellow };
|
||||||
const ColorSet = std.enums.EnumSet(Color);
|
const ColorSet = std.enums.EnumSet(Color);
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
var colors = ColorSet.initEmpty();
|
var colors: ColorSet = .empty;
|
||||||
var all = ColorSet.initFull();
|
const all: ColorSet = .full;
|
||||||
|
|
||||||
// Struct-style init
|
// Struct-style init
|
||||||
var primary = ColorSet.init(.{
|
var primary = ColorSet.init(.{
|
||||||
|
|
@ -106,7 +106,7 @@ if (map.get(.red)) |value| {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get with default
|
// Get with default
|
||||||
const value = map.getOrDefault(.blue, 0);
|
const value = map.get(.blue) orelse 0;
|
||||||
|
|
||||||
// Get pointer
|
// Get pointer
|
||||||
if (map.getPtr(.red)) |ptr| {
|
if (map.getPtr(.red)) |ptr| {
|
||||||
|
|
@ -132,10 +132,10 @@ while (it.next()) |entry| {
|
||||||
std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
|
std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate keys only
|
// Iterate keys only by ignoring each entry's value pointer
|
||||||
var key_it = map.keyIterator();
|
var key_it = map.iterator();
|
||||||
while (key_it.next()) |key| {
|
while (key_it.next()) |entry| {
|
||||||
std.debug.print("{}\n", .{key});
|
std.debug.print("{}\n", .{entry.key});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -171,8 +171,11 @@ for (std.enums.values(Color)) |color| {
|
||||||
std.debug.print("{}: {}\n", .{ color, rgb.get(color) });
|
std.debug.print("{}: {}\n", .{ color, rgb.get(color) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct slice access
|
// Public sequential access uses the iterator.
|
||||||
const slice = rgb.values; // [3]u32
|
var rgb_it = rgb.iterator();
|
||||||
|
while (rgb_it.next()) |entry| {
|
||||||
|
std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## EnumIndexer
|
## EnumIndexer
|
||||||
|
|
@ -191,7 +194,7 @@ const count = Indexer.count; // 3
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Get all values as slice
|
// Get all values as slice
|
||||||
const colors = std.enums.values(Color); // [3]Color
|
const colors = std.enums.values(Color); // []const Color
|
||||||
|
|
||||||
// Safe tag name (works with non-exhaustive)
|
// Safe tag name (works with non-exhaustive)
|
||||||
const name = std.enums.tagName(Color, .red); // "red" or null
|
const name = std.enums.tagName(Color, .red); // "red" or null
|
||||||
|
|
@ -244,7 +247,7 @@ fn canAccess(user: User, required: Permissions) bool {
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
const admin = User{
|
const admin = User{
|
||||||
.name = "admin",
|
.name = "admin",
|
||||||
.perms = Permissions.initFull(),
|
.perms = .full,
|
||||||
};
|
};
|
||||||
|
|
||||||
const reader = User{
|
const reader = User{
|
||||||
|
|
@ -293,8 +296,8 @@ pub fn main() void {
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- `EnumSet`: Bit-backed, use for presence tracking
|
- `EnumSet`: Bit-backed, use for presence tracking
|
||||||
- `EnumMap`: Sparse, only stores present values
|
- `EnumMap`: Fixed-size dense value storage plus a presence bitset
|
||||||
- `EnumArray`: Dense, all values always present
|
- `EnumArray`: Dense, all values always present
|
||||||
- All are fixed-size, zero-allocation, copyable by value
|
- All are fixed-size, zero-allocation, copyable by value
|
||||||
- Use `std.StaticBitSet` for non-enum integer sets
|
- Use `std.StaticBitSet` for non-enum integer sets
|
||||||
- Works with non-exhaustive enums (explicit fields only)
|
- Exhaustive sparse enums are remapped densely. For non-exhaustive enums, index-based containers span the representable tag range rather than only explicit fields, which can make them impractically large; check each container's `EnumIndexer` behavior before use.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/releas
|
||||||
- `std.fmt.format` is replaced by `std.Io.Writer.print`.
|
- `std.fmt.format` is replaced by `std.Io.Writer.print`.
|
||||||
- `std.fmt.Formatter` is renamed to `std.fmt.Alt`.
|
- `std.fmt.Formatter` is renamed to `std.fmt.Alt`.
|
||||||
- `std.fmt.FormatOptions` is renamed to `std.fmt.Options`.
|
- `std.fmt.FormatOptions` is renamed to `std.fmt.Options`.
|
||||||
- `std.fmt.bufPrintZ` is renamed to `std.fmt.bufPrintSentinel`.
|
- `std.fmt.bufPrintZ` remains as a deprecated compatibility wrapper; use `std.fmt.bufPrintSentinel`.
|
||||||
- The `{D}` duration specifier was removed; format `std.Io.Duration` with `{f}`.
|
- The `{D}` duration specifier was removed; format `std.Io.Duration` with `{f}`.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -38,13 +38,13 @@ pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||||
|
|
||||||
## Format String Syntax
|
## Format String Syntax
|
||||||
|
|
||||||
Full syntax: `{[arg]:[fill][alignment][width][.precision][specifier]}`
|
Full syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}`
|
||||||
|
|
||||||
### Components
|
### Components
|
||||||
|
|
||||||
| Component | Description | Example |
|
| Component | Description | Example |
|
||||||
|-----------|-------------|---------|
|
|-----------|-------------|---------|
|
||||||
| `arg` | Argument index or name | `{0}`, `{name}` |
|
| `argument` | Argument index or bracketed name | `{0}`, `{[name]s}` |
|
||||||
| `fill` | Padding character | `{:0>5}` uses `0` |
|
| `fill` | Padding character | `{:0>5}` uses `0` |
|
||||||
| `alignment` | `<` left, `^` center, `>` right | `{:<10}` |
|
| `alignment` | `<` left, `^` center, `>` right | `{:<10}` |
|
||||||
| `width` | Minimum field width | `{:10}` |
|
| `width` | Minimum field width | `{:10}` |
|
||||||
|
|
@ -63,14 +63,14 @@ std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a"
|
||||||
### Named Arguments
|
### Named Arguments
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
std.debug.print("{name}: {value}\n", .{ .name = "x", .value = 42 });
|
std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 });
|
||||||
```
|
```
|
||||||
|
|
||||||
### Runtime Width/Precision
|
### Runtime Width/Precision
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
std.debug.print("{d:[width]}\n", .{ .width = @as(usize, 8), 42 });
|
std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = @as(usize, 8) });
|
||||||
std.debug.print("{d:.[prec]}\n", .{ .prec = @as(usize, 2), 3.14159 });
|
std.debug.print("{[value]d:.[prec]}\n", .{ .value = 3.14159, .prec = @as(usize, 2) });
|
||||||
```
|
```
|
||||||
|
|
||||||
### Escape Braces
|
### Escape Braces
|
||||||
|
|
@ -81,7 +81,9 @@ std.debug.print("{{literal}}\n", .{}); // "{literal}"
|
||||||
|
|
||||||
## Format Specifiers
|
## Format Specifiers
|
||||||
|
|
||||||
### Type Specifiers
|
### Selected Type Specifiers
|
||||||
|
|
||||||
|
This table lists common specifiers; it is not a complete substitute for `std.Io.Writer`'s type-specific formatting rules.
|
||||||
|
|
||||||
| Specifier | Types | Output |
|
| Specifier | Types | Output |
|
||||||
|-----------|-------|--------|
|
|-----------|-------|--------|
|
||||||
|
|
@ -171,7 +173,8 @@ const e = try std.fmt.parseInt(i32, "0b101", 0); // 5 (binary)
|
||||||
const f = try std.fmt.parseInt(i32, "0o17", 0); // 15 (octal)
|
const f = try std.fmt.parseInt(i32, "0o17", 0); // 15 (octal)
|
||||||
const g = try std.fmt.parseInt(i32, "42", 0); // 42 (decimal)
|
const g = try std.fmt.parseInt(i32, "42", 0); // 42 (decimal)
|
||||||
|
|
||||||
// Underscores allowed between digits
|
// Underscores are ignored within the digit sequence. Leading/trailing
|
||||||
|
// underscores and underscores immediately after a base prefix are invalid.
|
||||||
const h = try std.fmt.parseInt(u32, "1_000_000", 10); // 1000000
|
const h = try std.fmt.parseInt(u32, "1_000_000", 10); // 1000000
|
||||||
const i = try std.fmt.parseInt(u32, "0xff_ff", 0); // 65535
|
const i = try std.fmt.parseInt(u32, "0xff_ff", 0); // 65535
|
||||||
```
|
```
|
||||||
|
|
@ -306,19 +309,19 @@ const result = try std.fmt.bufPrint(&buf, "Hello {s}!", .{"world"});
|
||||||
**Errors:**
|
**Errors:**
|
||||||
- `error.NoSpaceLeft` - Buffer too small
|
- `error.NoSpaceLeft` - Buffer too small
|
||||||
|
|
||||||
### bufPrintZ
|
### bufPrintSentinel
|
||||||
|
|
||||||
Format into buffer with null terminator.
|
Format into buffer with null terminator.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const result = try std.fmt.bufPrintZ(&buf, "Hello {s}!", .{"world"});
|
const result = try std.fmt.bufPrintSentinel(&buf, "Hello {s}!", .{"world"}, 0);
|
||||||
// result is [:0]u8 = "Hello world!" (null-terminated)
|
// result is [:0]u8 = "Hello world!" (null-terminated)
|
||||||
```
|
```
|
||||||
|
|
||||||
### count
|
### count
|
||||||
|
|
||||||
Count characters needed for format (without allocating).
|
Count output bytes needed for the format (without allocating).
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const len = std.fmt.count("Value: {d}, Name: {s}", .{ 42, "test" });
|
const len = std.fmt.count("Value: {d}, Name: {s}", .{ 42, "test" });
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.h
|
||||||
|
|
||||||
Zig 0.16 migrates file-system operations to `std.Io`. Use `std.Io.Dir`, `std.Io.File`, and an explicit `std.Io` parameter. `std.fs` is now mostly path helpers and deprecated compatibility names.
|
Zig 0.16 migrates file-system operations to `std.Io`. Use `std.Io.Dir`, `std.Io.File`, and an explicit `std.Io` parameter. `std.fs` is now mostly path helpers and deprecated compatibility names.
|
||||||
|
|
||||||
|
The snippets below are focused fragments. They assume `const std =
|
||||||
|
@import("std")`, a caller-supplied `io: std.Io`, a suitable allocator, and any
|
||||||
|
named application values such as `max_size`, `from`, and `to`.
|
||||||
|
|
||||||
## Core Types
|
## Core Types
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -41,7 +45,10 @@ const file = try std.Io.Dir.cwd().createFile(io, "out.txt", .{});
|
||||||
defer file.close(io);
|
defer file.close(io);
|
||||||
```
|
```
|
||||||
|
|
||||||
Common options remain conceptually similar: truncate, exclusive create, read access, mode/permissions, and locking where supported.
|
With default options, `createFile` truncates an existing regular file. Use the
|
||||||
|
exclusive-create option when overwriting must fail. Other options cover read
|
||||||
|
access, `permissions`, and locking where supported. File access mode is an
|
||||||
|
`openFile` option, not the name of the creation-permissions field.
|
||||||
|
|
||||||
## Reading Files
|
## Reading Files
|
||||||
|
|
||||||
|
|
@ -68,6 +75,9 @@ defer allocator.free(bytes);
|
||||||
```
|
```
|
||||||
|
|
||||||
The limit uses `std.Io.Limit`. Hitting the limit returns `error.StreamTooLong`.
|
The limit uses `std.Io.Limit`. Hitting the limit returns `error.StreamTooLong`.
|
||||||
|
`readFileAlloc` creates a file reader internally and reads until the supplied
|
||||||
|
limit. Use an explicit `File.Reader` when streaming, reusing buffers, or
|
||||||
|
controlling incremental consumption.
|
||||||
|
|
||||||
### Read To End From Existing File
|
### Read To End From Existing File
|
||||||
|
|
||||||
|
|
@ -130,14 +140,16 @@ if (try stdin_reader.interface.takeDelimiter('\n')) |line| {
|
||||||
## Directories
|
## Directories
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var dir = try std.Io.Dir.cwd().openDir(io, "assets", .{});
|
var dir = try std.Io.Dir.cwd().openDir(io, "assets", .{ .iterate = true });
|
||||||
defer dir.close(io);
|
defer dir.close(io);
|
||||||
|
|
||||||
try std.Io.Dir.cwd().createDir(io, "new-dir", .default_dir);
|
try std.Io.Dir.cwd().createDir(io, "new-dir", .default_dir);
|
||||||
try std.Io.Dir.cwd().createDirPath(io, "path/to/nested");
|
try std.Io.Dir.cwd().createDirPath(io, "path/to/nested");
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `openDir` options for iteration/access/no-follow behavior as needed.
|
Both walking APIs require a directory opened with `.iterate = true`; iterating
|
||||||
|
without that capability is illegal behavior. Use other `openDir` options for
|
||||||
|
access/no-follow behavior as needed.
|
||||||
|
|
||||||
## Walking
|
## Walking
|
||||||
|
|
||||||
|
|
@ -190,7 +202,10 @@ try file.setTimestamps(io, .{
|
||||||
|
|
||||||
`std.Io.Dir.path` / `std.fs.path` functions handle Windows paths more consistently in 0.16.
|
`std.Io.Dir.path` / `std.fs.path` functions handle Windows paths more consistently in 0.16.
|
||||||
|
|
||||||
`relative`, `relativeWindows`, and `relativePosix` are pure: pass the current directory and optional environment map instead of letting the function query the OS.
|
The relative-path helpers are pure and receive the current directory rather
|
||||||
|
than querying the OS. `relative` and `relativeWindows` also accept an optional
|
||||||
|
environment map for Windows per-drive current-directory resolution;
|
||||||
|
`relativePosix` has a distinct signature without that map.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const cwd_path = try std.process.currentPathAlloc(io, allocator);
|
const cwd_path = try std.process.currentPathAlloc(io, allocator);
|
||||||
|
|
@ -202,7 +217,10 @@ defer allocator.free(rel);
|
||||||
|
|
||||||
## Atomic Files
|
## Atomic Files
|
||||||
|
|
||||||
Use `std.Io.File.Atomic` or directory atomic helpers instead of hand-rolled random temporary names. The 0.16 implementation is routed through `std.Io`, including entropy.
|
Obtain a `std.Io.File.Atomic` value through `Dir.createFileAtomic`, then use its
|
||||||
|
public cleanup/materialization lifecycle. Do not construct `File.Atomic`
|
||||||
|
directly or hand-roll random temporary names; the helper routes entropy and
|
||||||
|
filesystem work through `std.Io`.
|
||||||
|
|
||||||
## Absolute Operations
|
## Absolute Operations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ const h7 = hash.CityHash64.hash("hello world");
|
||||||
|
|
||||||
### Streaming/Incremental Hashing
|
### Streaming/Incremental Hashing
|
||||||
|
|
||||||
All hashers support incremental updates for large or streaming data:
|
Hashers that expose `init`/`update`/`final` support incremental input. Some checksum types have a simpler state shape, and some algorithms expose only one-shot helpers; check the selected type rather than assuming a uniform interface.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
@ -101,7 +101,7 @@ const h3 = fnv.final();
|
||||||
|
|
||||||
## Auto-Hashing (Generic Types)
|
## Auto-Hashing (Generic Types)
|
||||||
|
|
||||||
`std.hash.autoHash` automatically hashes any Zig type:
|
`std.hash.autoHash` hashes eligible Zig value types. It intentionally rejects ambiguous categories such as slices unless the caller selects an explicit pointer strategy, and some type categories are unsupported.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
@ -117,7 +117,7 @@ fn hashPoint(p: Point) u64 {
|
||||||
return hasher.final();
|
return hasher.final();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Works with any hashable type
|
// Works with types accepted by autoHash; slices/pointers require deliberate handling.
|
||||||
fn hashAny(value: anytype) u64 {
|
fn hashAny(value: anytype) u64 {
|
||||||
var hasher = std.hash.Wyhash.init(0);
|
var hasher = std.hash.Wyhash.init(0);
|
||||||
std.hash.autoHash(&hasher, value);
|
std.hash.autoHash(&hasher, value);
|
||||||
|
|
@ -141,7 +141,7 @@ const Strategy = std.hash.Strategy;
|
||||||
var hasher = std.hash.Wyhash.init(0);
|
var hasher = std.hash.Wyhash.init(0);
|
||||||
const data: []const u8 = "hello";
|
const data: []const u8 = "hello";
|
||||||
|
|
||||||
// Shallow: hash pointer address only (default for autoHash)
|
// Shallow slice hashing includes its pointer and length, not its contents.
|
||||||
std.hash.autoHashStrat(&hasher, data, .Shallow);
|
std.hash.autoHashStrat(&hasher, data, .Shallow);
|
||||||
|
|
||||||
// Deep: follow pointer, hash contents (one level)
|
// Deep: follow pointer, hash contents (one level)
|
||||||
|
|
@ -153,7 +153,7 @@ std.hash.autoHashStrat(&hasher, data, .DeepRecursive);
|
||||||
|
|
||||||
| Strategy | Behavior |
|
| Strategy | Behavior |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| `.Shallow` | Hash pointer address, not contents |
|
| `.Shallow` | Hash a one-item pointer's address; for a slice, hash pointer and length, not elements |
|
||||||
| `.Deep` | Follow pointer one level, hash contents |
|
| `.Deep` | Follow pointer one level, hash contents |
|
||||||
| `.DeepRecursive` | Follow all pointers, hash all contents |
|
| `.DeepRecursive` | Follow all pointers, hash all contents |
|
||||||
|
|
||||||
|
|
@ -240,14 +240,14 @@ const Adler32 = std.hash.Adler32;
|
||||||
const checksum = Adler32.hash("data");
|
const checksum = Adler32.hash("data");
|
||||||
|
|
||||||
// Streaming
|
// Streaming
|
||||||
var adler = Adler32.init();
|
var adler: Adler32 = .{};
|
||||||
adler.update("data");
|
adler.update("data");
|
||||||
const result = adler.final();
|
const result = adler.adler;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using with HashMap
|
## Using with HashMap
|
||||||
|
|
||||||
HashMap uses `std.hash.autoHash` by default:
|
`AutoHashMap` uses the stdlib's automatic hashing context. `StringHashMap` uses a string-specific context, while custom `HashMap` users provide their own context:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
@ -409,8 +409,13 @@ const std = @import("std");
|
||||||
// Requires 128-bit key
|
// Requires 128-bit key
|
||||||
const key: [16]u8 = .{0} ** 16;
|
const key: [16]u8 = .{0} ** 16;
|
||||||
|
|
||||||
const h64 = std.hash.SipHash64(2, 4).hash(&key, "data");
|
var sip64 = std.hash.SipHash64(2, 4).init(&key);
|
||||||
const h128 = std.hash.SipHash128(2, 4).hash(&key, "data");
|
sip64.update("data");
|
||||||
|
const h64 = sip64.finalInt();
|
||||||
|
|
||||||
|
var sip128 = std.hash.SipHash128(2, 4).init(&key);
|
||||||
|
sip128.update("data");
|
||||||
|
const h128 = sip128.finalInt();
|
||||||
|
|
||||||
// Default parameters (2-4 rounds)
|
// Default parameters (2-4 rounds)
|
||||||
const SipHash = std.hash.SipHash64(2, 4);
|
const SipHash = std.hash.SipHash64(2, 4);
|
||||||
|
|
@ -440,17 +445,19 @@ fn hashPair(comptime T: type, a: T, b: T) u64 {
|
||||||
### File Checksum
|
### File Checksum
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn checksumFile(path: []const u8) !u32 {
|
fn checksumFile(io: std.Io, path: []const u8) !u32 {
|
||||||
const file = try std.fs.cwd().openFile(path, .{});
|
const file = try std.Io.Dir.cwd().openFile(io, path, .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var crc = std.hash.Crc32.init();
|
var crc: std.hash.Crc32 = .init();
|
||||||
var buf: [4096]u8 = undefined;
|
var reader_buf: [4096]u8 = undefined;
|
||||||
|
var chunk: [4096]u8 = undefined;
|
||||||
|
var reader = file.reader(io, &reader_buf);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const n = try file.read(&buf);
|
const n = try reader.interface.readSliceShort(&chunk);
|
||||||
if (n == 0) break;
|
if (n == 0) break;
|
||||||
crc.update(buf[0..n]);
|
crc.update(chunk[0..n]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return crc.final();
|
return crc.final();
|
||||||
|
|
@ -460,15 +467,13 @@ fn checksumFile(path: []const u8) !u32 {
|
||||||
### Bloom Filter Hash
|
### Bloom Filter Hash
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn bloomHashes(data: []const u8, k: usize) []u64 {
|
fn bloomHashes(data: []const u8, hashes: []u64) void {
|
||||||
var hashes: [16]u64 = undefined;
|
|
||||||
const h1 = std.hash.Wyhash.hash(0, data);
|
const h1 = std.hash.Wyhash.hash(0, data);
|
||||||
const h2 = std.hash.Wyhash.hash(h1, data);
|
const h2 = std.hash.Wyhash.hash(h1, data);
|
||||||
|
|
||||||
for (0..k) |i| {
|
for (hashes, 0..) |*hash, i| {
|
||||||
hashes[i] = h1 +% @as(u64, i) *% h2;
|
hash.* = h1 +% @as(u64, i) *% h2;
|
||||||
}
|
}
|
||||||
return hashes[0..k];
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ std.AutoHashMapUnmanaged(KeyType, ValueType) // no stored allocator
|
||||||
std.StringHashMap(ValueType)
|
std.StringHashMap(ValueType)
|
||||||
std.StringHashMapUnmanaged(ValueType)
|
std.StringHashMapUnmanaged(ValueType)
|
||||||
|
|
||||||
// ArrayHashMap - preserves insertion order, fast iteration
|
// ArrayHashMap - Zig 0.16 unmanaged ordered maps
|
||||||
std.ArrayHashMap(K, V, Context, store_hash)
|
std.array_hash_map.Auto(K, V)
|
||||||
std.StringArrayHashMap(V)
|
std.array_hash_map.String(V)
|
||||||
|
std.array_hash_map.Custom(K, V, Context, store_hash)
|
||||||
```
|
```
|
||||||
|
|
||||||
## AutoHashMap Usage
|
## AutoHashMap Usage
|
||||||
|
|
@ -54,7 +55,7 @@ const n = map.count();
|
||||||
## Unmanaged Variant
|
## Unmanaged Variant
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// No stored allocator - pass to each method
|
// No stored allocator - pass it to operations that allocate or release memory
|
||||||
var map: std.AutoHashMapUnmanaged(u32, []const u8) = .empty;
|
var map: std.AutoHashMapUnmanaged(u32, []const u8) = .empty;
|
||||||
defer map.deinit(allocator);
|
defer map.deinit(allocator);
|
||||||
|
|
||||||
|
|
@ -98,12 +99,16 @@ while (iter.next()) |entry| {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys only
|
// Keys only
|
||||||
for (map.keys()) |key| { }
|
var keys = map.keyIterator();
|
||||||
|
while (keys.next()) |key_ptr| { _ = key_ptr.*; }
|
||||||
|
|
||||||
// Values only
|
// Values only
|
||||||
for (map.values()) |value| { }
|
var values = map.valueIterator();
|
||||||
|
while (values.next()) |value_ptr| { _ = value_ptr.*; }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Any modification invalidates iterators. Growth or rehashing can invalidate returned key/value pointers; removal immediately invalidates the removed entry's pointers.
|
||||||
|
|
||||||
## Capacity
|
## Capacity
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -138,11 +143,11 @@ var map = std.HashMap(MyKey, Value, Context, 80).initContext(allocator, context)
|
||||||
Preserves insertion order, supports indexed access:
|
Preserves insertion order, supports indexed access:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var map = std.StringArrayHashMap(i32).init(allocator);
|
var map: std.array_hash_map.String(i32) = .empty;
|
||||||
defer map.deinit();
|
defer map.deinit(allocator);
|
||||||
|
|
||||||
try map.put("b", 2);
|
try map.put(allocator, "b", 2);
|
||||||
try map.put("a", 1);
|
try map.put(allocator, "a", 1);
|
||||||
|
|
||||||
// Iterate in insertion order: "b", "a"
|
// Iterate in insertion order: "b", "a"
|
||||||
for (map.keys(), map.values()) |k, v| { }
|
for (map.keys(), map.values()) |k, v| { }
|
||||||
|
|
@ -152,10 +157,10 @@ const key = map.keys()[0]; // "b"
|
||||||
const val = map.values()[0]; // 2
|
const val = map.values()[0]; // 2
|
||||||
|
|
||||||
// Swap remove (O(1) but changes order)
|
// Swap remove (O(1) but changes order)
|
||||||
map.swapRemove("b");
|
_ = map.swapRemove("b");
|
||||||
|
|
||||||
// Ordered remove (O(n) but preserves order)
|
// Ordered remove (O(n) but preserves order)
|
||||||
map.orderedRemove("a");
|
_ = map.orderedRemove("a");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
@ -174,8 +179,15 @@ for (words) |word| {
|
||||||
|
|
||||||
// Cache with owned keys
|
// Cache with owned keys
|
||||||
var cache = std.StringHashMap(Data).init(allocator);
|
var cache = std.StringHashMap(Data).init(allocator);
|
||||||
|
defer cache.deinit();
|
||||||
|
defer {
|
||||||
|
var owned_keys = cache.keyIterator();
|
||||||
|
while (owned_keys.next()) |key_ptr| allocator.free(key_ptr.*);
|
||||||
|
}
|
||||||
// When inserting, dupe the key if needed:
|
// When inserting, dupe the key if needed:
|
||||||
const key_copy = try allocator.dupe(u8, external_key);
|
const key_copy = try allocator.dupe(u8, external_key);
|
||||||
errdefer allocator.free(key_copy);
|
errdefer allocator.free(key_copy);
|
||||||
try cache.put(key_copy, data);
|
try cache.put(key_copy, data);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
On removal, free the duplicated key returned by `fetchRemove`. Before clearing or deinitializing the map, iterate and free every remaining owned key as above; the map does not own string storage automatically.
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ var client: std.http.Client = .{
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
```
|
```
|
||||||
|
|
||||||
Older examples below may still show 0.15 client construction. Add `.io = io` and prefer `std.Io.net` patterns in new code.
|
The client borrows both `allocator` and `io`; keep them valid through `client.deinit()`.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [HTTP Client](#http-client)
|
- [HTTP Client](#http-client)
|
||||||
|
|
@ -30,12 +30,13 @@ Older examples below may still show 0.15 client construction. Add `.io = io` and
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
const io = init.io;
|
||||||
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
// Simple GET - response body discarded
|
// Simple GET - response body discarded
|
||||||
|
|
@ -49,7 +50,7 @@ pub fn main() !void {
|
||||||
### Fetch with Response Body
|
### Fetch with Response Body
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
// Create writer to capture response
|
// Create writer to capture response
|
||||||
|
|
@ -84,7 +85,7 @@ const result = try client.fetch(.{
|
||||||
For more control over the request lifecycle:
|
For more control over the request lifecycle:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
const uri = try std.Uri.parse("https://api.example.com/resource");
|
const uri = try std.Uri.parse("https://api.example.com/resource");
|
||||||
|
|
@ -135,7 +136,13 @@ defer req.deinit();
|
||||||
const body = "request body content";
|
const body = "request body content";
|
||||||
try req.sendBodyComplete(@constCast(body));
|
try req.sendBodyComplete(@constCast(body));
|
||||||
|
|
||||||
// Or for streaming:
|
```
|
||||||
|
|
||||||
|
Streaming is a separate request lifecycle; do not call both body-send forms on one request:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
var req = try client.request(.POST, uri, .{});
|
||||||
|
defer req.deinit();
|
||||||
req.transfer_encoding = .{ .content_length = body.len };
|
req.transfer_encoding = .{ .content_length = body.len };
|
||||||
var body_writer_buf: [1024]u8 = undefined;
|
var body_writer_buf: [1024]u8 = undefined;
|
||||||
var bw = try req.sendBody(&body_writer_buf);
|
var bw = try req.sendBody(&body_writer_buf);
|
||||||
|
|
@ -212,7 +219,7 @@ std.debug.print("Final URL: {s}\n", .{req.uri.path.raw});
|
||||||
Connections are automatically pooled and reused:
|
Connections are automatically pooled and reused:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
// Configure pool size (default 32)
|
// Configure pool size (default 32)
|
||||||
|
|
@ -226,20 +233,25 @@ client.write_buffer_size = 2048; // default 1024
|
||||||
for (0..10) |_| {
|
for (0..10) |_| {
|
||||||
var req = try client.request(.GET, uri, .{ .keep_alive = true });
|
var req = try client.request(.GET, uri, .{ .keep_alive = true });
|
||||||
defer req.deinit();
|
defer req.deinit();
|
||||||
// ... same connection reused
|
try req.sendBodiless();
|
||||||
|
var response = try req.receiveHead(&.{});
|
||||||
|
var discard_buf: [1024]u8 = undefined;
|
||||||
|
_ = try response.reader(&discard_buf).discardRemaining();
|
||||||
|
// req.deinit() returns an eligible keep-alive connection to the pool.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Proxy Configuration
|
### Proxy Configuration
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
// Load from environment (HTTP_PROXY, HTTPS_PROXY, etc.)
|
// Load from environment (HTTP_PROXY, HTTPS_PROXY, etc.)
|
||||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
try client.initDefaultProxies(arena.allocator());
|
// `environ_map` and the arena-backed proxy strings must outlive the client.
|
||||||
|
try client.initDefaultProxies(arena.allocator(), &environ_map);
|
||||||
|
|
||||||
// Or configure manually:
|
// Or configure manually:
|
||||||
var proxy: std.http.Proxy = .{
|
var proxy: std.http.Proxy = .{
|
||||||
|
|
@ -255,15 +267,15 @@ client.http_proxy = &proxy;
|
||||||
### TLS Configuration
|
### TLS Configuration
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
// TLS is enabled by default for https://
|
// TLS is enabled by default for https://
|
||||||
// Configure TLS buffer size (affects memory usage)
|
// Configure TLS buffer size (affects memory usage)
|
||||||
client.tls_buffer_size = std.crypto.tls.Client.min_buffer_len;
|
client.tls_buffer_size = std.crypto.tls.Client.min_buffer_len;
|
||||||
|
|
||||||
// Force certificate rescan on next HTTPS request
|
// Force time/root-certificate freshness to be reconsidered on the next HTTPS request.
|
||||||
client.next_https_rescan_certs = true;
|
client.now = null;
|
||||||
|
|
||||||
// Disable TLS at compile time via std.options.http_disable_tls
|
// Disable TLS at compile time via std.options.http_disable_tls
|
||||||
```
|
```
|
||||||
|
|
@ -274,25 +286,26 @@ client.next_https_rescan_certs = true;
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const net = std.net;
|
const net = std.Io.net;
|
||||||
const http = std.http;
|
const http = std.http;
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
const address = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080);
|
const io = init.io;
|
||||||
var tcp_server = try address.listen(.{});
|
const address = try net.IpAddress.parseIp4("127.0.0.1", 8080);
|
||||||
defer tcp_server.deinit();
|
var tcp_server = try address.listen(io, .{});
|
||||||
|
defer tcp_server.deinit(io);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const conn = try tcp_server.accept();
|
const conn = try tcp_server.accept(io);
|
||||||
defer conn.stream.close();
|
defer conn.close(io);
|
||||||
|
|
||||||
var read_buf: [8192]u8 = undefined;
|
var read_buf: [8192]u8 = undefined;
|
||||||
var write_buf: [4096]u8 = undefined;
|
var write_buf: [4096]u8 = undefined;
|
||||||
|
|
||||||
var reader = conn.stream.reader(&read_buf);
|
var reader = conn.reader(io, &read_buf);
|
||||||
var writer = conn.stream.writer(&write_buf);
|
var writer = conn.writer(io, &write_buf);
|
||||||
|
|
||||||
var server = http.Server.init(reader.interface(), &writer.interface);
|
var server = http.Server.init(&reader.interface, &writer.interface);
|
||||||
|
|
||||||
const request = server.receiveHead() catch |err| {
|
const request = server.receiveHead() catch |err| {
|
||||||
std.debug.print("Failed to receive: {}\n", .{err});
|
std.debug.print("Failed to receive: {}\n", .{err});
|
||||||
|
|
@ -479,7 +492,7 @@ const Method = enum {
|
||||||
GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH,
|
GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH,
|
||||||
|
|
||||||
pub fn requestHasBody(m: Method) bool; // POST, PUT, PATCH
|
pub fn requestHasBody(m: Method) bool; // POST, PUT, PATCH
|
||||||
pub fn responseHasBody(m: Method) bool; // GET, POST, DELETE, CONNECT, OPTIONS, PATCH
|
pub fn responseHasBody(m: Method) bool; // GET, POST, PUT, DELETE, CONNECT, OPTIONS, PATCH
|
||||||
pub fn safe(m: Method) bool; // GET, HEAD, OPTIONS, TRACE
|
pub fn safe(m: Method) bool; // GET, HEAD, OPTIONS, TRACE
|
||||||
pub fn idempotent(m: Method) bool; // GET, HEAD, PUT, DELETE, OPTIONS, TRACE
|
pub fn idempotent(m: Method) bool; // GET, HEAD, PUT, DELETE, OPTIONS, TRACE
|
||||||
pub fn cacheable(m: Method) bool; // GET, HEAD
|
pub fn cacheable(m: Method) bool; // GET, HEAD
|
||||||
|
|
@ -569,8 +582,8 @@ const Header = struct {
|
||||||
### JSON API Client
|
### JSON API Client
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn fetchJson(comptime T: type, allocator: Allocator, url: []const u8) !T {
|
fn fetchJson(comptime T: type, io: std.Io, allocator: Allocator, url: []const u8) !std.json.Parsed(T) {
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var body_buf: [65536]u8 = undefined;
|
var body_buf: [65536]u8 = undefined;
|
||||||
|
|
@ -586,19 +599,22 @@ fn fetchJson(comptime T: type, allocator: Allocator, url: []const u8) !T {
|
||||||
|
|
||||||
if (result.status != .ok) return error.HttpError;
|
if (result.status != .ok) return error.HttpError;
|
||||||
|
|
||||||
const parsed = try std.json.parseFromSlice(T, allocator, body_writer.buffered(), .{});
|
// alloc_always prevents returned strings from borrowing body_buf. The
|
||||||
return parsed.value;
|
// caller owns the returned Parsed(T) and must call deinit().
|
||||||
|
return std.json.parseFromSlice(T, allocator, body_writer.buffered(), .{
|
||||||
|
.allocate = .alloc_always,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### POST JSON Data
|
### POST JSON Data
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn postJson(allocator: Allocator, url: []const u8, data: anytype) !void {
|
fn postJson(io: std.Io, allocator: Allocator, url: []const u8, data: anytype) !void {
|
||||||
const json = try std.json.stringifyAlloc(allocator, data, .{});
|
const json = try std.json.stringifyAlloc(allocator, data, .{});
|
||||||
defer allocator.free(json);
|
defer allocator.free(json);
|
||||||
|
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
const result = try client.fetch(.{
|
const result = try client.fetch(.{
|
||||||
|
|
@ -617,8 +633,8 @@ fn postJson(allocator: Allocator, url: []const u8, data: anytype) !void {
|
||||||
### Download File
|
### Download File
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn downloadFile(allocator: Allocator, url: []const u8, path: []const u8) !void {
|
fn downloadFile(io: std.Io, allocator: Allocator, url: []const u8, path: []const u8) !void {
|
||||||
var client: std.http.Client = .{ .allocator = allocator };
|
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
const uri = try std.Uri.parse(url);
|
const uri = try std.Uri.parse(url);
|
||||||
|
|
@ -632,11 +648,11 @@ fn downloadFile(allocator: Allocator, url: []const u8, path: []const u8) !void {
|
||||||
|
|
||||||
if (response.head.status != .ok) return error.HttpError;
|
if (response.head.status != .ok) return error.HttpError;
|
||||||
|
|
||||||
const file = try std.fs.cwd().createFile(path, .{});
|
const file = try std.Io.Dir.cwd().createFile(io, path, .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var file_buf: [4096]u8 = undefined;
|
var file_buf: [4096]u8 = undefined;
|
||||||
var file_writer = file.writer(&file_buf);
|
var file_writer = file.writer(io, &file_buf);
|
||||||
|
|
||||||
var reader_buf: [4096]u8 = undefined;
|
var reader_buf: [4096]u8 = undefined;
|
||||||
const body_reader = response.reader(&reader_buf);
|
const body_reader = response.reader(&reader_buf);
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ while (try reader.takeDelimiter('\n')) |line| {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`takeDelimiter` returns `!?[]u8`: `null` means EOF, not an error.
|
`takeDelimiter` returns `!?[]u8`: `null` means EOF with no buffered bytes remaining. A final unterminated line is returned as data before a later call yields `null`.
|
||||||
|
|
||||||
### File Reader
|
### File Reader
|
||||||
|
|
||||||
|
|
@ -148,6 +148,8 @@ const header = try reader.takeStruct(Header, .little);
|
||||||
const leb = try reader.takeLeb128(u64);
|
const leb = try reader.takeLeb128(u64);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`takeStruct` reads an extern/packed memory representation through the reader buffer; use a layout with a defined byte representation, ensure the reader can supply the full size, and do not treat a native-layout struct as a portable wire format.
|
||||||
|
|
||||||
## File Integration
|
## File Integration
|
||||||
|
|
||||||
Use `std.Io.Dir` and `std.Io.File`.
|
Use `std.Io.Dir` and `std.Io.File`.
|
||||||
|
|
@ -206,11 +208,14 @@ Use `io.randomSecure` for fresh secure entropy with error reporting.
|
||||||
|
|
||||||
## Time
|
## Time
|
||||||
|
|
||||||
The release notes map:
|
Timestamp reads now require an explicit clock choice:
|
||||||
|
|
||||||
- `std.time.Instant` -> `std.Io.Timestamp`
|
```zig
|
||||||
- `std.time.Timer` -> `std.Io.Timestamp`
|
const now = std.Io.Timestamp.now(io, .real);
|
||||||
- `std.time.timestamp` -> `std.Io.Timestamp.now`
|
const elapsed_mark = std.Io.Timestamp.now(io, .awake);
|
||||||
|
```
|
||||||
|
|
||||||
|
`Timestamp` is not a one-for-one replacement for every old `Instant`/`Timer` behavior; choose `.real` versus an appropriate monotonic clock such as `.awake`, and build elapsed-time helpers around that choice.
|
||||||
|
|
||||||
Use a shared application helper when common timestamp reads require consistent clock selection or conversion semantics.
|
Use a shared application helper when common timestamp reads require consistent clock selection or conversion semantics.
|
||||||
|
|
||||||
|
|
@ -220,9 +225,9 @@ Use a shared application helper when common timestamp reads require consistent c
|
||||||
|
|
||||||
- `io.async(...)`
|
- `io.async(...)`
|
||||||
- `std.Io.Group`
|
- `std.Io.Group`
|
||||||
- `std.Io.Select`
|
- `std.Io.Select(U)` where `U` is the tagged union of possible results
|
||||||
- `std.Io.Batch`
|
- `std.Io.Batch`
|
||||||
- `std.Io.Queue(T)`
|
- `std.Io.Queue(Elem)`, initialized with caller-provided typed element storage
|
||||||
|
|
||||||
Cancelation guidance:
|
Cancelation guidance:
|
||||||
|
|
||||||
|
|
@ -250,8 +255,8 @@ Blocking sync moved to `std.Io` equivalents:
|
||||||
| `std.Thread.Semaphore` | `std.Io.Semaphore` |
|
| `std.Thread.Semaphore` | `std.Io.Semaphore` |
|
||||||
| `std.Thread.RwLock` | `std.Io.RwLock` |
|
| `std.Thread.RwLock` | `std.Io.RwLock` |
|
||||||
| `std.Thread.ResetEvent` | `std.Io.Event` |
|
| `std.Thread.ResetEvent` | `std.Io.Event` |
|
||||||
| `std.Thread.WaitGroup` | `std.Io.Group` |
|
| `std.Thread.WaitGroup` | Conceptually `std.Io.Group`; submit tasks, then await or cancel the group |
|
||||||
| `std.Thread.Futex` | `std.Io.Futex` |
|
| `std.Thread.Futex` | `io.futexWait*` / `io.futexWake`; waits have cancelable and uncancelable forms |
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
try mutex.lock(io);
|
try mutex.lock(io);
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ defer allocator.free(json);
|
||||||
|
|
||||||
// To writer
|
// To writer
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var writer = std.fs.File.stdout().writer(&buf);
|
var writer = std.Io.File.stdout().writer(io, &buf);
|
||||||
try std.json.Stringify.value(config, .{}, &writer.interface);
|
try std.json.Stringify.value(config, .{}, &writer.interface);
|
||||||
try writer.interface.flush();
|
try writer.interface.flush();
|
||||||
```
|
```
|
||||||
|
|
@ -184,22 +184,23 @@ pub const Value = union(enum) {
|
||||||
float: f64,
|
float: f64,
|
||||||
number_string: []const u8, // unparsed number
|
number_string: []const u8, // unparsed number
|
||||||
string: []const u8,
|
string: []const u8,
|
||||||
array: Array, // std.ArrayList(Value)
|
array: Array, // std.array_list.Managed(Value)
|
||||||
object: ObjectMap, // StringArrayHashMap(Value)
|
object: ObjectMap, // std.array_hash_map.String(Value)
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building Values Manually
|
### Building Values Manually
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var obj = std.json.ObjectMap.init(allocator);
|
var obj: std.json.ObjectMap = .empty;
|
||||||
try obj.put("name", .{ .string = "test" });
|
defer obj.deinit(allocator);
|
||||||
try obj.put("count", .{ .integer = 42 });
|
try obj.put(allocator, "name", .{ .string = "test" });
|
||||||
|
try obj.put(allocator, "count", .{ .integer = 42 });
|
||||||
|
|
||||||
var arr = std.json.Array.init(allocator);
|
var arr = std.json.Array.init(allocator);
|
||||||
try arr.append(.{ .integer = 1 });
|
try arr.append(.{ .integer = 1 });
|
||||||
try arr.append(.{ .integer = 2 });
|
try arr.append(.{ .integer = 2 });
|
||||||
try obj.put("items", .{ .array = arr });
|
try obj.put(allocator, "items", .{ .array = arr });
|
||||||
|
|
||||||
const value = std.json.Value{ .object = obj };
|
const value = std.json.Value{ .object = obj };
|
||||||
```
|
```
|
||||||
|
|
@ -283,7 +284,7 @@ const Point = struct {
|
||||||
Build JSON incrementally:
|
Build JSON incrementally:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var out: std.io.Writer.Allocating = .init(allocator);
|
var out: std.Io.Writer.Allocating = .init(allocator);
|
||||||
defer out.deinit();
|
defer out.deinit();
|
||||||
|
|
||||||
var jw: std.json.Stringify = .{
|
var jw: std.json.Stringify = .{
|
||||||
|
|
@ -345,14 +346,16 @@ const Config = struct {
|
||||||
debug: bool = false,
|
debug: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config {
|
fn loadConfig(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !Config {
|
||||||
const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
|
const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
|
||||||
error.FileNotFound => return Config{}, // defaults
|
error.FileNotFound => return Config{}, // defaults
|
||||||
else => return err,
|
else => return err,
|
||||||
};
|
};
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
const content = try file.readToEndAlloc(allocator, 1024 * 1024);
|
var read_buf: [4096]u8 = undefined;
|
||||||
|
var reader = file.reader(io, &read_buf);
|
||||||
|
const content = try reader.interface.allocRemaining(allocator, .limited(1024 * 1024));
|
||||||
defer allocator.free(content);
|
defer allocator.free(content);
|
||||||
|
|
||||||
const parsed = try std.json.parseFromSlice(Config, allocator, content, .{
|
const parsed = try std.json.parseFromSlice(Config, allocator, content, .{
|
||||||
|
|
@ -360,7 +363,8 @@ fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config {
|
||||||
});
|
});
|
||||||
defer parsed.deinit();
|
defer parsed.deinit();
|
||||||
|
|
||||||
// Copy strings to owned memory since parsed will be freed
|
// Copy strings to owned memory since parsed and the source buffer are
|
||||||
|
// about to be freed. The caller owns and must free the returned host.
|
||||||
return Config{
|
return Config{
|
||||||
.host = try allocator.dupe(u8, parsed.value.host),
|
.host = try allocator.dupe(u8, parsed.value.host),
|
||||||
.port = parsed.value.port,
|
.port = parsed.value.port,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Intrusive linked lists for O(1) insertion/removal. Nodes are embedded in user st
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
- O(1) insertion/removal anywhere in list
|
- O(1) insertion/removal anywhere in list
|
||||||
- Elements that need to be in multiple lists
|
- Elements that need to be in multiple lists (embed one distinct node per simultaneous list membership)
|
||||||
- Preallocated/arena-allocated nodes
|
- Preallocated/arena-allocated nodes
|
||||||
- No allocation on insert (nodes already exist)
|
- No allocation on insert (nodes already exist)
|
||||||
|
|
||||||
|
|
@ -27,12 +27,13 @@ var list: std.DoublyLinkedList = .{};
|
||||||
var a: Item = .{ .data = 1 };
|
var a: Item = .{ .data = 1 };
|
||||||
var b: Item = .{ .data = 2 };
|
var b: Item = .{ .data = 2 };
|
||||||
var c: Item = .{ .data = 3 };
|
var c: Item = .{ .data = 3 };
|
||||||
|
var d: Item = .{ .data = 4 };
|
||||||
|
|
||||||
// Insert
|
// Insert
|
||||||
list.append(&a.node); // add to end
|
list.append(&a.node); // add to end
|
||||||
list.prepend(&b.node); // add to start
|
list.prepend(&b.node); // add to start
|
||||||
list.insertAfter(&a.node, &c.node); // insert c after a
|
list.insertAfter(&a.node, &c.node); // insert c after a
|
||||||
list.insertBefore(&a.node, &c.node); // insert c before a
|
list.insertBefore(&a.node, &d.node); // insert a different unlinked node before a
|
||||||
|
|
||||||
// Remove
|
// Remove
|
||||||
list.remove(&a.node); // O(1) remove specific node
|
list.remove(&a.node); // O(1) remove specific node
|
||||||
|
|
@ -46,20 +47,26 @@ if (list.first) |node| {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse forward
|
// Traverse forward
|
||||||
var it = list.first;
|
{
|
||||||
while (it) |node| : (it = node.next) {
|
var it = list.first;
|
||||||
|
while (it) |node| : (it = node.next) {
|
||||||
const item: *Item = @fieldParentPtr("node", node);
|
const item: *Item = @fieldParentPtr("node", node);
|
||||||
// use item.data
|
// use item.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse backward
|
// 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);
|
const item: *Item = @fieldParentPtr("node", node);
|
||||||
// use item.data
|
// use item.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concatenate (moves all from list2 to end of list1)
|
// Concatenate (moves all from list2 to end of list1)
|
||||||
|
var list1: std.DoublyLinkedList = .{};
|
||||||
|
var list2: std.DoublyLinkedList = .{};
|
||||||
list1.concatByMoving(&list2);
|
list1.concatByMoving(&list2);
|
||||||
|
|
||||||
// Length (O(n) - consider tracking separately)
|
// Length (O(n) - consider tracking separately)
|
||||||
|
|
@ -85,10 +92,11 @@ var b: Item = .{ .data = 2 };
|
||||||
list.prepend(&a.node); // add to front
|
list.prepend(&a.node); // add to front
|
||||||
a.node.insertAfter(&b.node); // insert b after a
|
a.node.insertAfter(&b.node); // insert b after a
|
||||||
|
|
||||||
// Remove
|
// Remove b after a, then reinsert it so the remaining operations are valid.
|
||||||
const first = list.popFirst(); // remove and return first
|
_ = a.node.removeNext(); // removes and returns b
|
||||||
_ = a.node.removeNext(); // remove node after a
|
list.prepend(&b.node);
|
||||||
list.remove(&b.node); // O(n) - must find predecessor
|
list.remove(&a.node); // O(n) - must find predecessor
|
||||||
|
const first = list.popFirst(); // removes and returns b
|
||||||
|
|
||||||
// Traverse (forward only)
|
// Traverse (forward only)
|
||||||
var it = list.first;
|
var it = list.first;
|
||||||
|
|
@ -122,9 +130,11 @@ node.insertAfter(new_node)
|
||||||
node.removeNext() // ?*Node - removes and returns next
|
node.removeNext() // ?*Node - removes and returns next
|
||||||
node.findLast() // *Node
|
node.findLast() // *Node
|
||||||
node.countChildren() // usize
|
node.countChildren() // usize
|
||||||
node.reverse(&optional_ptr)
|
std.SinglyLinkedList.Node.reverse(&optional_ptr)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Insertion requires a node that is not currently linked into a list, and removal requires membership in that exact list. Removal updates surrounding links and list heads but does not clear every stale `prev`/`next` field on the removed node; reinitializing or deliberately reinserting the node establishes its next valid state.
|
||||||
|
|
||||||
## Common Pattern: LRU Cache
|
## Common Pattern: LRU Cache
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,12 @@ pub fn main() void {
|
||||||
|
|
||||||
| Level | Build Mode Default | Purpose |
|
| Level | Build Mode Default | Purpose |
|
||||||
|-------|-------------------|---------|
|
|-------|-------------------|---------|
|
||||||
| `.err` | Always shown | Something went wrong |
|
| `.err` | Enabled by the default configuration | Something went wrong |
|
||||||
| `.warn` | Always shown | Uncertain if wrong, worth investigating |
|
| `.warn` | Enabled by the default configuration | Uncertain if wrong, worth investigating |
|
||||||
| `.info` | Debug + Release | General program state |
|
| `.info` | Enabled in common default modes | General program state |
|
||||||
| `.debug` | Debug only | Messages only useful for debugging |
|
| `.debug` | Commonly Debug-only by default | Messages only useful for debugging |
|
||||||
|
|
||||||
|
Compile-time level/scope filtering is controlled by `std.options`; a custom `logFn` also decides what its sink actually emits.
|
||||||
|
|
||||||
Default level by build mode:
|
Default level by build mode:
|
||||||
- **Debug**: `.debug` (all messages)
|
- **Debug**: `.debug` (all messages)
|
||||||
|
|
@ -122,14 +124,10 @@ fn myLogFn(
|
||||||
const level_txt = comptime level.asText();
|
const level_txt = comptime level.asText();
|
||||||
const prefix = "[" ++ level_txt ++ "] (" ++ scope_prefix ++ "): ";
|
const prefix = "[" ++ level_txt ++ "] (" ++ scope_prefix ++ "): ";
|
||||||
|
|
||||||
std.debug.lockStdErr();
|
|
||||||
defer std.debug.unlockStdErr();
|
|
||||||
|
|
||||||
const io = applicationIo(); // Application-owned accessor for std.Io.
|
|
||||||
var buf: [64]u8 = undefined;
|
var buf: [64]u8 = undefined;
|
||||||
var stderr = std.Io.File.stderr().writer(io, &buf);
|
const locked = std.debug.lockStderr(&buf);
|
||||||
stderr.interface.print(prefix ++ format ++ "\n", args) catch return;
|
defer std.debug.unlockStderr(); // flushes the returned writer
|
||||||
stderr.interface.flush() catch return;
|
locked.file_writer.interface.print(prefix ++ format ++ "\n", args) catch return;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -148,7 +146,7 @@ fn process() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// For default scope
|
// For default scope
|
||||||
if (std.log.defaultLogEnabled(.debug)) {
|
if (std.log.logEnabled(.debug, .default)) {
|
||||||
std.log.debug("Debug message", .{});
|
std.log.debug("Debug message", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,9 +175,15 @@ fn myLogFn(
|
||||||
comptime format: []const u8,
|
comptime format: []const u8,
|
||||||
args: anytype,
|
args: anytype,
|
||||||
) void {
|
) void {
|
||||||
// Add timestamp, then forward to default
|
// Emit the timestamp and message under one stderr lock so concurrent
|
||||||
std.debug.print("[{d}] ", .{applicationTimestampNow().toNanoseconds()});
|
// records cannot split the prefix from the message.
|
||||||
std.log.defaultLog(level, scope, format, args);
|
var buf: [128]u8 = undefined;
|
||||||
|
const locked = std.debug.lockStderr(&buf);
|
||||||
|
defer std.debug.unlockStderr();
|
||||||
|
locked.file_writer.interface.print(
|
||||||
|
"[{d}] " ++ format ++ "\n",
|
||||||
|
.{applicationTimestampNow().toNanoseconds()} ++ args,
|
||||||
|
) catch return;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -243,8 +247,10 @@ fn fileLogFn(
|
||||||
args: anytype,
|
args: anytype,
|
||||||
) void {
|
) void {
|
||||||
const io = applicationIo();
|
const io = applicationIo();
|
||||||
const file = std.Io.Dir.cwd().openFile(io, "app.log", .{ .mode = .write_only }) catch return;
|
// Returns a long-lived handle opened during startup with an explicit
|
||||||
defer file.close(io);
|
// create/append policy. Plain write_only would require an existing file
|
||||||
|
// and reopening each record could overwrite earlier data.
|
||||||
|
const file = applicationLogFile();
|
||||||
|
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
var writer = file.writer(io, &buf);
|
var writer = file.writer(io, &buf);
|
||||||
|
|
@ -257,3 +263,5 @@ fn fileLogFn(
|
||||||
w.flush() catch return;
|
w.flush() catch return;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If multiple tasks share this sink, serialize the complete record and keep the file/writer alive; opening, seeking, writing, and closing separately for each record is not atomic.
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ const log10_int_val = std.math.log10_int(1000); // 3
|
||||||
```zig
|
```zig
|
||||||
// Powers
|
// Powers
|
||||||
const pow_val = std.math.pow(f64, 2.0, 3.0); // 2^3 = 8.0
|
const pow_val = std.math.pow(f64, 2.0, 3.0); // 2^3 = 8.0
|
||||||
const powi_val = std.math.powi(f64, 2.0, 3); // 2^3 (integer exponent)
|
const powi_val = try std.math.powi(i32, 2, 3); // checked integer power
|
||||||
|
|
||||||
// Roots
|
// Roots
|
||||||
const sqrt_val = std.math.sqrt(@as(f64, 16.0)); // 4.0
|
const sqrt_val = std.math.sqrt(@as(f64, 16.0)); // 4.0
|
||||||
|
|
@ -202,11 +202,13 @@ const negated = std.math.negate(@as(i8, -128)) catch |err| {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Shift with overflow check
|
// Shift with overflow check
|
||||||
const shifted = std.math.shlExact(u8, 1, 8) catch |err| {
|
const shifted = std.math.shlExact(u8, 2, 7) catch |err| {
|
||||||
return err; // Overflow: 1 << 8 doesn't fit in u8
|
return err; // Overflow: 2 << 7 doesn't fit in u8
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The shift amount has type `std.math.Log2Int(T)`, so an out-of-range count such as 8 for `u8` is rejected before `shlExact` can report value overflow.
|
||||||
|
|
||||||
## Division Functions
|
## Division Functions
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -362,13 +364,17 @@ defer b.deinit();
|
||||||
// Arithmetic
|
// Arithmetic
|
||||||
try a.add(&a, &b);
|
try a.add(&a, &b);
|
||||||
try a.mul(&a, &b);
|
try a.mul(&a, &b);
|
||||||
try a.div(&q, &r, &a, &b); // quotient and remainder
|
var q = try Managed.init(allocator);
|
||||||
|
defer q.deinit();
|
||||||
|
var r = try Managed.init(allocator);
|
||||||
|
defer r.deinit();
|
||||||
|
try q.divTrunc(&r, &a, &b); // quotient in q, remainder in r
|
||||||
|
|
||||||
// Comparison
|
// Comparison
|
||||||
const ord = a.order(b); // .lt, .eq, or .gt
|
const ord = a.order(b); // .lt, .eq, or .gt
|
||||||
|
|
||||||
// Convert to primitive (if fits)
|
// Convert to primitive (if fits)
|
||||||
const val = a.to(i128) catch |err| {
|
const val = a.toInt(i128) catch |err| {
|
||||||
// Value doesn't fit in i128
|
// Value doesn't fit in i128
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
@ -386,8 +392,9 @@ try c.setString(10, "123456789012345678901234567890");
|
||||||
const gcd_val = std.math.gcd(@as(u32, 48), @as(u32, 18)); // 6
|
const gcd_val = std.math.gcd(@as(u32, 48), @as(u32, 18)); // 6
|
||||||
|
|
||||||
// Least common multiple
|
// Least common multiple
|
||||||
const lcm_val = try std.math.lcm(@as(u32, 4), @as(u32, 6)); // 12
|
const lcm_val = std.math.lcm(@as(u32, 4), @as(u32, 6)); // 12
|
||||||
// Returns error.Overflow if result doesn't fit
|
// lcm is not an error union; intermediate multiplication follows the selected
|
||||||
|
// integer overflow/safety behavior.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gamma Functions
|
## Gamma Functions
|
||||||
|
|
@ -402,9 +409,9 @@ const lg = std.math.lgamma(f64, 100.0);
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Most functions work with `f16`, `f32`, `f64`, `f80`, `f128` and `comptime_float`
|
- Floating-point support varies by function and target; check each signature rather than assuming every float width and `comptime_float` are accepted
|
||||||
- Many functions support SIMD vectors: `sin(@Vector(4, f32){...})`
|
- Many functions support SIMD vectors: `sin(@Vector(4, f32){...})`
|
||||||
- Integer overflow-checking functions return `error.Overflow` or `error.DivisionByZero`
|
- Integer helpers document their own error sets; `error.Overflow` and `error.DivisionByZero` are common examples, not a universal pair
|
||||||
- Hardware instructions used when available (`@sin`, `@cos`, `@sqrt`, etc.)
|
- Hardware instructions used when available (`@sin`, `@cos`, `@sqrt`, etc.)
|
||||||
- `approxEqAbs` for values near zero, `approxEqRel` for larger values
|
- `approxEqAbs` for values near zero, `approxEqRel` for larger values
|
||||||
- Complex number operations available in `std.math.complex`
|
- Complex number operations available in `std.math.complex`
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ std.mem.cutPrefix(u8, path, "assets/")
|
||||||
std.mem.cutSuffix(u8, file_name, ".zig")
|
std.mem.cutSuffix(u8, file_name, ".zig")
|
||||||
```
|
```
|
||||||
|
|
||||||
Older examples below may use 0.15 `indexOf` names; translate them to the `find`/`cut` APIs in Zig 0.16.
|
The examples below use the Zig 0.16 `find`/`cut` names rather than the older `indexOf` family.
|
||||||
|
|
||||||
|
The legacy `copyForwards`/`copyBackwards` helpers are deprecated; use `@memmove` when source and destination may overlap (or `@memcpy` when they provably do not).
|
||||||
|
|
||||||
## Slice Comparison & Search
|
## Slice Comparison & Search
|
||||||
|
|
||||||
|
|
@ -101,10 +103,12 @@ defer allocator.free(joined); // "a, b, c"
|
||||||
|
|
||||||
// Join with null terminator
|
// Join with null terminator
|
||||||
const joinedZ = try std.mem.joinZ(allocator, "/", &.{ "path", "to", "file" });
|
const joinedZ = try std.mem.joinZ(allocator, "/", &.{ "path", "to", "file" });
|
||||||
|
defer allocator.free(joinedZ);
|
||||||
// [:0]u8 = "path/to/file"
|
// [:0]u8 = "path/to/file"
|
||||||
|
|
||||||
// Concatenate without separator
|
// Concatenate without separator
|
||||||
const concatted = try std.mem.concat(allocator, u8, &.{ "hello", " ", "world" });
|
const concatted = try std.mem.concat(allocator, u8, &.{ "hello", " ", "world" });
|
||||||
|
defer allocator.free(concatted);
|
||||||
// "hello world"
|
// "hello world"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -125,7 +129,8 @@ std.mem.trim(u8, "\n\thello\n\t", " \t\n")
|
||||||
// In-place replace (returns count)
|
// In-place replace (returns count)
|
||||||
var buf: [100]u8 = undefined;
|
var buf: [100]u8 = undefined;
|
||||||
const count = std.mem.replace(u8, "hello", "l", "L", &buf);
|
const count = std.mem.replace(u8, "hello", "l", "L", &buf);
|
||||||
// buf contains "heLLo", count = 2
|
const output_len = std.mem.replacementSize(u8, "hello", "l", "L");
|
||||||
|
const replaced = buf[0..output_len]; // "heLLo"; count = 2
|
||||||
|
|
||||||
// Allocate new slice
|
// Allocate new slice
|
||||||
const result = try std.mem.replaceOwned(u8, allocator, "hello", "l", "L");
|
const result = try std.mem.replaceOwned(u8, allocator, "hello", "l", "L");
|
||||||
|
|
@ -148,18 +153,20 @@ const bytes = std.mem.asBytes(&val); // *const [4]u8
|
||||||
const byte_copy = std.mem.toBytes(val); // [4]u8 (copy)
|
const byte_copy = std.mem.toBytes(val); // [4]u8 (copy)
|
||||||
|
|
||||||
// Bytes to value
|
// Bytes to value
|
||||||
const bytes = [_]u8{ 0xEF, 0xBE, 0xAD, 0xDE };
|
const word_bytes: [4]u8 align(@alignOf(u32)) = .{ 0xEF, 0xBE, 0xAD, 0xDE };
|
||||||
const ptr = std.mem.bytesAsValue(u32, &bytes); // *const u32
|
const ptr = std.mem.bytesAsValue(u32, &word_bytes); // native-memory view retaining input alignment
|
||||||
const val = std.mem.bytesToValue(u32, &bytes); // u32 (copy)
|
const word = std.mem.bytesToValue(u32, &word_bytes); // u32 copy
|
||||||
|
|
||||||
// Slice conversions
|
// Slice conversions
|
||||||
const u16_slice = [_]u16{ 0x0102, 0x0304 };
|
const u16_slice = [_]u16{ 0x0102, 0x0304 };
|
||||||
const u8_slice = std.mem.sliceAsBytes(&u16_slice); // []const u8
|
const u8_slice = std.mem.sliceAsBytes(&u16_slice); // []const u8
|
||||||
|
|
||||||
const u8_data = [_]u8{ 1, 0, 2, 0, 3, 0, 4, 0 };
|
const u8_data: [8]u8 align(@alignOf(u16)) = .{ 1, 0, 2, 0, 3, 0, 4, 0 };
|
||||||
const u16_view = std.mem.bytesAsSlice(u16, &u8_data); // []const u16
|
const u16_view = std.mem.bytesAsSlice(u16, &u8_data); // []const u16
|
||||||
```
|
```
|
||||||
|
|
||||||
|
These are native-memory views: alignment follows the input pointer and integer byte order follows the target. Use `readInt`/`writeInt` for portable byte-order decoding.
|
||||||
|
|
||||||
## Alignment
|
## Alignment
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -182,8 +189,9 @@ std.mem.isValidAlign(3) // false
|
||||||
const ptr: [*]u8 = @ptrFromInt(0x123);
|
const ptr: [*]u8 = @ptrFromInt(0x123);
|
||||||
const aligned = std.mem.alignPointer(ptr, 0x100); // ?[*]u8 = 0x200
|
const aligned = std.mem.alignPointer(ptr, 0x100); // ?[*]u8 = 0x200
|
||||||
|
|
||||||
// Find aligned slice within bytes
|
// Find aligned mutable slice within bytes
|
||||||
const aligned_slice = std.mem.alignInBytes(bytes, 16); // ?[]align(16) u8
|
var storage: [128]u8 = undefined;
|
||||||
|
const aligned_slice = std.mem.alignInBytes(&storage, 16); // ?[]align(16) u8
|
||||||
```
|
```
|
||||||
|
|
||||||
## Alignment Type
|
## Alignment Type
|
||||||
|
|
@ -208,14 +216,14 @@ const ok = align_val.check(0x100); // true if aligned
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// To/from native endianness
|
// To/from native endianness
|
||||||
const native = std.mem.littleToNative(u32, 0x12345678);
|
const native_from_little = std.mem.littleToNative(u32, 0x12345678);
|
||||||
const native = std.mem.bigToNative(u32, 0x12345678);
|
const native_from_big = std.mem.bigToNative(u32, 0x12345678);
|
||||||
const little = std.mem.nativeToLittle(u32, native_val);
|
const little = std.mem.nativeToLittle(u32, native_val);
|
||||||
const big = std.mem.nativeToBig(u32, native_val);
|
const big = std.mem.nativeToBig(u32, native_val);
|
||||||
|
|
||||||
// General conversion
|
// General conversion
|
||||||
const val = std.mem.toNative(u32, x, .little); // from little to native
|
const decoded = std.mem.toNative(u32, x, .little); // from little to native
|
||||||
const val = std.mem.nativeTo(u32, x, .big); // from native to big
|
const encoded = std.mem.nativeTo(u32, x, .big); // from native to big
|
||||||
|
|
||||||
// Byte swap all fields in struct
|
// Byte swap all fields in struct
|
||||||
std.mem.byteSwapAllFields(MyStruct, &my_struct);
|
std.mem.byteSwapAllFields(MyStruct, &my_struct);
|
||||||
|
|
@ -255,6 +263,8 @@ const partial = std.mem.zeroInit(MyStruct, .{
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Prefer explicit field initialization. `zeroes` is mainly for types whose all-zero state is intentionally valid (often C interop); in review, verify that every enum, pointer-like field, invariant, and nested type has a meaningful zero state.
|
||||||
|
|
||||||
## Min/Max
|
## Min/Max
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -264,9 +274,9 @@ std.mem.min(i32, &slice) // 1
|
||||||
std.mem.max(i32, &slice) // 5
|
std.mem.max(i32, &slice) // 5
|
||||||
std.mem.minMax(i32, &slice) // .{ 1, 5 }
|
std.mem.minMax(i32, &slice) // .{ 1, 5 }
|
||||||
|
|
||||||
std.mem.indexOfMin(i32, &slice) // 1
|
std.mem.findMin(i32, &slice) // 1
|
||||||
std.mem.indexOfMax(i32, &slice) // 4
|
std.mem.findMax(i32, &slice) // 4
|
||||||
std.mem.indexOfMinMax(i32, &slice) // .{ 1, 4 }
|
std.mem.findMinMax(i32, &slice) // .{ 1, 4 }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reverse & Rotate
|
## Reverse & Rotate
|
||||||
|
|
@ -300,7 +310,7 @@ const len = std.mem.len(c_string); // length of null-terminated string
|
||||||
const slice = std.mem.span(c_string);
|
const slice = std.mem.span(c_string);
|
||||||
|
|
||||||
// Index of first difference
|
// Index of first difference
|
||||||
std.mem.indexOfDiff(u8, "hello", "helps") // ?usize = 3
|
std.mem.findDiff(u8, "hello", "helps") // ?usize = 3
|
||||||
|
|
||||||
// Collapse repeated elements
|
// Collapse repeated elements
|
||||||
var data = "aabbcc".*;
|
var data = "aabbcc".*;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ Comptime type introspection and manipulation utilities. Essential for generic pr
|
||||||
| Function | Purpose |
|
| Function | Purpose |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| `stringToEnum(T, str)` | Convert string to enum variant |
|
| `stringToEnum(T, str)` | Convert string to enum variant |
|
||||||
| `fields(T)` | Get struct/union/enum/error fields |
|
| `fields(T)` | Get struct/union/enum or concrete error-set fields |
|
||||||
| `fieldNames(T)` | Get field names as string slice |
|
| `fieldNames(T)` | Get field names as string slice |
|
||||||
| `fieldInfo(T, field)` | Get info for specific field |
|
| `fieldInfo(T, field)` | Get info for specific field |
|
||||||
| `fieldIndex(T, name)` | Get field index by name |
|
| `fieldIndex(T, name)` | Get field index by name |
|
||||||
|
|
@ -71,6 +71,8 @@ const MyError = error{ NotFound, Timeout };
|
||||||
const error_fields = std.meta.fields(MyError);
|
const error_fields = std.meta.fields(MyError);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`fields` cannot enumerate the global `anyerror` set; use it only with a concrete error set whose members are known.
|
||||||
|
|
||||||
### Get Field Names
|
### Get Field Names
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -153,6 +155,8 @@ inline for (std.meta.tags(PointField)) |field| {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For a tagged union, `FieldEnum` reuses the existing tag enum only when field names match in order and the tag values are consecutive from zero; otherwise it constructs a separate enum.
|
||||||
|
|
||||||
For tagged unions, returns the existing tag type if compatible.
|
For tagged unions, returns the existing tag type if compatible.
|
||||||
|
|
||||||
### DeclEnum - Generate Enum from Declarations
|
### DeclEnum - Generate Enum from Declarations
|
||||||
|
|
@ -170,8 +174,8 @@ const ApiMethod = std.meta.DeclEnum(Api);
|
||||||
### Int/Float Type Construction
|
### Int/Float Type Construction
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const U24 = std.meta.Int(.unsigned, 24); // u24
|
const U24 = @Int(.unsigned, 24); // u24
|
||||||
const I7 = std.meta.Int(.signed, 7); // i7
|
const I7 = @Int(.signed, 7); // i7
|
||||||
const F32 = std.meta.Float(32); // f32
|
const F32 = std.meta.Float(32); // f32
|
||||||
const F16 = std.meta.Float(16); // f16
|
const F16 = std.meta.Float(16); // f16
|
||||||
```
|
```
|
||||||
|
|
@ -180,7 +184,7 @@ const F16 = std.meta.Float(16); // f16
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// From type array
|
// From type array
|
||||||
const T1 = std.meta.Tuple(&.{ u32, f32, bool });
|
const T1 = std.meta.Tuple(&.{ u32, f32, bool }); // deprecated compatibility helper
|
||||||
// Equivalent to: struct { u32, f32, bool }
|
// Equivalent to: struct { u32, f32, bool }
|
||||||
|
|
||||||
// From function signature
|
// From function signature
|
||||||
|
|
@ -213,9 +217,9 @@ std.meta.Elem(?[*]u8) // u8 (through optional)
|
||||||
### Sentinel
|
### Sentinel
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
std.meta.sentinel([:0]u8) // @as(u8, 0)
|
const slice_sentinel = std.meta.sentinel([:0]u8).?; // @as(u8, 0)
|
||||||
std.meta.sentinel([*:0]u8) // @as(u8, 0)
|
const ptr_sentinel = std.meta.sentinel([*:0]u8).?; // @as(u8, 0)
|
||||||
std.meta.sentinel([5:0]u8) // @as(u8, 0)
|
const array_sentinel = std.meta.sentinel([5:0]u8).?; // @as(u8, 0)
|
||||||
std.meta.sentinel([]u8) // null
|
std.meta.sentinel([]u8) // null
|
||||||
std.meta.sentinel([5]u8) // null
|
std.meta.sentinel([5]u8) // null
|
||||||
```
|
```
|
||||||
|
|
@ -259,6 +263,8 @@ std.meta.eql(&p1, &p1) // true
|
||||||
|
|
||||||
**Supported types:** structs, arrays, vectors, optionals, error unions, tagged unions, primitives.
|
**Supported types:** structs, arrays, vectors, optionals, error unions, tagged unions, primitives.
|
||||||
|
|
||||||
|
Non-packed untagged unions cannot be compared generically because there is no active tag to select a field. Packed unions are the exception handled as their packed representation.
|
||||||
|
|
||||||
**Not supported:** untagged unions (compile error).
|
**Not supported:** untagged unions (compile error).
|
||||||
|
|
||||||
## Type Queries
|
## Type Queries
|
||||||
|
|
@ -333,13 +339,17 @@ const decls = std.meta.declarations(S);
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Check if value is error (deprecated: use std.enums.fromInt)
|
// Inspect an error union with ordinary error-union syntax.
|
||||||
const result = std.math.divTrunc(u8, 5, 0);
|
const result = std.math.divTrunc(u8, 5, 0);
|
||||||
std.meta.isError(result) // true
|
if (result) |value| {
|
||||||
|
_ = value;
|
||||||
|
} else |err| {
|
||||||
|
_ = err;
|
||||||
|
}
|
||||||
|
|
||||||
// Enum from int (deprecated: use std.enums.fromInt)
|
// Checked enum conversion returns an optional.
|
||||||
const Color = enum { red, green, blue };
|
const Color = enum { red, green, blue };
|
||||||
const c = std.meta.intToEnum(Color, 1) catch unreachable; // Color.green
|
const c = std.enums.fromInt(Color, 1) orelse return error.InvalidColor;
|
||||||
```
|
```
|
||||||
|
|
||||||
## TrailerFlags
|
## TrailerFlags
|
||||||
|
|
@ -349,11 +359,12 @@ Memory-efficient optional field storage using bit flags:
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const Flags = std.meta.TrailerFlags(struct {
|
const Trailer = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
age: u32,
|
age: u32,
|
||||||
email: []const u8,
|
email: []const u8,
|
||||||
});
|
};
|
||||||
|
const Flags = std.meta.TrailerFlags(Trailer);
|
||||||
|
|
||||||
// Initialize with some fields active
|
// Initialize with some fields active
|
||||||
var flags = Flags.init(.{
|
var flags = Flags.init(.{
|
||||||
|
|
@ -364,7 +375,7 @@ var flags = Flags.init(.{
|
||||||
|
|
||||||
// Allocate only needed space
|
// Allocate only needed space
|
||||||
const size = flags.sizeInBytes();
|
const size = flags.sizeInBytes();
|
||||||
const data = try allocator.alignedAlloc(u8, @alignOf(@TypeOf(flags).Fields), size);
|
const data = try allocator.alignedAlloc(u8, .of(Trailer), size);
|
||||||
defer allocator.free(data);
|
defer allocator.free(data);
|
||||||
|
|
||||||
// Set values
|
// Set values
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# std.MultiArrayList
|
# std.MultiArrayList
|
||||||
|
|
||||||
Struct-of-Arrays container for cache-efficient struct storage. Stores each field in a separate contiguous array, reducing padding overhead and improving cache locality when accessing individual fields.
|
Struct-of-Arrays container for cache-efficient struct storage. Stores each field in a separate contiguous array, reducing padding overhead and improving cache locality when accessing individual fields. The element type must be a struct or tagged union; untagged unions are not supported.
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ const n = list.len;
|
||||||
When accessing multiple fields, use `slice()` to compute pointers once:
|
When accessing multiple fields, use `slice()` to compute pointers once:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const slices = list.slice();
|
var slices = list.slice();
|
||||||
|
|
||||||
// Now access fields without recomputing offsets
|
// Now access fields without recomputing offsets
|
||||||
for (slices.items(.id), slices.items(.score)) |id, score| {
|
for (slices.items(.id), slices.items(.score)) |id, score| {
|
||||||
|
|
@ -75,8 +75,9 @@ list.swapRemove(index);
|
||||||
// O(n) but preserves order
|
// O(n) but preserves order
|
||||||
list.orderedRemove(index);
|
list.orderedRemove(index);
|
||||||
|
|
||||||
// Remove multiple indices (must be sorted ascending)
|
// Remove multiple in-bounds indices from the pre-removal list. They must be
|
||||||
list.orderedRemoveMany(&.{ 1, 5, 7, 9 });
|
// sorted ascending; duplicates are allowed and count as one removed element.
|
||||||
|
list.orderedRemoveMany(&.{ 0, 1 });
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tagged Union Support
|
## Tagged Union Support
|
||||||
|
|
@ -96,7 +97,7 @@ try values.append(allocator, .{ .float = 3.14 });
|
||||||
|
|
||||||
// Access tags and data separately
|
// Access tags and data separately
|
||||||
const tags = values.items(.tags); // []meta.Tag(Value)
|
const tags = values.items(.tags); // []meta.Tag(Value)
|
||||||
const data = values.items(.data); // []Value.Bare (untagged union)
|
const data = values.items(.data); // slice of internal payload-only union storage
|
||||||
|
|
||||||
// Reconstruct full union
|
// Reconstruct full union
|
||||||
const full = values.get(0); // Value{ .int = 42 }
|
const full = values.get(0); // Value{ .int = 42 }
|
||||||
|
|
@ -132,6 +133,11 @@ list.clearAndFree(allocator);
|
||||||
## Clone and Transfer
|
## Clone and Transfer
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const copy = try list.clone(allocator);
|
var copy = try list.clone(allocator);
|
||||||
const owned_slice = list.toOwnedSlice(); // empties list, caller owns
|
defer copy.deinit(allocator);
|
||||||
|
|
||||||
|
var owned_slice = list.toOwnedSlice(); // empties list; Slice now owns storage
|
||||||
|
defer owned_slice.deinit(allocator);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A cached `Slice` contains derived field pointers and length metadata. Operations on the original list that grow, reallocate, remove, resize, or transfer storage can make that cached metadata stale; reacquire `list.slice()` after mutations. A successful growth can also invalidate previously returned field pointers.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
# std.Io.net Reference (Zig 0.16.0)
|
# std.Io.net Reference (Zig 0.16.0)
|
||||||
|
|
||||||
Cross-platform networking abstractions for TCP/IP connections, address handling, and DNS resolution.
|
Cross-platform networking abstractions for IP connections, address handling, DNS resolution, Unix-domain sockets, and lower-level socket operations. Zig 0.16 exposes these APIs under `std.Io.net`; there is no root `std.net` module.
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
Zig 0.16 moves networking under `std.Io.net`. New networking code should accept/use `std.Io`, and stream/socket close/read/write operations should use the `io` parameter. Older `std.net` examples below are historical; translate them to `std.Io.net` before using them in new code.
|
Networking operations take an explicit `std.Io`. Readers, writers, servers, sockets, and streams also use that `io` value for construction or cleanup.
|
||||||
|
|
||||||
High-level HTTP clients also store `io`:
|
High-level HTTP clients store it directly:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var client: std.http.Client = .{
|
var client: std.http.Client = .{
|
||||||
|
|
@ -17,626 +17,550 @@ defer client.deinit();
|
||||||
```
|
```
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [TCP Client](#tcp-client)
|
|
||||||
- [TCP Server](#tcp-server)
|
- [API Map](#api-map)
|
||||||
- [Address Types](#address-types)
|
- [TCP Clients](#tcp-clients)
|
||||||
|
- [TCP Servers](#tcp-servers)
|
||||||
|
- [IP Address Types](#ip-address-types)
|
||||||
- [Stream I/O](#stream-io)
|
- [Stream I/O](#stream-io)
|
||||||
- [DNS Resolution](#dns-resolution)
|
- [DNS and Host Names](#dns-and-host-names)
|
||||||
- [Unix Sockets](#unix-sockets)
|
- [Unix-Domain Sockets](#unix-domain-sockets)
|
||||||
|
- [Sockets and Datagram APIs](#sockets-and-datagram-apis)
|
||||||
- [Common Patterns](#common-patterns)
|
- [Common Patterns](#common-patterns)
|
||||||
|
- [Error Sets](#error-sets)
|
||||||
|
|
||||||
## TCP Client
|
## API Map
|
||||||
|
|
||||||
### Connect by Hostname
|
```zig
|
||||||
|
const net = std.Io.net;
|
||||||
|
|
||||||
|
net.IpAddress // tagged union: .ip4 or .ip6
|
||||||
|
net.Ip4Address // value-oriented IPv4 bytes + port
|
||||||
|
net.Ip6Address // IPv6 bytes + port + flow + interface
|
||||||
|
net.HostName // validated DNS host name and lookup/connect APIs
|
||||||
|
net.UnixAddress // Unix-domain socket path
|
||||||
|
net.Socket // open socket plus resolved/bound address
|
||||||
|
net.Stream // reliable connected byte stream
|
||||||
|
net.Server // listening socket
|
||||||
|
net.Protocol // tcp, udp, and other protocol identifiers
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `IpAddress.connect` when an IP is already known. Use `HostName.connect` when DNS resolution and address fallback are required.
|
||||||
|
|
||||||
|
## TCP Clients
|
||||||
|
|
||||||
|
### Connect by Host Name
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const net = std.net;
|
const net = std.Io.net;
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
const io = init.io;
|
||||||
defer _ = gpa.deinit();
|
const host: net.HostName = try .init("example.com");
|
||||||
const allocator = gpa.allocator();
|
|
||||||
|
|
||||||
// Connect to host:port (handles DNS resolution)
|
const stream = try host.connect(io, 80, .{
|
||||||
const stream = try net.tcpConnectToHost(allocator, "example.com", 80);
|
.mode = .stream,
|
||||||
defer stream.close();
|
.protocol = .tcp,
|
||||||
|
});
|
||||||
|
defer stream.close(io);
|
||||||
|
|
||||||
// Create buffered reader/writer
|
|
||||||
var read_buf: [4096]u8 = undefined;
|
var read_buf: [4096]u8 = undefined;
|
||||||
var write_buf: [1024]u8 = undefined;
|
var write_buf: [1024]u8 = undefined;
|
||||||
|
var reader = stream.reader(io, &read_buf);
|
||||||
|
var writer = stream.writer(io, &write_buf);
|
||||||
|
|
||||||
var reader = stream.reader(&read_buf);
|
try writer.interface.writeAll(
|
||||||
var writer = stream.writer(&write_buf);
|
"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n",
|
||||||
|
);
|
||||||
// Write request
|
|
||||||
try writer.interface.writeAll("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
|
|
||||||
try writer.interface.flush();
|
try writer.interface.flush();
|
||||||
|
|
||||||
// Read response
|
while (reader.interface.take(4096)) |chunk| {
|
||||||
while (reader.interface().take(4096)) |chunk| {
|
|
||||||
std.debug.print("{s}", .{chunk});
|
std.debug.print("{s}", .{chunk});
|
||||||
} else |err| switch (err) {
|
} else |err| switch (err) {
|
||||||
error.EndOfStream => {},
|
error.EndOfStream => {},
|
||||||
else => return err,
|
error.ReadFailed => return reader.err.?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connect by Address
|
`HostName.connect` performs lookup and races/falls back across returned addresses. The host's bytes are externally owned, so keep their backing storage alive for the call.
|
||||||
|
|
||||||
|
### Connect to a Parsed Address
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Parse and connect to IP address directly (no DNS)
|
const address = try net.IpAddress.parseIp4("192.168.1.1", 8080);
|
||||||
const address = try net.Address.parseIp4("192.168.1.1", 8080);
|
const stream = try address.connect(io, .{
|
||||||
const stream = try net.tcpConnectToAddress(address);
|
.mode = .stream,
|
||||||
defer stream.close();
|
.protocol = .tcp,
|
||||||
|
});
|
||||||
|
defer stream.close(io);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connect with IPv6
|
### IPv6 and Scoped IPv6
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// IPv6 address
|
// Pure parsing: no interface-name scope lookup.
|
||||||
const addr6 = try net.Address.parseIp6("::1", 8080);
|
const loopback = try net.IpAddress.parseIp6("::1", 8080);
|
||||||
const stream = try net.tcpConnectToAddress(addr6);
|
const stream = try loopback.connect(io, .{ .mode = .stream, .protocol = .tcp });
|
||||||
defer stream.close();
|
defer stream.close(io);
|
||||||
|
|
||||||
// IPv6 with scope ID (link-local)
|
// Resolving `%eth0` / `%eno1` requires Io because the interface name must be
|
||||||
const link_local = try net.Address.resolveIp6("fe80::1%eth0", 8080);
|
// converted to an operating-system interface index.
|
||||||
|
const link_local = try net.IpAddress.resolveIp6(io, "fe80::1%eth0", 8080);
|
||||||
```
|
```
|
||||||
|
|
||||||
## TCP Server
|
### Connection Timeout
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const host: net.HostName = try .init("example.com");
|
||||||
|
const stream = try host.connect(io, 443, .{
|
||||||
|
.mode = .stream,
|
||||||
|
.protocol = .tcp,
|
||||||
|
.timeout = .{ .duration = .fromSeconds(5) },
|
||||||
|
});
|
||||||
|
defer stream.close(io);
|
||||||
|
```
|
||||||
|
|
||||||
|
Timeouts can be `.none`, a relative `.duration`, or an absolute `.deadline`.
|
||||||
|
|
||||||
|
## TCP Servers
|
||||||
|
|
||||||
### Basic Server
|
### Basic Server
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const net = std.net;
|
const net = std.Io.net;
|
||||||
|
|
||||||
pub fn main() !void {
|
fn serve(io: std.Io) !void {
|
||||||
// Create address to listen on
|
const address: net.IpAddress = .{ .ip4 = .unspecified(8080) };
|
||||||
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080);
|
var server = try address.listen(io, .{
|
||||||
|
|
||||||
// Start listening
|
|
||||||
var server = try address.listen(.{
|
|
||||||
.reuse_address = true,
|
.reuse_address = true,
|
||||||
|
.kernel_backlog = 256,
|
||||||
});
|
});
|
||||||
defer server.deinit();
|
defer server.deinit(io);
|
||||||
|
|
||||||
std.debug.print("Listening on port {d}\n", .{server.listen_address.getPort()});
|
std.debug.print("Listening on port {d}\n", .{server.socket.address.getPort()});
|
||||||
|
|
||||||
// Accept loop
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const conn = try server.accept();
|
const client = try server.accept(io);
|
||||||
defer conn.stream.close();
|
defer client.close(io);
|
||||||
|
try handleClient(io, client);
|
||||||
// Handle connection
|
|
||||||
try handleClient(conn.stream, conn.address);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleClient(stream: net.Stream, client_addr: net.Address) !void {
|
fn handleClient(io: std.Io, stream: net.Stream) !void {
|
||||||
var read_buf: [4096]u8 = undefined;
|
var read_buf: [4096]u8 = undefined;
|
||||||
var write_buf: [1024]u8 = undefined;
|
var write_buf: [1024]u8 = undefined;
|
||||||
|
var reader = stream.reader(io, &read_buf);
|
||||||
|
var writer = stream.writer(io, &write_buf);
|
||||||
|
|
||||||
var reader = stream.reader(&read_buf);
|
const request_line = reader.interface.takeDelimiter('\n') catch |err| switch (err) {
|
||||||
var writer = stream.writer(&write_buf);
|
error.ReadFailed => return reader.err.?,
|
||||||
|
error.StreamTooLong => return error.RequestLineTooLong,
|
||||||
// Read request
|
|
||||||
const request = reader.interface().takeDelimiter('\n') catch |err| switch (err) {
|
|
||||||
error.EndOfStream => return,
|
|
||||||
else => return err,
|
|
||||||
} orelse return;
|
} orelse return;
|
||||||
|
std.debug.print("Request from {f}: {s}\n", .{ stream.socket.address, request_line });
|
||||||
|
|
||||||
std.debug.print("Request from client: {s}\n", .{request});
|
|
||||||
|
|
||||||
// Send response
|
|
||||||
try writer.interface.writeAll("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello");
|
try writer.interface.writeAll("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello");
|
||||||
try writer.interface.flush();
|
try writer.interface.flush();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`Server.accept(io)` returns a `Stream`, not a separate connection wrapper. The accepted stream contains its socket and address.
|
||||||
|
|
||||||
### Listen Options
|
### Listen Options
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const server = try address.listen(.{
|
const server = try address.listen(io, .{
|
||||||
// Allow address reuse (SO_REUSEADDR + SO_REUSEPORT on POSIX)
|
.kernel_backlog = 128,
|
||||||
.reuse_address = true,
|
.reuse_address = true,
|
||||||
|
.mode = .stream,
|
||||||
// Connection backlog (default 128)
|
.protocol = .tcp,
|
||||||
.kernel_backlog = 256,
|
|
||||||
|
|
||||||
// Non-blocking accept (O_NONBLOCK)
|
|
||||||
.force_nonblocking = false,
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Server on Any Available Port
|
`IpAddress.ListenOptions` contains exactly `kernel_backlog`, `reuse_address`, `mode`, and `protocol`. It does not contain the older `force_nonblocking` field.
|
||||||
|
|
||||||
|
### Ephemeral Port
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Listen on port 0 to let OS assign an available port
|
const address: net.IpAddress = .{ .ip4 = .loopback(0) };
|
||||||
const address = net.Address.initIp4(.{ 127, 0, 0, 1 }, 0);
|
var server = try address.listen(io, .{});
|
||||||
var server = try address.listen(.{});
|
defer server.deinit(io);
|
||||||
defer server.deinit();
|
|
||||||
|
|
||||||
// Get the assigned port
|
const assigned_port = server.socket.address.getPort();
|
||||||
const port = server.listen_address.getPort();
|
|
||||||
std.debug.print("Listening on port {d}\n", .{port});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Address Types
|
The resolved port is stored on `server.socket.address`; there is no `listen_address` field.
|
||||||
|
|
||||||
### Address Union
|
## IP Address Types
|
||||||
|
|
||||||
|
### Tagged Union
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub const Address = extern union {
|
pub const IpAddress = union(enum) {
|
||||||
any: posix.sockaddr,
|
ip4: Ip4Address,
|
||||||
in: Ip4Address,
|
ip6: Ip6Address,
|
||||||
in6: Ip6Address,
|
|
||||||
un: posix.sockaddr.un, // Unix socket (if supported)
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creating Addresses
|
This is a value-oriented union, not an extern `sockaddr` overlay. OS-specific socket-address conversion is handled below this API.
|
||||||
|
|
||||||
|
### Construction
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// IPv4 from bytes
|
const loopback4: net.IpAddress = .{ .ip4 = .loopback(8080) };
|
||||||
const addr4 = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080);
|
const any4: net.IpAddress = .{ .ip4 = .unspecified(8080) };
|
||||||
|
const loopback6: net.IpAddress = .{ .ip6 = .loopback(8080) };
|
||||||
|
const any6: net.IpAddress = .{ .ip6 = .unspecified(8080) };
|
||||||
|
|
||||||
// IPv6 from bytes
|
const explicit4: net.IpAddress = .{ .ip4 = .{
|
||||||
const addr6 = net.Address.initIp6(
|
.bytes = .{ 127, 0, 0, 1 },
|
||||||
.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, // ::1
|
.port = 8080,
|
||||||
8080, // port
|
} };
|
||||||
0, // flowinfo
|
|
||||||
0, // scope_id
|
|
||||||
);
|
|
||||||
|
|
||||||
// Unix socket
|
|
||||||
const unix = try net.Address.initUnix("/tmp/my.sock");
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Parsing Addresses
|
`Ip6Address` also has `flow: u32 = 0` and `interface: net.Interface = .none` fields.
|
||||||
|
|
||||||
|
### Parsing
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Parse IPv4
|
const addr4 = try net.IpAddress.parseIp4("192.168.1.1", 8080);
|
||||||
const addr4 = try net.Address.parseIp4("192.168.1.1", 8080);
|
const addr6 = try net.IpAddress.parseIp6("2001:db8::1", 8080);
|
||||||
|
const either = try net.IpAddress.parse("::1", 8080);
|
||||||
|
|
||||||
// Parse IPv6
|
// Address plus optional port. IPv6 must be bracketed.
|
||||||
const addr6 = try net.Address.parseIp6("2001:db8::1", 8080);
|
const literal4 = try net.IpAddress.parseLiteral("192.168.1.1:8080");
|
||||||
|
const literal6 = try net.IpAddress.parseLiteral("[2001:db8::1]:8080");
|
||||||
|
|
||||||
// Parse either (tries IPv4 first, then IPv6)
|
// Handles an IPv6 interface-name scope and therefore requires Io.
|
||||||
const addr = try net.Address.parseIp("::1", 8080);
|
const scoped = try net.IpAddress.resolve(io, "fe80::1%eth0", 8080);
|
||||||
|
|
||||||
// Parse IP:port format
|
|
||||||
// IPv4: "192.168.1.1:8080"
|
|
||||||
// IPv6: "[::1]:8080" (brackets required)
|
|
||||||
const addr_port = try net.Address.parseIpAndPort("[::1]:8080");
|
|
||||||
|
|
||||||
// Resolve with interface lookup (for link-local IPv6)
|
|
||||||
const resolved = try net.Address.resolveIp6("fe80::1%eth0", 8080);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Address Methods
|
`parseLiteral` uses port zero when no port is present. `parse` accepts an explicit port and tries IPv4, then IPv6. `resolve` adds scoped-IPv6 interface lookup.
|
||||||
|
|
||||||
|
### Methods and Formatting
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var addr = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080);
|
var address = try net.IpAddress.parseIp4("127.0.0.1", 8080);
|
||||||
|
const port = address.getPort();
|
||||||
|
address.setPort(9090);
|
||||||
|
|
||||||
// Get/set port (native endian)
|
const same = address.eql(&other_address);
|
||||||
const port = addr.getPort(); // 8080
|
|
||||||
addr.setPort(9090);
|
|
||||||
|
|
||||||
// Get socket length for syscalls
|
var buf: [128]u8 = undefined;
|
||||||
const socklen = addr.getOsSockLen();
|
|
||||||
|
|
||||||
// Compare addresses
|
|
||||||
if (addr.eql(other_addr)) {
|
|
||||||
// addresses match
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format for printing
|
|
||||||
var buf: [64]u8 = undefined;
|
|
||||||
var writer: std.Io.Writer = .fixed(&buf);
|
var writer: std.Io.Writer = .fixed(&buf);
|
||||||
try addr.format(&writer);
|
try address.format(&writer); // omits an IPv6 interface-name scope
|
||||||
const formatted = writer.buffered(); // "127.0.0.1:8080"
|
try address.formatResolved(io, &writer); // includes a resolvable IPv6 scope
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ip4Address
|
`Ip4Address.format` and `Ip6Address.format` include the native-endian port. `IpAddress.fromIp6` converts IPv4-mapped IPv6 addresses back to `.ip4` where possible.
|
||||||
|
|
||||||
|
### IPv4 Parse Errors
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const Ip4Address = extern struct {
|
pub const Ip4Address.ParseError = error{
|
||||||
sa: posix.sockaddr.in,
|
Overflow,
|
||||||
|
InvalidEnd,
|
||||||
pub fn parse(buf: []const u8, port: u16) !Ip4Address;
|
InvalidCharacter,
|
||||||
pub fn init(addr: [4]u8, port: u16) Ip4Address;
|
Incomplete,
|
||||||
pub fn getPort(self: Ip4Address) u16;
|
NonCanonical,
|
||||||
pub fn setPort(self: *Ip4Address, port: u16) void;
|
|
||||||
pub fn format(self: Ip4Address, w: *std.Io.Writer) !void;
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ip6Address
|
For example, leading-zero forms such as `01.2.3.4` are non-canonical.
|
||||||
|
|
||||||
```zig
|
|
||||||
const Ip6Address = extern struct {
|
|
||||||
sa: posix.sockaddr.in6,
|
|
||||||
|
|
||||||
pub fn parse(buf: []const u8, port: u16) !Ip6Address;
|
|
||||||
pub fn resolve(buf: []const u8, port: u16) !Ip6Address; // handles %interface
|
|
||||||
pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address;
|
|
||||||
pub fn getPort(self: Ip6Address) u16;
|
|
||||||
pub fn setPort(self: *Ip6Address, port: u16) void;
|
|
||||||
pub fn format(self: Ip6Address, w: *std.Io.Writer) !void;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stream I/O
|
## Stream I/O
|
||||||
|
|
||||||
### Stream Type
|
### Stream Shape and Lifecycle
|
||||||
|
|
||||||
```zig
|
```text
|
||||||
pub const Stream = struct {
|
pub const Stream = struct {
|
||||||
handle: Handle, // fd on POSIX, SOCKET on Windows
|
socket: net.Socket,
|
||||||
|
|
||||||
pub fn close(s: Stream) void;
|
pub fn close(stream: *const Stream, io: std.Io) void;
|
||||||
pub fn reader(stream: Stream, buffer: []u8) Reader;
|
pub fn shutdown(stream: *const Stream, io: std.Io, how: net.ShutdownHow) !void;
|
||||||
pub fn writer(stream: Stream, buffer: []u8) Writer;
|
pub fn reader(stream: Stream, io: std.Io, buffer: []u8) Stream.Reader;
|
||||||
|
pub fn writer(stream: Stream, io: std.Io, buffer: []u8) Stream.Writer;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reading from Stream
|
Do not close a stream twice. Flush a buffered writer before shutdown/close when its bytes must reach the peer.
|
||||||
|
|
||||||
|
### Reading
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const stream = try net.tcpConnectToHost(allocator, "example.com", 80);
|
var read_buf: [4096]u8 = undefined;
|
||||||
defer stream.close();
|
var reader = stream.reader(io, &read_buf);
|
||||||
|
const r = &reader.interface;
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
|
||||||
var reader = stream.reader(&buf);
|
|
||||||
const r = reader.interface();
|
|
||||||
|
|
||||||
// Read bytes
|
|
||||||
const data = r.take(100) catch |err| switch (err) {
|
const data = r.take(100) catch |err| switch (err) {
|
||||||
error.EndOfStream => &.{},
|
error.EndOfStream => return,
|
||||||
error.ReadFailed => return reader.getError().?,
|
error.ReadFailed => return reader.err.?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read until delimiter
|
|
||||||
const line = r.takeDelimiter('\n') catch |err| switch (err) {
|
const line = r.takeDelimiter('\n') catch |err| switch (err) {
|
||||||
error.EndOfStream => null,
|
error.ReadFailed => return reader.err.?,
|
||||||
error.StreamTooLong => return error.LineTooLong,
|
error.StreamTooLong => return error.LineTooLong,
|
||||||
error.ReadFailed => return reader.getError().?,
|
|
||||||
} orelse return;
|
} orelse return;
|
||||||
|
|
||||||
// Discard bytes
|
_ = data;
|
||||||
_ = try r.discard(.limited(100));
|
_ = line;
|
||||||
|
|
||||||
// Stream to writer
|
|
||||||
_ = try r.streamRemaining(&output_writer);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Writing to Stream
|
The generic reader surface reports `error.ReadFailed`; the network-specific cause is stored in `reader.err`, with cases such as `ConnectionResetByPeer`, `Timeout`, `SocketUnconnected`, or `NetworkDown`.
|
||||||
|
|
||||||
|
### Writing
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var buf: [1024]u8 = undefined;
|
var write_buf: [1024]u8 = undefined;
|
||||||
var writer = stream.writer(&buf);
|
var writer = stream.writer(io, &write_buf);
|
||||||
const w = &writer.interface;
|
const w = &writer.interface;
|
||||||
|
|
||||||
// Write bytes
|
|
||||||
try w.writeAll("Hello, World!");
|
try w.writeAll("Hello, World!");
|
||||||
|
|
||||||
// Formatted output
|
|
||||||
try w.print("Count: {d}\n", .{42});
|
try w.print("Count: {d}\n", .{42});
|
||||||
|
|
||||||
// MUST flush before close
|
|
||||||
try w.flush();
|
try w.flush();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Handling
|
The generic writer reports `error.WriteFailed`; inspect `writer.err` for the network-specific cause. A successful `writeAll` may still be buffered until `flush`.
|
||||||
|
|
||||||
|
### Half-Close
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var reader = stream.reader(&buf);
|
try stream.shutdown(io, .send); // no more application writes
|
||||||
const r = reader.interface();
|
// Continue reading until EndOfStream if the protocol expects a response.
|
||||||
|
|
||||||
const data = r.take(100) catch |err| switch (err) {
|
|
||||||
error.EndOfStream => {
|
|
||||||
// Connection closed normally
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
error.ReadFailed => {
|
|
||||||
// Get underlying error
|
|
||||||
const read_err = reader.getError().?;
|
|
||||||
switch (read_err) {
|
|
||||||
error.ConnectionResetByPeer => return error.Disconnected,
|
|
||||||
error.SocketNotConnected => return error.Disconnected,
|
|
||||||
else => return read_err,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## DNS Resolution
|
`ShutdownHow` is `.recv`, `.send`, or `.both`.
|
||||||
|
|
||||||
### Get Address List
|
## DNS and Host Names
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const host = try net.HostName.init("example.com");
|
||||||
const net = std.net;
|
try net.HostName.validate("api.example.com");
|
||||||
|
|
||||||
pub fn main() !void {
|
const same = host.eql(try .init("EXAMPLE.COM")); // DNS names compare case-insensitively
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
const child = host.sameParentDomain(try .init("www.example.com"));
|
||||||
defer _ = gpa.deinit();
|
```
|
||||||
const allocator = gpa.allocator();
|
|
||||||
|
|
||||||
// Resolve hostname to addresses
|
`HostName` retains a borrowed byte slice. Labels and total length are validated; the maximum is `net.HostName.max_len`.
|
||||||
const list = try net.getAddressList(allocator, "example.com", 80);
|
|
||||||
defer list.deinit();
|
|
||||||
|
|
||||||
// Canonical name (if available)
|
### Queue-Based Lookup
|
||||||
if (list.canon_name) |name| {
|
|
||||||
std.debug.print("Canonical name: {s}\n", .{name});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate addresses
|
```zig
|
||||||
for (list.addrs) |addr| {
|
const host: net.HostName = try .init("example.com");
|
||||||
var buf: [64]u8 = undefined;
|
var result_storage: [16]net.HostName.LookupResult = undefined;
|
||||||
var w: std.Io.Writer = .fixed(&buf);
|
var results: std.Io.Queue(net.HostName.LookupResult) = .init(&result_storage);
|
||||||
try addr.format(&w);
|
|
||||||
std.debug.print("Address: {s}\n", .{w.buffered()});
|
try host.lookup(io, &results, .{ .port = 443 });
|
||||||
}
|
|
||||||
|
while (results.getOne(io)) |result| switch (result) {
|
||||||
|
.address => |address| std.debug.print("address: {f}\n", .{address}),
|
||||||
|
.canonical_name => |name| std.debug.print("canonical: {s}\n", .{name.bytes}),
|
||||||
|
} else |err| switch (err) {
|
||||||
|
error.Closed => {},
|
||||||
|
error.Canceled => return err,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connect with Fallback
|
`lookup` adds zero or more `.address` results and exactly one `.canonical_name`, then closes the queue even on error. Capacity 16 guarantees the call itself need not block waiting for a consumer.
|
||||||
|
|
||||||
`tcpConnectToHost` automatically tries all resolved addresses:
|
### Connect with Lookup and Fallback
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Tries each resolved address until one connects
|
const host: net.HostName = try .init("example.com");
|
||||||
const stream = net.tcpConnectToHost(allocator, "example.com", 80) catch |err| switch (err) {
|
const stream = host.connect(io, 443, .{
|
||||||
error.ConnectionRefused => return error.ServerDown,
|
.mode = .stream,
|
||||||
error.UnknownHostName => return error.DnsError,
|
.protocol = .tcp,
|
||||||
error.TemporaryNameServerFailure => return error.DnsError,
|
}) catch |err| switch (err) {
|
||||||
|
error.UnknownHostName, error.NoAddressReturned => return error.DnsFailure,
|
||||||
|
error.ConnectionRefused => return error.ServerUnavailable,
|
||||||
else => return err,
|
else => return err,
|
||||||
};
|
};
|
||||||
|
defer stream.close(io);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Unix Sockets
|
For advanced callers, `HostName.connectMany` asynchronously attempts all resolved addresses and writes successes or per-address connection errors to a caller-provided queue.
|
||||||
|
|
||||||
### Check Platform Support
|
## Unix-Domain Sockets
|
||||||
|
|
||||||
|
### Support and Client
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
if (net.has_unix_sockets) {
|
if (net.has_unix_sockets) {
|
||||||
// Unix sockets available
|
const address = try net.UnixAddress.init("/var/run/app.sock");
|
||||||
|
const stream = try address.connect(io);
|
||||||
|
defer stream.close(io);
|
||||||
|
|
||||||
|
var read_buf: [4096]u8 = undefined;
|
||||||
|
var reader = stream.reader(io, &read_buf);
|
||||||
|
_ = &reader;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connect to Unix Socket
|
`UnixAddress` borrows its path and rejects paths longer than `UnixAddress.max_len`. `isAbstract()` detects an empty/leading-NUL abstract address representation.
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const stream = try net.connectUnixSocket("/var/run/app.sock");
|
const socket_path = "/tmp/my.sock";
|
||||||
defer stream.close();
|
std.Io.Dir.deleteFileAbsolute(io, socket_path) catch |err| switch (err) {
|
||||||
|
error.FileNotFound => {},
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
defer std.Io.Dir.deleteFileAbsolute(io, socket_path) catch {};
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
const address = try net.UnixAddress.init(socket_path);
|
||||||
var reader = stream.reader(&buf);
|
var server = try address.listen(io, .{ .kernel_backlog = 128 });
|
||||||
var writer = stream.writer(&buf);
|
defer server.deinit(io);
|
||||||
// ... use like TCP
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unix Socket Server
|
|
||||||
|
|
||||||
```zig
|
|
||||||
const address = try net.Address.initUnix("/tmp/my.sock");
|
|
||||||
var server = try address.listen(.{ .reuse_address = true });
|
|
||||||
defer server.deinit();
|
|
||||||
|
|
||||||
// Remove socket file on cleanup
|
|
||||||
defer std.fs.deleteFileAbsolute("/tmp/my.sock") catch {};
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const conn = try server.accept();
|
const client = try server.accept(io);
|
||||||
defer conn.stream.close();
|
defer client.close(io);
|
||||||
// handle connection...
|
// Handle one client in this loop body.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Unix listen options contain only `kernel_backlog`; IP `reuse_address` options do not apply to this type.
|
||||||
|
|
||||||
|
## Sockets and Datagram APIs
|
||||||
|
|
||||||
|
`IpAddress.bind` is the non-streaming counterpart to `listen`:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const address: net.IpAddress = .{ .ip4 = .unspecified(5353) };
|
||||||
|
const socket = try address.bind(io, .{
|
||||||
|
.mode = .dgram,
|
||||||
|
.protocol = .udp,
|
||||||
|
.allow_broadcast = false,
|
||||||
|
});
|
||||||
|
defer socket.close(io);
|
||||||
|
```
|
||||||
|
|
||||||
|
`BindOptions` contains `ip6_only`, `allow_broadcast`, required `mode`, and optional `protocol`. `Socket` also exposes message send/receive operations, shutdown, option accessors, and `closeMany`; use those when datagram boundaries or raw socket features matter.
|
||||||
|
|
||||||
|
The listening API intentionally has a smaller option set than `bind`. In particular, `IpAddress.ListenOptions` has no `ip6_only` flag, so do not assume an IPv6 listener is unconditionally dual-stack on every target. Use explicitly managed IPv4/IPv6 listeners when that behavior must be controlled.
|
||||||
|
|
||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
### Echo Server
|
### Echo One Connection
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
fn echoConnection(io: std.Io, stream: net.Stream) !void {
|
||||||
const net = std.net;
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 7); // echo port
|
|
||||||
var server = try address.listen(.{ .reuse_address = true });
|
|
||||||
defer server.deinit();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const conn = try server.accept();
|
|
||||||
defer conn.stream.close();
|
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
|
||||||
var reader = conn.stream.reader(&buf);
|
|
||||||
var writer = conn.stream.writer(&buf);
|
|
||||||
|
|
||||||
// Echo back everything received
|
|
||||||
_ = reader.interface().streamRemaining(&writer.interface) catch {};
|
|
||||||
writer.interface.flush() catch {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Simple HTTP GET
|
|
||||||
|
|
||||||
```zig
|
|
||||||
fn httpGet(allocator: Allocator, host: []const u8, path: []const u8) ![]u8 {
|
|
||||||
const stream = try net.tcpConnectToHost(allocator, host, 80);
|
|
||||||
defer stream.close();
|
|
||||||
|
|
||||||
var write_buf: [1024]u8 = undefined;
|
|
||||||
var writer = stream.writer(&write_buf);
|
|
||||||
const w = &writer.interface;
|
|
||||||
|
|
||||||
try w.print("GET {s} HTTP/1.1\r\n", .{path});
|
|
||||||
try w.print("Host: {s}\r\n", .{host});
|
|
||||||
try w.writeAll("Connection: close\r\n\r\n");
|
|
||||||
try w.flush();
|
|
||||||
|
|
||||||
var read_buf: [4096]u8 = undefined;
|
var read_buf: [4096]u8 = undefined;
|
||||||
var reader = stream.reader(&read_buf);
|
var write_buf: [4096]u8 = undefined;
|
||||||
|
var reader = stream.reader(io, &read_buf);
|
||||||
|
var writer = stream.writer(io, &write_buf);
|
||||||
|
|
||||||
var response: std.ArrayList(u8) = .empty;
|
_ = reader.interface.streamRemaining(&writer.interface) catch |err| switch (err) {
|
||||||
defer response.deinit(allocator);
|
error.ReadFailed => return reader.err.?,
|
||||||
|
error.WriteFailed => return writer.err.?,
|
||||||
while (true) {
|
|
||||||
const chunk = reader.interface().take(4096) catch |err| switch (err) {
|
|
||||||
error.EndOfStream => break,
|
|
||||||
error.ReadFailed => return reader.getError().?,
|
|
||||||
};
|
};
|
||||||
try response.appendSlice(allocator, chunk);
|
try writer.interface.flush();
|
||||||
}
|
|
||||||
|
|
||||||
return response.toOwnedSlice(allocator);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Non-blocking Accept with Timeout
|
For a concurrent server, schedule each accepted stream through the active `std.Io` implementation and make one owner responsible for closing it.
|
||||||
|
|
||||||
|
### Address and Host Validation
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
fn isValidIpAddress(text: []const u8) bool {
|
||||||
const net = std.net;
|
_ = net.IpAddress.parse(text, 0) catch return false;
|
||||||
const posix = std.posix;
|
|
||||||
|
|
||||||
fn acceptWithTimeout(server: *net.Server, timeout_ms: i32) !?net.Server.Connection {
|
|
||||||
var pfd = [1]posix.pollfd{.{
|
|
||||||
.fd = server.stream.handle,
|
|
||||||
.events = posix.POLL.IN,
|
|
||||||
.revents = undefined,
|
|
||||||
}};
|
|
||||||
|
|
||||||
const ready = try posix.poll(&pfd, timeout_ms);
|
|
||||||
if (ready == 0) return null; // timeout
|
|
||||||
|
|
||||||
return try server.accept();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Address Validation
|
|
||||||
|
|
||||||
```zig
|
|
||||||
fn isValidIpAddress(str: []const u8) bool {
|
|
||||||
_ = net.Address.parseIp(str, 0) catch return false;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isValidHostname(hostname: []const u8) bool {
|
fn isValidHostName(text: []const u8) bool {
|
||||||
return net.isValidHostName(hostname);
|
net.HostName.validate(text) catch return false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dual-Stack Server (IPv4 + IPv6)
|
Parsing an IP is a pure syntax/canonicality check. Host-name validation does not perform DNS lookup.
|
||||||
|
|
||||||
|
### Index-Based Connection Pool
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Listen on IPv6 with dual-stack (accepts both IPv4 and IPv6)
|
|
||||||
const address = net.Address.initIp6(
|
|
||||||
.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // ::
|
|
||||||
8080,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
var server = try address.listen(.{ .reuse_address = true });
|
|
||||||
defer server.deinit();
|
|
||||||
|
|
||||||
// IPv4 clients appear as IPv4-mapped IPv6 addresses (::ffff:x.x.x.x)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Connection Pool Pattern
|
|
||||||
|
|
||||||
```zig
|
|
||||||
const Connection = struct {
|
|
||||||
stream: net.Stream,
|
|
||||||
in_use: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Pool = struct {
|
const Pool = struct {
|
||||||
connections: std.ArrayList(Connection),
|
const Entry = struct { stream: net.Stream, in_use: bool };
|
||||||
allocator: Allocator,
|
|
||||||
|
|
||||||
pub fn acquire(self: *Pool, address: net.Address) !net.Stream {
|
entries: std.ArrayList(Entry) = .empty,
|
||||||
// Find free connection
|
allocator: std.mem.Allocator,
|
||||||
for (self.connections.items) |*conn| {
|
io: std.Io,
|
||||||
if (!conn.in_use) {
|
|
||||||
conn.in_use = true;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn release(self: *Pool, stream: net.Stream) void {
|
fn acquire(self: *Pool, address: *const net.IpAddress) !usize {
|
||||||
for (self.connections.items) |*conn| {
|
for (self.entries.items, 0..) |*entry, index| {
|
||||||
if (conn.stream.handle == stream.handle) {
|
if (!entry.in_use) {
|
||||||
conn.in_use = false;
|
entry.in_use = true;
|
||||||
return;
|
return index;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Pool) void {
|
const stream = try address.connect(self.io, .{ .mode = .stream, .protocol = .tcp });
|
||||||
for (self.connections.items) |conn| {
|
errdefer stream.close(self.io);
|
||||||
conn.stream.close();
|
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
|
```zig
|
||||||
pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
|
error.ConnectionRefused
|
||||||
|
error.ConnectionResetByPeer
|
||||||
pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError;
|
error.HostUnreachable
|
||||||
// Includes: ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, etc.
|
error.NetworkUnreachable
|
||||||
|
error.NetworkDown
|
||||||
|
error.Timeout
|
||||||
|
error.WouldBlock
|
||||||
|
error.AccessDenied
|
||||||
```
|
```
|
||||||
|
|
||||||
### DNS Errors
|
It also includes cancellation and implementation-specific unexpected I/O errors. Match only cases the caller can handle meaningfully and propagate the rest.
|
||||||
|
|
||||||
|
### Host Lookup and Connect Errors
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub const GetAddressListError = error{
|
net.HostName.LookupError // UnknownHostName, NameServerFailure,
|
||||||
TemporaryNameServerFailure,
|
// NoAddressReturned, configuration/DNS record errors, ...
|
||||||
NameServerFailure,
|
|
||||||
AddressFamilyNotSupported,
|
net.HostName.ConnectError // LookupError || net.IpAddress.ConnectError
|
||||||
UnknownHostName,
|
|
||||||
HostLacksNetworkAddresses,
|
|
||||||
// ... and others
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Address Parse Errors
|
The old `GetAddressListError` and `TcpConnectToHostError` aliases are not Zig 0.16 APIs.
|
||||||
|
|
||||||
```zig
|
### Stream Error Translation
|
||||||
pub const IPv4ParseError = error{
|
|
||||||
Overflow,
|
|
||||||
InvalidEnd,
|
|
||||||
InvalidCharacter,
|
|
||||||
Incomplete,
|
|
||||||
NonCanonical, // e.g., leading zeros like "01.02.03.04"
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const IPv6ParseError = error{
|
`Stream.Reader` and `Stream.Writer` deliberately adapt network errors to the generic `std.Io.Reader`/`std.Io.Writer` interfaces:
|
||||||
Overflow,
|
|
||||||
InvalidEnd,
|
- On `error.ReadFailed`, inspect `reader.err`.
|
||||||
InvalidCharacter,
|
- On `error.WriteFailed`, inspect `writer.err`.
|
||||||
Incomplete,
|
- `EndOfStream` is the normal generic-reader signal for an orderly peer close.
|
||||||
InvalidIpv4Mapping,
|
- Close, shutdown, accept, connect, lookup, bind, and listen all take the explicit `io` used to create or operate the resource.
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,9 @@ std.os.wasi // WebAssembly System Interface
|
||||||
std.os.plan9 // Plan 9 system calls
|
std.os.plan9 // Plan 9 system calls
|
||||||
std.os.uefi // UEFI firmware interface
|
std.os.uefi // UEFI firmware interface
|
||||||
std.os.emscripten // Emscripten runtime
|
std.os.emscripten // Emscripten runtime
|
||||||
std.os.freebsd // FreeBSD-specific definitions
|
|
||||||
|
|
||||||
std.os.environ // Environment variables (populated at startup)
|
|
||||||
std.os.argv // Command line arguments (POSIX only)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: For most use cases, prefer `std.posix` (cross-platform POSIX-like APIs) or `std.fs`/`std.process` (high-level abstractions). Use `std.os` when you need direct OS-specific functionality.
|
Those six target modules are the complete public surface of `std.os` in Zig 0.16. For most use cases, prefer `std.Io` (portable, capability-based I/O) or `std.posix` (cross-platform POSIX-like APIs). Process startup arguments and environment data are supplied through `std.process.Init`, not `std.os.argv` or `std.os.environ`.
|
||||||
|
|
||||||
## Platform Submodules
|
## Platform Submodules
|
||||||
|
|
||||||
|
|
@ -37,13 +33,14 @@ std.os.argv // Command line arguments (POSIX only)
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// High-level (recommended for most code)
|
// High-level (recommended for most code)
|
||||||
const file = try std.fs.cwd().openFile("data.txt", .{});
|
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
|
||||||
|
defer file.close(io);
|
||||||
|
|
||||||
// POSIX-level (cross-platform low-level)
|
// POSIX-level (cross-platform low-level)
|
||||||
const fd = try std.posix.open("data.txt", .{}, 0);
|
const fd = try std.posix.open("data.txt", .{}, 0);
|
||||||
|
|
||||||
// OS-specific (platform-specific features)
|
// OS-specific (platform-specific features)
|
||||||
const result = std.os.linux.syscall3(.read, fd, buf.ptr, buf.len);
|
const result = std.os.linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Linux-Specific APIs
|
## Linux-Specific APIs
|
||||||
|
|
@ -55,7 +52,7 @@ const linux = std.os.linux;
|
||||||
|
|
||||||
// Raw syscall interface
|
// Raw syscall interface
|
||||||
const result = linux.syscall3(.write, fd, @intFromPtr(buf.ptr), buf.len);
|
const result = linux.syscall3(.write, fd, @intFromPtr(buf.ptr), buf.len);
|
||||||
if (linux.E.init(result) != .SUCCESS) {
|
if (linux.errno(result) != .SUCCESS) {
|
||||||
// handle error
|
// handle error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +71,7 @@ _ = linux.chroot(path);
|
||||||
const linux = std.os.linux;
|
const linux = std.os.linux;
|
||||||
|
|
||||||
// mmap with typed flags
|
// mmap with typed flags
|
||||||
const addr = linux.mmap(
|
const result = linux.mmap(
|
||||||
null,
|
null,
|
||||||
length,
|
length,
|
||||||
linux.PROT.READ | linux.PROT.WRITE,
|
linux.PROT.READ | linux.PROT.WRITE,
|
||||||
|
|
@ -82,9 +79,10 @@ const addr = linux.mmap(
|
||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
if (addr == linux.MAP_FAILED) {
|
const addr: [*]u8 = switch (linux.errno(result)) {
|
||||||
// handle error
|
.SUCCESS => @ptrFromInt(result),
|
||||||
}
|
else => |err| return std.posix.unexpectedErrno(err),
|
||||||
|
};
|
||||||
|
|
||||||
// Remap
|
// Remap
|
||||||
_ = linux.mremap(old_addr, old_size, new_size, .{ .MAYMOVE = true }, null);
|
_ = linux.mremap(old_addr, old_size, new_size, .{ .MAYMOVE = true }, null);
|
||||||
|
|
@ -121,7 +119,7 @@ const linux = std.os.linux;
|
||||||
// Wait on futex
|
// Wait on futex
|
||||||
_ = linux.futex(
|
_ = linux.futex(
|
||||||
&futex_word,
|
&futex_word,
|
||||||
.{ .op = .WAIT, .PRIVATE = true },
|
.{ .cmd = .WAIT, .private = true },
|
||||||
expected_value,
|
expected_value,
|
||||||
.{ .timeout = &timeout },
|
.{ .timeout = &timeout },
|
||||||
null,
|
null,
|
||||||
|
|
@ -131,7 +129,7 @@ _ = linux.futex(
|
||||||
// Wake waiters
|
// Wake waiters
|
||||||
_ = linux.futex(
|
_ = linux.futex(
|
||||||
&futex_word,
|
&futex_word,
|
||||||
.{ .op = .WAKE, .PRIVATE = true },
|
.{ .cmd = .WAKE, .private = true },
|
||||||
num_to_wake,
|
num_to_wake,
|
||||||
.{ .val2 = 0 },
|
.{ .val2 = 0 },
|
||||||
null,
|
null,
|
||||||
|
|
@ -150,7 +148,10 @@ var act: linux.Sigaction = .{
|
||||||
.mask = linux.empty_sigset,
|
.mask = linux.empty_sigset,
|
||||||
.flags = .{},
|
.flags = .{},
|
||||||
};
|
};
|
||||||
_ = linux.sigaction(linux.SIG.INT, &act, null);
|
const result = linux.sigaction(.INT, &act, null);
|
||||||
|
if (linux.errno(result) != .SUCCESS) {
|
||||||
|
// translate or handle the raw errno
|
||||||
|
}
|
||||||
|
|
||||||
// Kill process
|
// Kill process
|
||||||
_ = linux.kill(pid, linux.SIG.TERM);
|
_ = linux.kill(pid, linux.SIG.TERM);
|
||||||
|
|
@ -173,7 +174,11 @@ _ = linux.epoll_ctl(epfd, .ADD, client_fd, &event);
|
||||||
|
|
||||||
// Wait for events
|
// Wait for events
|
||||||
var events: [64]linux.epoll_event = undefined;
|
var events: [64]linux.epoll_event = undefined;
|
||||||
const n = linux.epoll_wait(epfd, &events, -1);
|
const result = linux.epoll_wait(epfd, &events, @intCast(events.len), -1);
|
||||||
|
const n: usize = switch (linux.errno(result)) {
|
||||||
|
.SUCCESS => result,
|
||||||
|
else => |err| return std.posix.unexpectedErrno(err),
|
||||||
|
};
|
||||||
for (events[0..n]) |ev| {
|
for (events[0..n]) |ev| {
|
||||||
// handle event
|
// handle event
|
||||||
}
|
}
|
||||||
|
|
@ -195,19 +200,13 @@ const platform = linux.getauxval(std.elf.AT_PLATFORM);
|
||||||
### File Operations
|
### File Operations
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const windows = std.os.windows;
|
// Prefer the portable std.Io layer even in Windows-only programs.
|
||||||
|
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
|
||||||
// Open file with NT API
|
defer file.close(io);
|
||||||
const handle = try windows.OpenFile(path_utf16, .{
|
|
||||||
.access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE,
|
|
||||||
.creation = windows.FILE_OPEN,
|
|
||||||
.share_access = windows.FILE_SHARE_READ,
|
|
||||||
.filter = .file_only,
|
|
||||||
.follow_symlinks = true,
|
|
||||||
});
|
|
||||||
defer windows.CloseHandle(handle);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For Win32 or NT features that `std.Io` does not expose, use declarations actually exported by `std.os.windows` and its `kernel32`/`ntdll` submodules. There is no public `std.os.windows.OpenFile` helper in Zig 0.16.
|
||||||
|
|
||||||
### Process Information
|
### Process Information
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -225,26 +224,13 @@ const err = windows.GetLastError();
|
||||||
|
|
||||||
### Pipes
|
### Pipes
|
||||||
|
|
||||||
```zig
|
Use the `std.Io` pipe/process APIs for portable pipes. If raw Windows handles are required, call a declaration that is actually exported from the relevant Windows submodule and translate its Win32 error explicitly; Zig 0.16 does not expose a fallible `std.os.windows.CreatePipe` wrapper with the signature shown in older examples.
|
||||||
const windows = std.os.windows;
|
|
||||||
|
|
||||||
var read_handle: windows.HANDLE = undefined;
|
|
||||||
var write_handle: windows.HANDLE = undefined;
|
|
||||||
var sa: windows.SECURITY_ATTRIBUTES = .{
|
|
||||||
.nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
|
|
||||||
.lpSecurityDescriptor = null,
|
|
||||||
.bInheritHandle = windows.TRUE,
|
|
||||||
};
|
|
||||||
|
|
||||||
try windows.CreatePipe(&read_handle, &write_handle, &sa);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Submodules
|
### Submodules
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
windows.kernel32 // kernel32.dll functions
|
windows.kernel32 // kernel32.dll functions
|
||||||
windows.ntdll // ntdll.dll functions (NT native API)
|
windows.ntdll // ntdll.dll functions (NT native API)
|
||||||
windows.advapi32 // advapi32.dll (security, registry)
|
|
||||||
windows.ws2_32 // Winsock 2 networking
|
windows.ws2_32 // Winsock 2 networking
|
||||||
windows.crypt32 // Cryptographic functions
|
windows.crypt32 // Cryptographic functions
|
||||||
windows.nls // National Language Support
|
windows.nls // National Language Support
|
||||||
|
|
@ -386,21 +372,21 @@ const submitted = try ring.submit();
|
||||||
_ = try ring.submit_and_wait(1);
|
_ = try ring.submit_and_wait(1);
|
||||||
|
|
||||||
// Process completions
|
// Process completions
|
||||||
while (ring.cq_ready() > 0) {
|
var cqes: [32]std.os.linux.io_uring_cqe = undefined;
|
||||||
const cqe = ring.peek_cqe() orelse break;
|
const ready = try ring.copy_cqes(&cqes, 1);
|
||||||
|
for (cqes[0..ready]) |cqe| {
|
||||||
const user_data = cqe.user_data;
|
const user_data = cqe.user_data;
|
||||||
const result = cqe.res; // bytes transferred or -errno
|
const result = cqe.res; // bytes transferred or -errno
|
||||||
|
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
const err = std.os.linux.E.init(@intCast(-result));
|
const err: std.os.linux.E = @enumFromInt(@as(u16, @intCast(-result)));
|
||||||
// handle error
|
// handle error
|
||||||
}
|
}
|
||||||
|
|
||||||
ring.cq_advance(1); // mark CQE as consumed
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`copy_cqes` copies and consumes completions as a batch. `copy_cqe` is the corresponding wait-for-one convenience method. Do not additionally call `cqe_seen` or `cq_advance` after either copying API.
|
||||||
|
|
||||||
### Common Operations
|
### Common Operations
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -411,8 +397,8 @@ sqe.prep_readv(fd, iovecs, offset);
|
||||||
sqe.prep_writev(fd, iovecs, offset);
|
sqe.prep_writev(fd, iovecs, offset);
|
||||||
|
|
||||||
// Fixed buffers (pre-registered, zero-copy)
|
// Fixed buffers (pre-registered, zero-copy)
|
||||||
sqe.prep_read_fixed(fd, buf, offset, buf_index);
|
sqe.prep_read_fixed(fd, registered_iovec, offset, buf_index);
|
||||||
sqe.prep_write_fixed(fd, data, offset, buf_index);
|
sqe.prep_write_fixed(fd, registered_iovec, offset, buf_index);
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0);
|
sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0);
|
||||||
|
|
@ -463,7 +449,7 @@ defer ring.unregister_buffers() catch {};
|
||||||
|
|
||||||
// Use registered buffer
|
// Use registered buffer
|
||||||
const sqe = try ring.get_sqe();
|
const sqe = try ring.get_sqe();
|
||||||
sqe.prep_read_fixed(fd, &buffers[0], 0, 0); // buf_index = 0
|
sqe.prep_read_fixed(fd, &iovecs[0], 0, 0); // buf_index = 0
|
||||||
```
|
```
|
||||||
|
|
||||||
### File Descriptor Registration
|
### File Descriptor Registration
|
||||||
|
|
@ -482,51 +468,14 @@ sqe.flags |= std.os.linux.IOSQE_FIXED_FILE;
|
||||||
|
|
||||||
## Common Functions
|
## Common Functions
|
||||||
|
|
||||||
### getFdPath
|
Zig 0.16 deliberately has no cross-platform function layer at the `std.os` root. Older references may mention root functions such as `std.os.getFdPath`, `std.os.accessW`, `std.os.fstatat_wasi`, or `std.os.fstat_wasi`; those are not public Zig 0.16 APIs.
|
||||||
|
|
||||||
Get canonical path from file descriptor (not all platforms).
|
Choose the API by intent:
|
||||||
|
|
||||||
```zig
|
- Use `std.Io.Dir` and `std.Io.File` for portable path, access, and metadata operations.
|
||||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
- Use `std.posix` for POSIX-like file-descriptor operations.
|
||||||
const path = try std.os.getFdPath(fd, &buf);
|
- Use `std.os.windows`, `std.os.wasi`, or another exported target module when the behavior is intentionally ABI-specific.
|
||||||
std.debug.print("Path: {s}\n", .{path});
|
- Preserve the path separately when an application needs to associate a portable path with an open file; a descriptor does not portably retain a recoverable canonical pathname.
|
||||||
|
|
||||||
// Check if supported at comptime
|
|
||||||
if (comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) {
|
|
||||||
// safe to call
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Supported**: Linux, macOS, FreeBSD, Windows, Solaris/illumos, DragonFly (6.0+), NetBSD (10.0+)
|
|
||||||
|
|
||||||
### accessW (Windows)
|
|
||||||
|
|
||||||
Check file accessibility with WTF-16LE path.
|
|
||||||
|
|
||||||
```zig
|
|
||||||
const path_w = std.unicode.utf8ToUtf16LeStringLiteral("C:\\file.txt");
|
|
||||||
std.os.accessW(path_w) catch |err| switch (err) {
|
|
||||||
error.FileNotFound => {},
|
|
||||||
error.AccessDenied => {},
|
|
||||||
else => return err,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### WASI stat functions
|
|
||||||
|
|
||||||
```zig
|
|
||||||
// stat by path
|
|
||||||
const stat = try std.os.fstatat_wasi(dirfd, path, .{ .SYMLINK_FOLLOW = true });
|
|
||||||
|
|
||||||
// stat by fd
|
|
||||||
const stat = try std.os.fstat_wasi(fd);
|
|
||||||
|
|
||||||
stat.size; // file size
|
|
||||||
stat.filetype; // .REGULAR_FILE, .DIRECTORY, .SYMBOLIC_LINK, etc.
|
|
||||||
stat.atim; // access time (nanoseconds)
|
|
||||||
stat.mtim; // modification time
|
|
||||||
stat.ctim; // status change time
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
|
|
@ -562,11 +511,10 @@ fn eventLoop(ring: *std.os.linux.IoUring) !void {
|
||||||
// Submit pending and wait for completions
|
// Submit pending and wait for completions
|
||||||
_ = try ring.submit_and_wait(1);
|
_ = try ring.submit_and_wait(1);
|
||||||
|
|
||||||
// Process all available completions
|
// Copying also advances the completion queue.
|
||||||
while (ring.cq_ready() > 0) {
|
var cqes: [64]std.os.linux.io_uring_cqe = undefined;
|
||||||
const cqe = ring.peek_cqe() orelse break;
|
const count = try ring.copy_cqes(&cqes, 1);
|
||||||
defer ring.cq_advance(1);
|
for (cqes[0..count]) |cqe| {
|
||||||
|
|
||||||
const ctx = @as(*Context, @ptrFromInt(cqe.user_data));
|
const ctx = @as(*Context, @ptrFromInt(cqe.user_data));
|
||||||
try ctx.handle_completion(cqe.res);
|
try ctx.handle_completion(cqe.res);
|
||||||
}
|
}
|
||||||
|
|
@ -582,7 +530,7 @@ const linux = std.os.linux;
|
||||||
fn readSyscall(fd: i32, buf: []u8) !usize {
|
fn readSyscall(fd: i32, buf: []u8) !usize {
|
||||||
const result = linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len);
|
const result = linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len);
|
||||||
|
|
||||||
switch (linux.E.init(result)) {
|
switch (linux.errno(result)) {
|
||||||
.SUCCESS => return result,
|
.SUCCESS => return result,
|
||||||
.INTR => return error.Interrupted,
|
.INTR => return error.Interrupted,
|
||||||
.AGAIN => return error.WouldBlock,
|
.AGAIN => return error.WouldBlock,
|
||||||
|
|
@ -601,28 +549,17 @@ fn readSyscall(fd: i32, buf: []u8) !usize {
|
||||||
```zig
|
```zig
|
||||||
const windows = std.os.windows;
|
const windows = std.os.windows;
|
||||||
|
|
||||||
fn windowsOperation() !void {
|
fn translateLastError() !void {
|
||||||
const result = windows.kernel32.SomeFunction(...);
|
// Call this immediately after a Win32 API reports failure; another Win32
|
||||||
if (result == windows.FALSE) {
|
// call may overwrite the thread's last-error value.
|
||||||
switch (windows.GetLastError()) {
|
switch (windows.GetLastError()) {
|
||||||
.ERROR_FILE_NOT_FOUND => return error.FileNotFound,
|
.FILE_NOT_FOUND => return error.FileNotFound,
|
||||||
.ERROR_ACCESS_DENIED => return error.AccessDenied,
|
.ACCESS_DENIED => return error.AccessDenied,
|
||||||
else => |e| return windows.unexpectedError(e),
|
else => |e| return windows.unexpectedError(e),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cross-Platform File Descriptor Path
|
### Retaining a Portable File Path
|
||||||
|
|
||||||
```zig
|
If later logic needs both a file and its pathname, store an owned copy of the pathname when opening the file. Trying to reconstruct a canonical path from a descriptor is target-specific and can be ambiguous after rename, unlink, mount, or namespace changes.
|
||||||
fn getFilePath(fd: std.posix.fd_t, allocator: Allocator) ![]u8 {
|
|
||||||
if (comptime !std.os.isGetFdPathSupportedOnTarget(builtin.os)) {
|
|
||||||
return error.Unsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
||||||
const path = try std.os.getFdPath(fd, &buf);
|
|
||||||
return try allocator.dupe(u8, path);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ Zig 0.16 changed priority dequeues to align with unmanaged containers:
|
||||||
- Initialize with `.empty`.
|
- Initialize with `.empty`.
|
||||||
- `add` -> `push`.
|
- `add` -> `push`.
|
||||||
- `addSlice` -> `pushSlice`.
|
- `addSlice` -> `pushSlice`.
|
||||||
- `addUnchecked` -> `pushUnchecked`.
|
- The old unchecked insertion helper has no public `pushUnchecked` replacement.
|
||||||
- `removeMin` / `removeMinOrNull` -> `popMin`.
|
- `removeMin` / `removeMinOrNull` -> `popMin`.
|
||||||
- `removeMax` / `removeMaxOrNull` -> `popMax`.
|
- `removeMax` / `removeMaxOrNull` -> `popMax`.
|
||||||
- `removeIndex` -> `popIndex`.
|
- `removeIndex` -> `popIndex`.
|
||||||
|
|
||||||
Old examples below may use removed 0.15 names; translate them before using in 0.16 code.
|
All examples below use the Zig 0.16 unmanaged API. `.empty` leaves `context` undefined; use `initContext` whenever the comparator reads it.
|
||||||
|
|
||||||
A min-max heap that efficiently supports both min and max extraction. Unlike `PriorityQueue`, you can pop from either end.
|
A min-max heap that efficiently supports both min and max extraction. Unlike `PriorityQueue`, you can pop from either end.
|
||||||
|
|
||||||
|
|
@ -35,20 +35,20 @@ fn compare(context: void, a: u32, b: u32) std.math.Order {
|
||||||
|
|
||||||
const PDQ = std.PriorityDequeue(u32, void, compare);
|
const PDQ = std.PriorityDequeue(u32, void, compare);
|
||||||
|
|
||||||
var dequeue = PDQ.init(allocator, {});
|
var dequeue = PDQ.initContext({});
|
||||||
defer dequeue.deinit();
|
defer dequeue.deinit(allocator);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Basic Operations
|
## Basic Operations
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Add elements
|
// Add elements
|
||||||
try dequeue.add(54);
|
try dequeue.push(allocator, 54);
|
||||||
try dequeue.add(12);
|
try dequeue.push(allocator, 12);
|
||||||
try dequeue.add(7);
|
try dequeue.push(allocator, 7);
|
||||||
|
|
||||||
// Add multiple
|
// Add multiple
|
||||||
try dequeue.addSlice(&[_]u32{ 1, 2, 3 });
|
try dequeue.pushSlice(allocator, &[_]u32{ 1, 2, 3 });
|
||||||
|
|
||||||
// Peek at min/max (doesn't remove)
|
// Peek at min/max (doesn't remove)
|
||||||
if (dequeue.peekMin()) |min| {
|
if (dequeue.peekMin()) |min| {
|
||||||
|
|
@ -59,12 +59,8 @@ if (dequeue.peekMax()) |max| {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove min/max
|
// Remove min/max
|
||||||
const min = dequeue.removeMin(); // asserts non-empty
|
const maybe_min = dequeue.popMin(); // ?T; null if empty
|
||||||
const max = dequeue.removeMax(); // asserts non-empty
|
const maybe_max = dequeue.popMax(); // ?T; null if empty
|
||||||
|
|
||||||
// Safe removal (returns null if empty)
|
|
||||||
const maybe_min = dequeue.removeMinOrNull();
|
|
||||||
const maybe_max = dequeue.removeMaxOrNull();
|
|
||||||
|
|
||||||
// Size
|
// Size
|
||||||
const n = dequeue.count();
|
const n = dequeue.count();
|
||||||
|
|
@ -76,21 +72,22 @@ const cap = dequeue.capacity();
|
||||||
```zig
|
```zig
|
||||||
// Take ownership of slice, heapify in place
|
// Take ownership of slice, heapify in place
|
||||||
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
|
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
|
||||||
var dequeue = PDQ.fromOwnedSlice(allocator, items, {});
|
var dequeue = PDQ.fromOwnedSlice(items, {});
|
||||||
defer dequeue.deinit();
|
defer dequeue.deinit(allocator);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Update Priority
|
## Update Priority
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
try dequeue.update(old_value, new_value);
|
try dequeue.update(old_value, new_value);
|
||||||
// Error if old_value not found
|
// Lookup uses comparator equality. Equal-priority duplicates are ambiguous;
|
||||||
|
// the API does not identify a particular equal element. Errors if none exists.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Remove by Index
|
## Remove by Index
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const removed = dequeue.removeIndex(index);
|
const removed = dequeue.popIndex(index); // asserts in bounds; heap index is not priority rank
|
||||||
```
|
```
|
||||||
|
|
||||||
## Iteration
|
## Iteration
|
||||||
|
|
@ -107,9 +104,9 @@ it.reset();
|
||||||
## Capacity Management
|
## Capacity Management
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
try dequeue.ensureTotalCapacity(100);
|
try dequeue.ensureTotalCapacity(allocator, 100);
|
||||||
try dequeue.ensureUnusedCapacity(10);
|
try dequeue.ensureUnusedCapacity(allocator, 10);
|
||||||
dequeue.shrinkAndFree(new_capacity);
|
dequeue.shrinkAndFree(allocator, new_capacity);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Context-Based Comparator
|
## Context-Based Comparator
|
||||||
|
|
@ -122,7 +119,8 @@ fn compareByScore(scores: []const u32, a: usize, b: usize) std.math.Order {
|
||||||
const IndexPDQ = std.PriorityDequeue(usize, []const u32, compareByScore);
|
const IndexPDQ = std.PriorityDequeue(usize, []const u32, compareByScore);
|
||||||
|
|
||||||
const scores = [_]u32{ 50, 30, 80, 20 };
|
const scores = [_]u32{ 50, 30, 80, 20 };
|
||||||
var dequeue = IndexPDQ.init(allocator, &scores);
|
var dequeue = IndexPDQ.initContext(scores[0..]);
|
||||||
|
defer dequeue.deinit(allocator);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Complete Example: Bounded Range Tracker
|
## Complete Example: Bounded Range Tracker
|
||||||
|
|
@ -140,15 +138,16 @@ pub fn main() !void {
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
var tracker = RangePDQ.init(gpa.allocator(), {});
|
const allocator = gpa.allocator();
|
||||||
defer tracker.deinit();
|
var tracker = RangePDQ.initContext({});
|
||||||
|
defer tracker.deinit(allocator);
|
||||||
|
|
||||||
// Add values
|
// Add values
|
||||||
try tracker.add(10);
|
try tracker.push(allocator, 10);
|
||||||
try tracker.add(5);
|
try tracker.push(allocator, 5);
|
||||||
try tracker.add(20);
|
try tracker.push(allocator, 20);
|
||||||
try tracker.add(3);
|
try tracker.push(allocator, 3);
|
||||||
try tracker.add(15);
|
try tracker.push(allocator, 15);
|
||||||
|
|
||||||
// Get range without removing
|
// Get range without removing
|
||||||
const min = tracker.peekMin().?; // 3
|
const min = tracker.peekMin().?; // 3
|
||||||
|
|
@ -158,8 +157,8 @@ pub fn main() !void {
|
||||||
std.debug.print("Range: {} to {} = {}\n", .{ min, max, range });
|
std.debug.print("Range: {} to {} = {}\n", .{ min, max, range });
|
||||||
|
|
||||||
// Pop from both ends
|
// Pop from both ends
|
||||||
_ = tracker.removeMin(); // removes 3
|
_ = tracker.popMin(); // removes 3
|
||||||
_ = tracker.removeMax(); // removes 20
|
_ = tracker.popMax(); // removes 20
|
||||||
|
|
||||||
// New range is 5 to 15
|
// New range is 5 to 15
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +176,7 @@ pub fn main() !void {
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Both `removeMin()` and `removeMax()` are O(log n)
|
- Both `popMin()` and `popMax()` are O(log n)
|
||||||
- `peekMin()` is O(1), `peekMax()` is O(1) after first 2 elements
|
- Both nullable peeks are O(1), including empty and one-element deques
|
||||||
- Iterator order is heap array order, not priority order
|
- Iterator order is heap array order, not priority order
|
||||||
- Use when you need efficient access to both extremes
|
- Use when you need efficient access to both extremes
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ Zig 0.16 changed priority queues to align with unmanaged containers:
|
||||||
- Empty queues can use `.empty`.
|
- Empty queues can use `.empty`.
|
||||||
- `init` -> `initContext` when context is needed.
|
- `init` -> `initContext` when context is needed.
|
||||||
- `add` -> `push`.
|
- `add` -> `push`.
|
||||||
- `addUnchecked` -> `pushUnchecked`.
|
- The old unchecked insertion helper has no public `pushUnchecked` replacement; reserve and use supported public operations.
|
||||||
- `addSlice` -> `pushSlice`.
|
- `addSlice` -> `pushSlice`.
|
||||||
- `remove` / `removeOrNull` -> `pop`.
|
- `remove` / `removeOrNull` -> `pop`.
|
||||||
- `removeIndex` -> `popIndex`.
|
- `removeIndex` -> `popIndex`.
|
||||||
|
|
||||||
Old examples below may use removed 0.15 names; translate them before using in 0.16 code.
|
All examples below use the Zig 0.16 unmanaged API.
|
||||||
|
|
||||||
A binary heap-based priority queue. Efficiently retrieves elements by priority order.
|
A binary heap-based priority queue. Efficiently retrieves elements by priority order.
|
||||||
|
|
||||||
|
|
@ -37,8 +37,8 @@ fn lessThan(context: void, a: u32, b: u32) std.math.Order {
|
||||||
|
|
||||||
const PQ = std.PriorityQueue(u32, void, lessThan);
|
const PQ = std.PriorityQueue(u32, void, lessThan);
|
||||||
|
|
||||||
var queue = PQ.init(allocator, {});
|
var queue = PQ.initContext({});
|
||||||
defer queue.deinit();
|
defer queue.deinit(allocator);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Max-Heap
|
## Max-Heap
|
||||||
|
|
@ -56,12 +56,12 @@ const MaxPQ = std.PriorityQueue(u32, void, greaterThan);
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Add elements
|
// Add elements
|
||||||
try queue.add(54);
|
try queue.push(allocator, 54);
|
||||||
try queue.add(12);
|
try queue.push(allocator, 12);
|
||||||
try queue.add(7);
|
try queue.push(allocator, 7);
|
||||||
|
|
||||||
// Add multiple
|
// Add multiple
|
||||||
try queue.addSlice(&[_]u32{ 1, 2, 3 });
|
try queue.pushSlice(allocator, &[_]u32{ 1, 2, 3 });
|
||||||
|
|
||||||
// Peek at highest priority (doesn't remove)
|
// Peek at highest priority (doesn't remove)
|
||||||
if (queue.peek()) |top| {
|
if (queue.peek()) |top| {
|
||||||
|
|
@ -69,8 +69,7 @@ if (queue.peek()) |top| {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove highest priority
|
// Remove highest priority
|
||||||
const top = queue.remove(); // asserts non-empty
|
const maybe_top = queue.pop(); // ?T; null when empty
|
||||||
const maybe = queue.removeOrNull(); // returns ?T
|
|
||||||
|
|
||||||
// Size
|
// Size
|
||||||
const n = queue.count();
|
const n = queue.count();
|
||||||
|
|
@ -82,8 +81,8 @@ const cap = queue.capacity();
|
||||||
```zig
|
```zig
|
||||||
// Take ownership of slice, heapify in place
|
// Take ownership of slice, heapify in place
|
||||||
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
|
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
|
||||||
var queue = PQ.fromOwnedSlice(allocator, items, {});
|
var queue = PQ.fromOwnedSlice(items, {});
|
||||||
defer queue.deinit();
|
defer queue.deinit(allocator);
|
||||||
// Now queue is a valid heap
|
// Now queue is a valid heap
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -92,14 +91,15 @@ defer queue.deinit();
|
||||||
```zig
|
```zig
|
||||||
// Change priority of existing element
|
// Change priority of existing element
|
||||||
try queue.update(old_value, new_value);
|
try queue.update(old_value, new_value);
|
||||||
// Error if old_value not found
|
// Selection uses comparator equality. If duplicates compare equal, which one
|
||||||
|
// is updated is not a stable identity guarantee. Errors if no equal value exists.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Remove by Index
|
## Remove by Index
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Remove element at specific position (not priority order)
|
// Remove element at specific position (not priority order)
|
||||||
const removed = queue.removeIndex(index);
|
const removed = queue.popIndex(index); // asserts index < count; heap index is not priority rank
|
||||||
```
|
```
|
||||||
|
|
||||||
## Iteration (Non-Priority Order)
|
## Iteration (Non-Priority Order)
|
||||||
|
|
@ -113,14 +113,16 @@ while (it.next()) |elem| {
|
||||||
it.reset(); // restart iteration
|
it.reset(); // restart iteration
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Any queue mutation invalidates the iterator.
|
||||||
|
|
||||||
## Capacity Management
|
## Capacity Management
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
try queue.ensureTotalCapacity(100);
|
try queue.ensureTotalCapacity(allocator, 100);
|
||||||
try queue.ensureUnusedCapacity(10);
|
try queue.ensureUnusedCapacity(allocator, 10);
|
||||||
queue.shrinkAndFree(new_capacity);
|
queue.shrinkAndFree(allocator, new_capacity);
|
||||||
queue.clearRetainingCapacity();
|
queue.clearRetainingCapacity();
|
||||||
queue.clearAndFree();
|
queue.clearAndFree(allocator);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Context-Based Comparator
|
## Context-Based Comparator
|
||||||
|
|
@ -135,16 +137,16 @@ fn compareByScore(scores: []const u32, a: usize, b: usize) std.math.Order {
|
||||||
const IndexPQ = std.PriorityQueue(usize, []const u32, compareByScore);
|
const IndexPQ = std.PriorityQueue(usize, []const u32, compareByScore);
|
||||||
|
|
||||||
const scores = [_]u32{ 50, 30, 80, 20 };
|
const scores = [_]u32{ 50, 30, 80, 20 };
|
||||||
var queue = IndexPQ.init(allocator, &scores);
|
var queue = IndexPQ.initContext(scores[0..]);
|
||||||
defer queue.deinit();
|
defer queue.deinit(allocator);
|
||||||
|
|
||||||
try queue.add(0); // score 50
|
try queue.push(allocator, 0); // score 50
|
||||||
try queue.add(1); // score 30
|
try queue.push(allocator, 1); // score 30
|
||||||
try queue.add(2); // score 80
|
try queue.push(allocator, 2); // score 80
|
||||||
try queue.add(3); // score 20
|
try queue.push(allocator, 3); // score 20
|
||||||
|
|
||||||
// Removes index 3 (score 20 is smallest)
|
// Removes index 3 (score 20 is smallest)
|
||||||
const best = queue.remove(); // 3
|
const best = queue.pop().?; // 3
|
||||||
```
|
```
|
||||||
|
|
||||||
## Complete Example: Task Scheduler
|
## Complete Example: Task Scheduler
|
||||||
|
|
@ -167,14 +169,15 @@ pub fn main() !void {
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
var tasks = TaskQueue.init(gpa.allocator(), {});
|
const allocator = gpa.allocator();
|
||||||
defer tasks.deinit();
|
var tasks = TaskQueue.initContext({});
|
||||||
|
defer tasks.deinit(allocator);
|
||||||
|
|
||||||
try tasks.add(.{ .name = "low priority", .priority = 100 });
|
try tasks.push(allocator, .{ .name = "low priority", .priority = 100 });
|
||||||
try tasks.add(.{ .name = "urgent", .priority = 1 });
|
try tasks.push(allocator, .{ .name = "urgent", .priority = 1 });
|
||||||
try tasks.add(.{ .name = "medium", .priority = 50 });
|
try tasks.push(allocator, .{ .name = "medium", .priority = 50 });
|
||||||
|
|
||||||
while (tasks.removeOrNull()) |task| {
|
while (tasks.pop()) |task| {
|
||||||
std.debug.print("Processing: {s}\n", .{task.name});
|
std.debug.print("Processing: {s}\n", .{task.name});
|
||||||
}
|
}
|
||||||
// Output:
|
// Output:
|
||||||
|
|
@ -187,6 +190,6 @@ pub fn main() !void {
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Heap property: parent has higher priority than children
|
- Heap property: parent has higher priority than children
|
||||||
- `remove()` is O(log n), `peek()` is O(1)
|
- `pop()` is nullable and O(log n); `peek()` is nullable and O(1)
|
||||||
- Iterator order is NOT priority order (it's heap array order)
|
- Iterator order is NOT priority order (it's heap array order)
|
||||||
- Use `removeOrNull()` for safe extraction from potentially empty queue
|
- Use `pop()` for extraction from a potentially empty queue
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ const cwd = try std.process.currentPathAlloc(io, gpa);
|
||||||
defer gpa.free(cwd);
|
defer gpa.free(cwd);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
On Windows these paths use WTF-8. On other platforms they are opaque path bytes
|
||||||
|
with no guaranteed text encoding; do not assume UTF-8 when displaying or
|
||||||
|
parsing them.
|
||||||
|
|
||||||
Do not add new `std.process.getCwd*` callsites.
|
Do not add new `std.process.getCwd*` callsites.
|
||||||
|
|
||||||
## Run and Capture Output
|
## Run and Capture Output
|
||||||
|
|
@ -113,6 +117,10 @@ Important options:
|
||||||
- `disable_aslr`
|
- `disable_aslr`
|
||||||
- `timeout`
|
- `timeout`
|
||||||
|
|
||||||
|
Supplying `.environ_map` replaces the child's environment, but its `PATH` does
|
||||||
|
not resolve `argv[0]`; executable lookup still uses the parent environment.
|
||||||
|
Use `std.process.spawnPath` for deterministic directory-relative resolution.
|
||||||
|
|
||||||
## Spawn Child Process
|
## Spawn Child Process
|
||||||
|
|
||||||
Use `std.process.spawn(io, options)`. `std.process.Child.init` is not the 0.16 pattern.
|
Use `std.process.spawn(io, options)`. `std.process.Child.init` is not the 0.16 pattern.
|
||||||
|
|
@ -214,6 +222,8 @@ Memory locking/protection APIs moved under `std.process`:
|
||||||
- `std.process.unlockMemory`
|
- `std.process.unlockMemory`
|
||||||
- `std.process.lockMemoryAll`
|
- `std.process.lockMemoryAll`
|
||||||
- `std.process.unlockMemoryAll`
|
- `std.process.unlockMemoryAll`
|
||||||
|
- `std.process.MemoryProtection`
|
||||||
|
- `std.process.protectMemory`
|
||||||
|
|
||||||
Use them only for explicit platform/security needs.
|
Use them only for explicit platform/security needs.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/releas
|
||||||
|
|
||||||
## Zig 0.16 Entropy Rule
|
## Zig 0.16 Entropy Rule
|
||||||
|
|
||||||
System entropy moved under `std.Io`.
|
Random bytes moved under `std.Io`. `io.random` is the process CSPRNG stream and may use a less-secure seeding fallback on platforms where fresh system entropy is unavailable. `io.randomSecure` requests fresh external entropy and reports failure.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var seed: [32]u8 = undefined;
|
var seed: [32]u8 = undefined;
|
||||||
|
|
@ -18,7 +18,7 @@ const rng = source.interface();
|
||||||
|
|
||||||
Use `io.randomSecure(&bytes)` when fresh OS-backed secure entropy is required and errors must be surfaced.
|
Use `io.randomSecure(&bytes)` when fresh OS-backed secure entropy is required and errors must be surfaced.
|
||||||
|
|
||||||
Old examples below may mention `std.crypto.random`; use the `std.Io` patterns above in new 0.16 code.
|
The examples below use those `std.Io` patterns for current Zig 0.16 code.
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
|
|
@ -37,10 +37,8 @@ Old examples below may mention `std.crypto.random`; use the `std.Io` patterns ab
|
||||||
|
|
||||||
```
|
```
|
||||||
Need crypto security?
|
Need crypto security?
|
||||||
├─ Yes → ChaCha (DefaultCsprng) or Ascon
|
|- Yes -> use `io.random`/`io.randomSecure`, or seed ChaCha/Ascon securely
|
||||||
└─ No → Need speed?
|
`- No -> use Xoshiro256 (DefaultPrng), Sfc64, RomuTrio, or Pcg
|
||||||
├─ Yes → Xoshiro256 (DefaultPrng), Sfc64, or RomuTrio
|
|
||||||
└─ No → Pcg (smaller state), Xoroshiro128
|
|
||||||
```
|
```
|
||||||
|
|
||||||
| PRNG | State | Output | Use Case |
|
| PRNG | State | Output | Use Case |
|
||||||
|
|
@ -50,8 +48,8 @@ Need crypto security?
|
||||||
| `Pcg` | 128-bit | 32-bit | Compact, statistically excellent |
|
| `Pcg` | 128-bit | 32-bit | Compact, statistically excellent |
|
||||||
| `Sfc64` | 256-bit | 64-bit | Very fast |
|
| `Sfc64` | 256-bit | 64-bit | Very fast |
|
||||||
| `RomuTrio` | 192-bit | 64-bit | Fast, small code size |
|
| `RomuTrio` | 192-bit | 64-bit | Fast, small code size |
|
||||||
| `Isaac64` | 8KB | 64-bit | Cryptographic-ish (prefer ChaCha) |
|
| `Isaac64` | 8KB | 64-bit | Non-default PRNG; do not select it as the documented CSPRNG path |
|
||||||
| `ChaCha` | 512-bit | stream | CSPRNG, forward secure |
|
| `ChaCha` | Internal state | stream | CSPRNG, forward secure |
|
||||||
| `Ascon` | 320-bit | stream | CSPRNG, lightweight |
|
| `Ascon` | 320-bit | stream | CSPRNG, lightweight |
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
@ -79,14 +77,13 @@ pub fn main() void {
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main(init: std.process.Init) void {
|
||||||
// Use std.crypto.random for system entropy
|
const io = init.io;
|
||||||
const secure = std.crypto.random;
|
|
||||||
|
|
||||||
var key: [32]u8 = undefined;
|
var key: [32]u8 = undefined;
|
||||||
secure.bytes(&key); // fill with cryptographically secure random bytes
|
io.random(&key); // process CSPRNG stream
|
||||||
|
|
||||||
const token = secure.int(u64);
|
var source: std.Random.IoSource = .{ .io = io };
|
||||||
|
const token = source.interface().int(u64);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -94,7 +91,7 @@ pub fn main() void {
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var seed: u64 = undefined;
|
var seed: u64 = undefined;
|
||||||
std.crypto.random.bytes(std.mem.asBytes(&seed));
|
io.random(std.mem.asBytes(&seed));
|
||||||
var prng = std.Random.DefaultPrng.init(seed);
|
var prng = std.Random.DefaultPrng.init(seed);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -115,7 +112,7 @@ prng.jump();
|
||||||
```zig
|
```zig
|
||||||
// Requires 32-byte secret seed
|
// Requires 32-byte secret seed
|
||||||
var secret_seed: [std.Random.ChaCha.secret_seed_length]u8 = undefined;
|
var secret_seed: [std.Random.ChaCha.secret_seed_length]u8 = undefined;
|
||||||
std.crypto.random.bytes(&secret_seed);
|
try io.randomSecure(&secret_seed); // or io.random when process-CSPRNG semantics suffice
|
||||||
|
|
||||||
var csprng = std.Random.ChaCha.init(secret_seed);
|
var csprng = std.Random.ChaCha.init(secret_seed);
|
||||||
const random = csprng.random();
|
const random = csprng.random();
|
||||||
|
|
@ -257,10 +254,13 @@ const int_weights = [_]u32{ 5, 3, 2 };
|
||||||
const int_choice = random.weightedIndex(u32, &int_weights);
|
const int_choice = random.weightedIndex(u32, &int_weights);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Weights must be finite/nonnegative for floating-point use (or valid nonnegative integer weights), their sum must be positive, and accumulation must remain representable. Validate untrusted weight arrays before calling.
|
||||||
|
|
||||||
### Random Element from Slice
|
### Random Element from Slice
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn randomElement(comptime T: type, random: std.Random, slice: []const T) T {
|
fn randomElement(comptime T: type, random: std.Random, slice: []const T) T {
|
||||||
|
std.debug.assert(slice.len != 0);
|
||||||
const index = random.uintLessThan(usize, slice.len);
|
const index = random.uintLessThan(usize, slice.len);
|
||||||
return slice[index];
|
return slice[index];
|
||||||
}
|
}
|
||||||
|
|
@ -272,10 +272,11 @@ const color = randomElement([]const u8, random, &colors);
|
||||||
### Random Sample (Without Replacement)
|
### Random Sample (Without Replacement)
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn sample(comptime T: type, random: std.Random, source: []const T, dest: []T) void {
|
fn sample(comptime T: type, random: std.Random, source: []const T, dest: []T, indices: []usize) void {
|
||||||
|
std.debug.assert(dest.len <= source.len);
|
||||||
|
std.debug.assert(indices.len >= source.len);
|
||||||
// Fisher-Yates partial shuffle
|
// Fisher-Yates partial shuffle
|
||||||
var indices: [source.len]usize = undefined;
|
for (indices[0..source.len], 0..) |*idx, i| idx.* = i;
|
||||||
for (&indices, 0..) |*idx, i| idx.* = i;
|
|
||||||
|
|
||||||
for (dest, 0..) |*d, i| {
|
for (dest, 0..) |*d, i| {
|
||||||
const j = random.intRangeLessThan(usize, i, source.len);
|
const j = random.intRangeLessThan(usize, i, source.len);
|
||||||
|
|
@ -304,10 +305,10 @@ std.debug.assert(prng1.random().int(u64) == prng2.random().int(u64));
|
||||||
```zig
|
```zig
|
||||||
threadlocal var tls_prng: ?std.Random.DefaultPrng = null;
|
threadlocal var tls_prng: ?std.Random.DefaultPrng = null;
|
||||||
|
|
||||||
fn getThreadRandom() std.Random {
|
fn getThreadRandom(io: std.Io) std.Random {
|
||||||
if (tls_prng == null) {
|
if (tls_prng == null) {
|
||||||
var seed: u64 = undefined;
|
var seed: u64 = undefined;
|
||||||
std.crypto.random.bytes(std.mem.asBytes(&seed));
|
io.random(std.mem.asBytes(&seed));
|
||||||
tls_prng = std.Random.DefaultPrng.init(seed);
|
tls_prng = std.Random.DefaultPrng.init(seed);
|
||||||
}
|
}
|
||||||
return tls_prng.?.random();
|
return tls_prng.?.random();
|
||||||
|
|
@ -318,6 +319,7 @@ fn getThreadRandom() std.Random {
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator) ![]std.Random.Xoshiro256 {
|
fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator) ![]std.Random.Xoshiro256 {
|
||||||
|
std.debug.assert(n != 0);
|
||||||
const prngs = try allocator.alloc(std.Random.Xoshiro256, n);
|
const prngs = try allocator.alloc(std.Random.Xoshiro256, n);
|
||||||
|
|
||||||
prngs[0] = std.Random.Xoshiro256.init(base_seed);
|
prngs[0] = std.Random.Xoshiro256.init(base_seed);
|
||||||
|
|
@ -330,6 +332,8 @@ fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The caller owns the returned slice and must free it with the same allocator.
|
||||||
|
|
||||||
### Monte Carlo Simulation
|
### Monte Carlo Simulation
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -357,7 +361,8 @@ fn generatePassword(random: std.Random, buf: []u8) void {
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
var password: [16]u8 = undefined;
|
var password: [16]u8 = undefined;
|
||||||
generatePassword(std.crypto.random, &password);
|
var source: std.Random.IoSource = .{ .io = io };
|
||||||
|
generatePassword(source.interface(), &password);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Gaussian Random with Box-Muller
|
### Gaussian Random with Box-Muller
|
||||||
|
|
@ -406,7 +411,7 @@ const MyPrng = struct {
|
||||||
|
|
||||||
- `DefaultPrng` is `Xoshiro256` - fast, high quality, not cryptographic
|
- `DefaultPrng` is `Xoshiro256` - fast, high quality, not cryptographic
|
||||||
- `DefaultCsprng` is `ChaCha` - cryptographically secure with forward secrecy
|
- `DefaultCsprng` is `ChaCha` - cryptographically secure with forward secrecy
|
||||||
- For crypto: use `std.crypto.random` which provides system entropy
|
- For crypto: use caller-provided `std.Io` randomness, choosing `randomSecure` when fresh external entropy and explicit errors are required
|
||||||
- `uintLessThan`/`intRangeLessThan` may reject values (not constant-time)
|
- `uintLessThan`/`intRangeLessThan` may reject values (not constant-time)
|
||||||
- Use biased variants (`*Biased`) for timing-sensitive applications
|
- Use biased variants (`*Biased`) for timing-sensitive applications
|
||||||
- `jump()` on Xoshiro256 advances 2^128 steps for parallel streams
|
- `jump()` on Xoshiro256 advances 2^128 steps for parallel streams
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
# std.SegmentedList - removed in Zig 0.16.0
|
# Historical std.SegmentedList Reference (Unavailable in Zig 0.16.0)
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
`std.SegmentedList` was removed in Zig 0.16. Do not use the API below in new code. Choose a project-owned segmented container, an arena-backed structure, an index-based `std.ArrayList`, or another stable-handle design depending on the ownership/lifetime requirement.
|
`std.SegmentedList` is not exported by the Zig 0.16 standard library, and there is no compatibility alias with these methods. The material below is retained as a **legacy API inventory** for migration and source archaeology; none of its snippets compile against Zig 0.16 as written. Choose a project-owned segmented container, an arena-backed structure, an index-based `std.ArrayList`, or another stable-handle design depending on the ownership/lifetime requirement.
|
||||||
|
|
||||||
A dynamic list where element pointers remain stable across growth. Unlike ArrayList, appending never invalidates existing pointers. Elements are stored in exponentially-sized segments.
|
Historically, the implementation described here was a dynamic list whose element pointers remained stable across growth. Unlike `ArrayList`, appending did not invalidate existing pointers, and elements were stored in exponentially sized segments. These are historical behavior and layout notes, not Zig 0.16 guarantees.
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
|
|
@ -15,11 +15,11 @@ A dynamic list where element pointers remain stable across growth. Unlike ArrayL
|
||||||
|
|
||||||
## Trade-offs
|
## Trade-offs
|
||||||
|
|
||||||
- Elements not contiguous (most are, but not guaranteed)
|
- Elements were not globally contiguous; each individual segment was contiguous
|
||||||
- O(log n) random access (vs O(1) for ArrayList)
|
- O(log n) random access (vs O(1) for ArrayList)
|
||||||
- Higher per-element overhead
|
- Higher per-element overhead
|
||||||
|
|
||||||
## Initialization
|
## Legacy Initialization
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Without preallocation
|
// Without preallocation
|
||||||
|
|
@ -32,7 +32,7 @@ var list = std.SegmentedList(i32, 16){};
|
||||||
defer list.deinit(allocator);
|
defer list.deinit(allocator);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Basic Operations
|
## Legacy Basic Operations
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Append (pointer remains valid forever)
|
// Append (pointer remains valid forever)
|
||||||
|
|
@ -84,7 +84,7 @@ if (it.peek()) |ptr| {
|
||||||
it.set(50);
|
it.set(50);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Capacity Management
|
## Legacy Capacity Management
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Grow capacity
|
// Grow capacity
|
||||||
|
|
@ -125,7 +125,7 @@ prealloc=4: prealloc: 4 elements (inline)
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common Pattern: Object Pool with Stable References
|
## Legacy Pattern: Object Pool with Stable References
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const Object = struct {
|
const Object = struct {
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ const any_true = @reduce(.Or, mask); // true
|
||||||
|
|
||||||
### suggestVectorLength
|
### suggestVectorLength
|
||||||
|
|
||||||
Query the optimal vector length for the current CPU:
|
Query the suggested vector length for the current target. This is a comptime heuristic: its element type must be comptime-known.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
@ -187,7 +187,7 @@ Returns `null` if scalars are recommended (no SIMD benefit).
|
||||||
|
|
||||||
### suggestVectorLengthForCpu
|
### suggestVectorLengthForCpu
|
||||||
|
|
||||||
Query optimal length for a specific CPU target:
|
Query the suggested length for a specific CPU target. Both the element type and `std.Target.Cpu` argument are comptime parameters:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2;
|
const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2;
|
||||||
|
|
@ -196,7 +196,7 @@ const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2;
|
||||||
**Architecture support:**
|
**Architecture support:**
|
||||||
- **x86**: SSE (128-bit), AVX2 (256-bit), AVX-512 (512-bit)
|
- **x86**: SSE (128-bit), AVX2 (256-bit), AVX-512 (512-bit)
|
||||||
- **ARM**: NEON (128-bit)
|
- **ARM**: NEON (128-bit)
|
||||||
- **AArch64**: NEON (128-bit), SVE (128-bit default)
|
- **AArch64**: SVE (256-bit heuristic), otherwise NEON (128-bit)
|
||||||
- **RISC-V**: V extension (32-bit to 65536-bit via zvl* features)
|
- **RISC-V**: V extension (32-bit to 65536-bit via zvl* features)
|
||||||
- **WebAssembly**: simd128 (128-bit)
|
- **WebAssembly**: simd128 (128-bit)
|
||||||
- **PowerPC**: AltiVec (128-bit)
|
- **PowerPC**: AltiVec (128-bit)
|
||||||
|
|
@ -289,7 +289,7 @@ const result = std.simd.deinterlace(2, interleaved);
|
||||||
|
|
||||||
### extract - Get Subvector
|
### extract - Get Subvector
|
||||||
|
|
||||||
Extract a contiguous slice of elements:
|
Extract a contiguous slice of elements. `first` and `count` are comptime-known:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const vec: @Vector(8, u32) = .{ 0, 1, 2, 3, 4, 5, 6, 7 };
|
const vec: @Vector(8, u32) = .{ 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||||
|
|
@ -299,7 +299,7 @@ const slice = std.simd.extract(vec, 2, 3);
|
||||||
|
|
||||||
### shiftElementsLeft / shiftElementsRight
|
### shiftElementsLeft / shiftElementsRight
|
||||||
|
|
||||||
Shift elements, filling with a value:
|
Shift elements, filling with a value. The shift amount is comptime-known:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
||||||
|
|
@ -315,7 +315,7 @@ const right = std.simd.shiftElementsRight(vec, 2, 999);
|
||||||
|
|
||||||
### rotateElementsLeft / rotateElementsRight
|
### rotateElementsLeft / rotateElementsRight
|
||||||
|
|
||||||
Circular rotation (elements wrap around):
|
Circular rotation (elements wrap around). The rotation amount is comptime-known:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
||||||
|
|
@ -339,7 +339,7 @@ const reversed = std.simd.reverseOrder(vec);
|
||||||
|
|
||||||
### mergeShift
|
### mergeShift
|
||||||
|
|
||||||
Combine two vectors and extract a shifted window:
|
Combine two vectors and extract a shifted window. The shift amount is comptime-known:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const a: @Vector(4, u32) = .{ 1, 2, 3, 4 };
|
const a: @Vector(4, u32) = .{ 1, 2, 3, 4 };
|
||||||
|
|
@ -400,7 +400,7 @@ const count = std.simd.countElementsWithValue(vec, 4); // 3
|
||||||
|
|
||||||
### prefixScan
|
### prefixScan
|
||||||
|
|
||||||
Compute cumulative operations across vector lanes:
|
Compute cumulative operations across vector lanes. This is an O(log N), non-linear associative scan; floating-point results can differ from a scalar left-to-right scan because the grouping and rounding differ.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const vec: @Vector(4, i32) = .{ 11, 23, 9, -21 };
|
const vec: @Vector(4, i32) = .{ 11, 23, 9, -21 };
|
||||||
|
|
@ -443,7 +443,7 @@ const rev = std.simd.prefixScan(.Add, -1, vec);
|
||||||
|
|
||||||
### prefixScanWithFunc
|
### prefixScanWithFunc
|
||||||
|
|
||||||
Use a custom associative function:
|
Use a custom associative function. The callback may return an error union; choose `ErrorType` accordingly and use `try` on `prefixScanWithFunc` to propagate callback errors. Passing `void` selects the non-erroring form:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn myMax(a: @Vector(4, f32), b: @Vector(4, f32)) @Vector(4, f32) {
|
fn myMax(a: @Vector(4, f32), b: @Vector(4, f32)) @Vector(4, f32) {
|
||||||
|
|
@ -576,11 +576,11 @@ fn atan2Simd(y: @Vector(4, f64), x: @Vector(4, f64)) @Vector(4, f64) {
|
||||||
## Performance Notes
|
## Performance Notes
|
||||||
|
|
||||||
- **Optimal vector size:** Use `suggestVectorLength` for portable code; don't hardcode lane counts. Powers of two (2-64) are most efficient
|
- **Optimal vector size:** Use `suggestVectorLength` for portable code; don't hardcode lane counts. Powers of two (2-64) are most efficient
|
||||||
- **Compilation:** Short vectors → single SIMD instruction; long vectors → multiple instructions; no SIMD → scalar fallback
|
- **Compilation:** Lowering depends on the target, vector width, operation, and optimizer; inspect generated code for performance-critical paths
|
||||||
- **Alignment:** Vectors are automatically aligned; use `@alignCast` when loading from byte pointers
|
- **Alignment:** Vectors are automatically aligned; use `@alignCast` when loading from byte pointers
|
||||||
- **Branching:** Replace scalar branches with `@select` for branchless SIMD code. `and`/`or` keywords don't work on bool vectors
|
- **Branching:** Replace scalar branches with `@select` for branchless SIMD code. `and`/`or` keywords don't work on bool vectors
|
||||||
- **Reductions:** `@reduce` operations break SIMD parallelism; minimize their use in hot paths
|
- **Reductions:** `@reduce` operations break SIMD parallelism; minimize their use in hot paths
|
||||||
- **Memory layout:** Prefer Struct-of-Arrays over Array-of-Structs for better vectorization
|
- **Memory layout:** Prefer Struct-of-Arrays over Array-of-Structs for better vectorization
|
||||||
- **Cache tiling:** For large datasets, process in cache-sized chunks (e.g., 64 elements) to maintain data locality
|
- **Cache tiling:** For large datasets, process in cache-sized chunks (e.g., 64 elements) to maintain data locality
|
||||||
- **Fused operations:** Use `@mulAdd(a, b, c)` for `(a * b) + c` - rounds once, more accurate
|
- **Fused operations:** For floating-point `T`, use `@mulAdd(T, a, b, c)` for a fused `(a * b) + c` with one final rounding
|
||||||
- **MIPS limitation:** `interlace` and `prefixScan` don't work on MIPS architecture
|
- **MIPS limitation:** `interlace` and `prefixScan` don't work on MIPS architecture
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
# std.sort
|
# std.sort (Zig 0.16.0)
|
||||||
|
|
||||||
Sorting algorithms and binary search utilities. All sorts are in-place and require no allocator.
|
Sorting algorithms and binary search utilities. All sorts are in-place and require no allocator.
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
| Function | Stable | Complexity | When to Use |
|
| Function | Stable | Worst-case | When to Use |
|
||||||
|----------|--------|------------|-------------|
|
|----------|--------|------------|-------------|
|
||||||
| `block` | Yes | O(n log n) | Default choice when stability matters |
|
| `block` | Yes | O(n log n) | Default choice when stability matters |
|
||||||
| `pdq` | No | O(n log n) | Default choice when stability doesn't matter |
|
| `pdq` | No | O(n log n) | Default choice when stability doesn't matter |
|
||||||
| `insertion` | Yes | O(n²) | Small arrays (<20), nearly sorted data |
|
| `insertion` | Yes | O(n²) | Small arrays or nearly sorted data |
|
||||||
| `heap` | No | O(n log n) | Guaranteed worst-case, no recursion |
|
| `heap` | No | O(n log n) | Guaranteed worst-case, no recursion |
|
||||||
|
|
||||||
## Comparator Functions
|
## Comparator Functions
|
||||||
|
|
@ -33,7 +33,7 @@ const desc_i32 = std.sort.desc(i32); // descending
|
||||||
```zig
|
```zig
|
||||||
var items = [_]i32{ 5, 2, 8, 1, 9 };
|
var items = [_]i32{ 5, 2, 8, 1, 9 };
|
||||||
|
|
||||||
// Unstable sort (fastest general-purpose)
|
// General-purpose unstable sort
|
||||||
std.sort.pdq(i32, &items, {}, std.sort.asc(i32));
|
std.sort.pdq(i32, &items, {}, std.sort.asc(i32));
|
||||||
// items = [1, 2, 5, 8, 9]
|
// items = [1, 2, 5, 8, 9]
|
||||||
|
|
||||||
|
|
@ -188,7 +188,7 @@ var items = [_]i32{ 5, 2, 8, 1 };
|
||||||
const ctx = Context{ .items = &items };
|
const ctx = Context{ .items = &items };
|
||||||
|
|
||||||
// Sort a subrange using indices
|
// Sort a subrange using indices
|
||||||
std.sort.pdqContext(1, 4, ctx); // sort indices 1..4
|
std.sort.pdqContext(1, 4, ctx); // sort end-exclusive range [1, 4): indices 1, 2, 3
|
||||||
// items = [5, 1, 2, 8]
|
// items = [5, 1, 2, 8]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -220,15 +220,19 @@ std.sort.block(Item, &items, {}, byPriority);
|
||||||
|
|
||||||
## Algorithm Selection
|
## Algorithm Selection
|
||||||
|
|
||||||
- **`pdq`** (Pattern-Defeating Quicksort): Best general-purpose unstable sort. Adapts to input patterns, falls back to heapsort for worst cases.
|
- **`pdq`** (Pattern-Defeating Quicksort): General-purpose unstable sort. Adapts to input patterns, falls back to heapsort for worst cases.
|
||||||
- **`block`**: Best general-purpose stable sort. Preserves relative order of equal elements.
|
- **`block`**: General-purpose stable sort. Preserves relative order of equal elements.
|
||||||
- **`insertion`**: O(n) on nearly sorted data. Use for small arrays or as final pass.
|
- **`insertion`**: O(n) on nearly sorted data. Use for small arrays or as final pass.
|
||||||
- **`heap`**: Guaranteed O(n log n) with O(1) memory. No recursion, predictable performance.
|
- **`heap`**: Guaranteed O(n log n) with O(1) memory. No recursion, predictable performance.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- All sorts are **in-place** with O(1) or O(log n) auxiliary memory
|
- All sorts are **in-place** with O(1) or O(log n) auxiliary memory
|
||||||
- Comparators must define strict weak ordering (if `a < b` then not `b < a`)
|
- Comparators must define a strict weak ordering: irreflexive and transitive,
|
||||||
|
with a transitive equivalence relation for elements where neither compares
|
||||||
|
less than the other. `block` is limited to less-than/greater-than style
|
||||||
|
comparisons as documented by that algorithm.
|
||||||
- `asc`/`desc` helpers work with any type supporting `<` operator
|
- `asc`/`desc` helpers work with any type supporting `<` operator
|
||||||
- Binary search functions require the array to already be sorted
|
- Binary search functions require the array to already be sorted
|
||||||
- `equalRange` is more efficient than calling `lowerBound` + `upperBound` separately
|
- `equalRange` returns both bounds in O(log n); do not assume it is faster than
|
||||||
|
calling the two bound helpers without measurement.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# std.StaticStringMap
|
# std.StaticStringMap
|
||||||
|
|
||||||
Compile-time optimized string lookup. Perfect hash for small, fixed sets of string keys.
|
Compile-time optimized lookup for small, fixed sets of string keys. Keys are grouped by length; lookup quickly rejects absent lengths and then compares candidates in the matching length group. This is not a perfect hash.
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ Compile-time optimized string lookup. Perfect hash for small, fixed sets of stri
|
||||||
- Command/option parsing
|
- Command/option parsing
|
||||||
- Static configuration keys
|
- Static configuration keys
|
||||||
- When string set is known at compile time
|
- When string set is known at compile time
|
||||||
- Very fast O(1) lookups by string length
|
- Fast length-based narrowing, followed by a linear scan of keys with the queried length (plus string comparison cost)
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ if (reserved.has("break")) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Case-Insensitive Lookup
|
## ASCII Case-Insensitive Lookup
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const commands = std.StaticStringMapWithEql(
|
const commands = std.StaticStringMapWithEql(
|
||||||
|
|
@ -86,6 +86,8 @@ defer map.deinit(allocator);
|
||||||
_ = map.get("two"); // 2
|
_ = map.get("two"); // 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Runtime initialization copies the entry arrays and key slices, but not the bytes behind each key slice. Keep the key backing storage alive and unchanged for the lifetime of the map.
|
||||||
|
|
||||||
## Get Index
|
## Get Index
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Tar archive reading and writing. Zig 0.16 file and stream APIs use `std.Io.Dir`,
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
Old examples below may contain 0.15 `std.fs` or `std.io` patterns; translate them to `std.Io` before using in new code. The 0.16 release notes also call out path traversal sanitization in `tar.extract`.
|
The examples below use Zig 0.16's explicit `std.Io` APIs. `tar.extract` also sanitizes archive paths to prevent traversal outside the destination.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Module Structure](#module-structure)
|
- [Module Structure](#module-structure)
|
||||||
|
|
@ -21,8 +21,8 @@ std.tar.Iterator // Iterate over entries in tar archive
|
||||||
std.tar.Writer // Create tar archives
|
std.tar.Writer // Create tar archives
|
||||||
std.tar.Diagnostics // Collect errors during extraction
|
std.tar.Diagnostics // Collect errors during extraction
|
||||||
std.tar.FileKind // .file, .directory, .sym_link
|
std.tar.FileKind // .file, .directory, .sym_link
|
||||||
std.tar.PipeOptions // Options for pipeToFileSystem
|
std.tar.ExtractOptions // Options for extract
|
||||||
std.tar.pipeToFileSystem() // Extract archive to directory
|
std.tar.extract() // Extract archive to directory
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reading Tar Archives
|
## Reading Tar Archives
|
||||||
|
|
@ -36,8 +36,8 @@ const data = @embedFile("archive.tar");
|
||||||
var reader: std.Io.Reader = .fixed(data);
|
var reader: std.Io.Reader = .fixed(data);
|
||||||
|
|
||||||
// Buffers must be provided by caller
|
// Buffers must be provided by caller
|
||||||
var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
|
var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
|
var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
|
|
||||||
var it: std.tar.Iterator = .init(&reader, .{
|
var it: std.tar.Iterator = .init(&reader, .{
|
||||||
.file_name_buffer = &file_name_buffer,
|
.file_name_buffer = &file_name_buffer,
|
||||||
|
|
@ -70,16 +70,16 @@ pub const File = struct {
|
||||||
|
|
||||||
### Reading File Contents
|
### Reading File Contents
|
||||||
|
|
||||||
File content must be read before calling `next()` again:
|
Read or copy file content before calling `next()` again if the content is needed. If content remains unread, `next()` discards it while advancing rather than failing:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
while (try it.next()) |file| {
|
while (try it.next()) |file| {
|
||||||
if (file.kind == .file) {
|
if (file.kind == .file) {
|
||||||
// Option 1: Stream to writer
|
// Option 1: Stream to writer
|
||||||
var buf: [1024]u8 = undefined;
|
var buf: [1024]u8 = undefined;
|
||||||
var output_file = try dir.createFile(file.name, .{});
|
var output_file = try dir.createFile(io, file.name, .{});
|
||||||
defer output_file.close();
|
defer output_file.close(io);
|
||||||
var file_writer = output_file.writer(&buf);
|
var file_writer = output_file.writer(io, &buf);
|
||||||
try it.streamRemaining(file, &file_writer.interface);
|
try it.streamRemaining(file, &file_writer.interface);
|
||||||
try file_writer.interface.flush();
|
try file_writer.interface.flush();
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ pub const Options = struct {
|
||||||
|
|
||||||
## Extracting to Filesystem
|
## Extracting to Filesystem
|
||||||
|
|
||||||
### pipeToFileSystem
|
### extract
|
||||||
|
|
||||||
Extract entire archive to a directory:
|
Extract entire archive to a directory:
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ Extract entire archive to a directory:
|
||||||
const data = @embedFile("archive.tar");
|
const data = @embedFile("archive.tar");
|
||||||
var reader: std.Io.Reader = .fixed(data);
|
var reader: std.Io.Reader = .fixed(data);
|
||||||
|
|
||||||
try std.tar.pipeToFileSystem(std.fs.cwd(), &reader, .{
|
try std.tar.extract(io, std.Io.Dir.cwd(), &reader, .{
|
||||||
.strip_components = 1, // remove leading path component
|
.strip_components = 1, // remove leading path component
|
||||||
.mode_mode = .executable_bit_only,
|
.mode_mode = .executable_bit_only,
|
||||||
.exclude_empty_directories = false,
|
.exclude_empty_directories = false,
|
||||||
|
|
@ -122,19 +122,19 @@ try std.tar.pipeToFileSystem(std.fs.cwd(), &reader, .{
|
||||||
### From File
|
### From File
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const file = try std.fs.cwd().openFile("archive.tar", .{});
|
const file = try std.Io.Dir.cwd().openFile(io, "archive.tar", .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var file_reader = file.reader(&buf);
|
var file_reader = file.reader(io, &buf);
|
||||||
|
|
||||||
try std.tar.pipeToFileSystem(output_dir, &file_reader.interface, .{});
|
try std.tar.extract(io, output_dir, &file_reader.interface, .{});
|
||||||
```
|
```
|
||||||
|
|
||||||
### PipeOptions
|
### ExtractOptions
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub const PipeOptions = struct {
|
pub const ExtractOptions = struct {
|
||||||
strip_components: u32 = 0, // directories to strip from paths
|
strip_components: u32 = 0, // directories to strip from paths
|
||||||
mode_mode: ModeMode = .executable_bit_only,
|
mode_mode: ModeMode = .executable_bit_only,
|
||||||
exclude_empty_directories: bool = false,
|
exclude_empty_directories: bool = false,
|
||||||
|
|
@ -180,24 +180,26 @@ try w.writeLink("latest", "v1.0", .{});
|
||||||
const tar_bytes = output.written();
|
const tar_bytes = output.written();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`setRoot` first emits a directory entry for a non-empty root and then applies that prefix to subsequent entries.
|
||||||
|
|
||||||
### Writing from File
|
### Writing from File
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var output_file = try std.fs.cwd().createFile("archive.tar", .{});
|
var output_file = try std.Io.Dir.cwd().createFile(io, "archive.tar", .{});
|
||||||
defer output_file.close();
|
defer output_file.close(io);
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var file_writer = output_file.writer(&buf);
|
var file_writer = output_file.writer(io, &buf);
|
||||||
|
|
||||||
var w: std.tar.Writer = .{ .underlying_writer = &file_writer.interface };
|
var w: std.tar.Writer = .{ .underlying_writer = &file_writer.interface };
|
||||||
|
|
||||||
// Write file from disk
|
// Write file from disk
|
||||||
var src_file = try std.fs.cwd().openFile("data.txt", .{});
|
var src_file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
|
||||||
defer src_file.close();
|
defer src_file.close(io);
|
||||||
var src_buf: [4096]u8 = undefined;
|
var src_buf: [4096]u8 = undefined;
|
||||||
var src_reader = src_file.reader(&src_buf);
|
var src_reader = src_file.reader(io, &src_buf);
|
||||||
const stat = try src_file.stat();
|
const stat = try src_file.stat(io);
|
||||||
|
|
||||||
try w.writeFile("data.txt", &src_reader, stat.mtime);
|
try w.writeFileTimestamp("data.txt", &src_reader, stat.mtime);
|
||||||
|
|
||||||
try file_writer.interface.flush();
|
try file_writer.interface.flush();
|
||||||
```
|
```
|
||||||
|
|
@ -226,12 +228,16 @@ pub fn writeFileBytes(w: *Writer, sub_path: []const u8, content: []const u8, opt
|
||||||
pub fn writeFileStream(w: *Writer, sub_path: []const u8, size: u64, reader: *std.Io.Reader, options: Options) WriteFileStreamError!void
|
pub fn writeFileStream(w: *Writer, sub_path: []const u8, size: u64, reader: *std.Io.Reader, options: Options) WriteFileStreamError!void
|
||||||
|
|
||||||
// Write file from file reader
|
// Write file from file reader
|
||||||
pub fn writeFile(w: *Writer, sub_path: []const u8, file_reader: *std.fs.File.Reader, stat_mtime: i128) WriteFileError!void
|
pub fn writeFile(w: *Writer, sub_path: []const u8, file_reader: *std.Io.File.Reader, mtime_seconds: u64) WriteFileError!void
|
||||||
|
|
||||||
|
// Convenience overload for std.Io.Timestamp
|
||||||
|
pub fn writeFileTimestamp(w: *Writer, sub_path: []const u8, file_reader: *std.Io.File.Reader, mtime: std.Io.Timestamp) WriteFileError!void
|
||||||
|
|
||||||
// Write symbolic link
|
// Write symbolic link
|
||||||
pub fn writeLink(w: *Writer, sub_path: []const u8, link_name: []const u8, options: Options) Error!void
|
pub fn writeLink(w: *Writer, sub_path: []const u8, link_name: []const u8, options: Options) Error!void
|
||||||
|
|
||||||
// Write two zero blocks (optional, per spec)
|
// Write two zero blocks. The stdlib recommends not calling this: readers must
|
||||||
|
// accept archives without them, and omitting them avoids unnecessary output.
|
||||||
pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void
|
pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -240,7 +246,7 @@ pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void
|
||||||
```zig
|
```zig
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
mode: u32 = 0, // POSIX mode (0 = default: 0o664 for files)
|
mode: u32 = 0, // POSIX mode (0 = default: 0o664 for files)
|
||||||
mtime: u64 = 0, // modification time (0 = current time)
|
mtime: u64 = 0, // seconds since POSIX epoch; zero is written as zero
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -254,7 +260,7 @@ Collect errors instead of failing immediately:
|
||||||
var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator };
|
var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator };
|
||||||
defer diagnostics.deinit();
|
defer diagnostics.deinit();
|
||||||
|
|
||||||
std.tar.pipeToFileSystem(dir, &reader, .{
|
std.tar.extract(io, dir, &reader, .{
|
||||||
.diagnostics = &diagnostics,
|
.diagnostics = &diagnostics,
|
||||||
}) catch |err| {
|
}) catch |err| {
|
||||||
// Some errors are still fatal
|
// Some errors are still fatal
|
||||||
|
|
@ -283,10 +289,12 @@ for (diagnostics.errors.items) |item| {
|
||||||
std.debug.print("Root dir: {s}, entries: {d}\n", .{ diagnostics.root_dir, diagnostics.entries });
|
std.debug.print("Root dir: {s}, entries: {d}\n", .{ diagnostics.root_dir, diagnostics.entries });
|
||||||
```
|
```
|
||||||
|
|
||||||
### Diagnostics.Error Types
|
### Diagnostics.Error Variants
|
||||||
|
|
||||||
```zig
|
```text
|
||||||
pub const Error = union(enum) {
|
// Descriptive shape only: the unsupported_file_type payload contains a
|
||||||
|
// private header-kind enum, so std.tar.Header.Kind is not a public type name.
|
||||||
|
union(enum) {
|
||||||
unable_to_create_sym_link: struct {
|
unable_to_create_sym_link: struct {
|
||||||
code: anyerror,
|
code: anyerror,
|
||||||
file_name: []const u8,
|
file_name: []const u8,
|
||||||
|
|
@ -298,7 +306,7 @@ pub const Error = union(enum) {
|
||||||
},
|
},
|
||||||
unsupported_file_type: struct {
|
unsupported_file_type: struct {
|
||||||
file_name: []const u8,
|
file_name: []const u8,
|
||||||
file_type: Header.Kind,
|
file_type: /* private archive header kind */,
|
||||||
},
|
},
|
||||||
components_outside_stripped_prefix: struct {
|
components_outside_stripped_prefix: struct {
|
||||||
file_name: []const u8,
|
file_name: []const u8,
|
||||||
|
|
@ -311,13 +319,13 @@ pub const Error = union(enum) {
|
||||||
### Extract and Process Archive
|
### Extract and Process Archive
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn extractTar(allocator: Allocator, tar_data: []const u8, dest: std.fs.Dir) !void {
|
fn extractTar(io: std.Io, allocator: Allocator, tar_data: []const u8, dest: std.Io.Dir) !void {
|
||||||
var reader: std.Io.Reader = .fixed(tar_data);
|
var reader: std.Io.Reader = .fixed(tar_data);
|
||||||
|
|
||||||
var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator };
|
var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator };
|
||||||
defer diagnostics.deinit();
|
defer diagnostics.deinit();
|
||||||
|
|
||||||
try std.tar.pipeToFileSystem(dest, &reader, .{
|
try std.tar.extract(io, dest, &reader, .{
|
||||||
.strip_components = 1,
|
.strip_components = 1,
|
||||||
.diagnostics = &diagnostics,
|
.diagnostics = &diagnostics,
|
||||||
});
|
});
|
||||||
|
|
@ -337,8 +345,8 @@ fn listTar(allocator: Allocator, tar_data: []const u8) !void {
|
||||||
_ = allocator;
|
_ = allocator;
|
||||||
var reader: std.Io.Reader = .fixed(tar_data);
|
var reader: std.Io.Reader = .fixed(tar_data);
|
||||||
|
|
||||||
var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
|
var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
|
var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
|
|
||||||
var it: std.tar.Iterator = .init(&reader, .{
|
var it: std.tar.Iterator = .init(&reader, .{
|
||||||
.file_name_buffer = &file_name_buffer,
|
.file_name_buffer = &file_name_buffer,
|
||||||
|
|
@ -365,7 +373,7 @@ fn listTar(allocator: Allocator, tar_data: []const u8) !void {
|
||||||
### Create Archive from Directory
|
### Create Archive from Directory
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []const u8) ![]u8 {
|
fn createTarFromDir(io: std.Io, allocator: Allocator, source_dir: std.Io.Dir, root_name: []const u8) ![]u8 {
|
||||||
var output: std.Io.Writer.Allocating = .init(allocator);
|
var output: std.Io.Writer.Allocating = .init(allocator);
|
||||||
errdefer output.deinit();
|
errdefer output.deinit();
|
||||||
|
|
||||||
|
|
@ -375,21 +383,21 @@ fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []c
|
||||||
var walker = try source_dir.walk(allocator);
|
var walker = try source_dir.walk(allocator);
|
||||||
defer walker.deinit();
|
defer walker.deinit();
|
||||||
|
|
||||||
while (try walker.next()) |entry| {
|
while (try walker.next(io)) |entry| {
|
||||||
switch (entry.kind) {
|
switch (entry.kind) {
|
||||||
.directory => try w.writeDir(entry.path, .{}),
|
.directory => try w.writeDir(entry.path, .{}),
|
||||||
.file => {
|
.file => {
|
||||||
var file = try entry.dir.openFile(entry.basename, .{});
|
var file = try entry.dir.openFile(io, entry.basename, .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var file_reader = file.reader(&buf);
|
var file_reader = file.reader(io, &buf);
|
||||||
const stat = try file.stat();
|
const stat = try file.stat(io);
|
||||||
try w.writeFile(entry.path, &file_reader, stat.mtime);
|
try w.writeFileTimestamp(entry.path, &file_reader, stat.mtime);
|
||||||
},
|
},
|
||||||
.sym_link => {
|
.sym_link => {
|
||||||
var link_buf: [std.fs.max_path_bytes]u8 = undefined;
|
var link_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
const target = try entry.dir.readLink(entry.basename, &link_buf);
|
const target_len = try entry.dir.readLink(io, entry.basename, &link_buf);
|
||||||
try w.writeLink(entry.path, target, .{});
|
try w.writeLink(entry.path, link_buf[0..target_len], .{});
|
||||||
},
|
},
|
||||||
else => {}, // skip special files
|
else => {}, // skip special files
|
||||||
}
|
}
|
||||||
|
|
@ -405,8 +413,8 @@ fn createTarFromDir(allocator: Allocator, source_dir: std.fs.Dir, root_name: []c
|
||||||
fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocator) !?[]u8 {
|
fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocator) !?[]u8 {
|
||||||
var reader: std.Io.Reader = .fixed(tar_data);
|
var reader: std.Io.Reader = .fixed(tar_data);
|
||||||
|
|
||||||
var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
|
var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
|
var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
|
|
||||||
var it: std.tar.Iterator = .init(&reader, .{
|
var it: std.tar.Iterator = .init(&reader, .{
|
||||||
.file_name_buffer = &file_name_buffer,
|
.file_name_buffer = &file_name_buffer,
|
||||||
|
|
@ -427,10 +435,10 @@ fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocat
|
||||||
|
|
||||||
## Supported Features
|
## Supported Features
|
||||||
|
|
||||||
**Formats**: POSIX ustar, GNU long name/link extensions, pax extended headers
|
**Formats**: A deliberately non-comprehensive reader/writer for POSIX ustar, GNU long name/link extensions, and selected PAX attributes (`path`, `linkpath`, and `size`). Global PAX headers are ignored.
|
||||||
|
|
||||||
**Entry types**: Regular files, directories, symbolic links
|
**Entry types**: Regular files, directories, symbolic links
|
||||||
|
|
||||||
**Not supported**: Hard links, device nodes, FIFOs, sparse files (logged via diagnostics)
|
**Not supported**: Hard links, device nodes, FIFOs, sparse files, and other special entries. With diagnostics supplied, unsupported entries can be recorded; without diagnostics, extraction returns `error.TarUnsupportedHeader`.
|
||||||
|
|
||||||
**Path handling**: Automatic prefix/name splitting, GNU extended headers for paths > 256 bytes
|
**Path handling**: Automatic prefix/name splitting, GNU extended headers for paths > 256 bytes
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ try testing.expectEqualSlices(u32, &[_]u32{1, 2, 3}, result_slice);
|
||||||
// Sentinel-terminated slice equality
|
// Sentinel-terminated slice equality
|
||||||
try testing.expectEqualSentinel(u8, 0, expected_cstr, actual_cstr);
|
try testing.expectEqualSentinel(u8, 0, expected_cstr, actual_cstr);
|
||||||
|
|
||||||
// Deep equality (recursively compares structs, arrays, pointers)
|
// Deep equality (recursively compares supported structs, arrays, and pointers)
|
||||||
try testing.expectEqualDeep(expected_struct, actual_struct);
|
try testing.expectEqualDeep(expected_struct, actual_struct);
|
||||||
|
|
||||||
// Float comparison (absolute tolerance)
|
// Float comparison (absolute tolerance)
|
||||||
|
|
@ -68,7 +68,7 @@ try testing.expectApproxEqRel(@as(f64, 100.0), result, 0.01);
|
||||||
```zig
|
```zig
|
||||||
const Point = struct { x: i32, y: i32 };
|
const Point = struct { x: i32, y: i32 };
|
||||||
|
|
||||||
// expectEqual - compares by value for primitives, by identity for pointers
|
// expectEqual - recursively compares structs/arrays, but pointer and slice identity
|
||||||
const p1 = Point{ .x = 1, .y = 2 };
|
const p1 = Point{ .x = 1, .y = 2 };
|
||||||
const p2 = Point{ .x = 1, .y = 2 };
|
const p2 = Point{ .x = 1, .y = 2 };
|
||||||
try testing.expectEqual(p1, p2); // OK - structs compared field-by-field
|
try testing.expectEqual(p1, p2); // OK - structs compared field-by-field
|
||||||
|
|
@ -78,11 +78,13 @@ const a = [_]u8{ 1, 2, 3 };
|
||||||
const b = [_]u8{ 1, 2, 3 };
|
const b = [_]u8{ 1, 2, 3 };
|
||||||
// testing.expectEqual(&a, &b); // FAILS - different pointers
|
// testing.expectEqual(&a, &b); // FAILS - different pointers
|
||||||
|
|
||||||
// expectEqualDeep - follows pointers, compares contents
|
// expectEqualDeep - follows single-item pointers and compares supported contents
|
||||||
try testing.expectEqualDeep(&a, &b); // OK - compares contents
|
try testing.expectEqualDeep(&a, &b); // OK - compares contents
|
||||||
try testing.expectEqualDeep("abc", "abc"); // OK
|
try testing.expectEqualDeep("abc", "abc"); // OK
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`expectEqualDeep` is not cycle-aware: self-referential values can recurse indefinitely. C pointers, many-item pointers, function pointers, and opaque pointers are compared by identity rather than dereferenced.
|
||||||
|
|
||||||
## Error Assertions
|
## Error Assertions
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -164,7 +166,7 @@ std.debug.print("Deallocations: {}\n", .{failing.deallocations});
|
||||||
|
|
||||||
## Exhaustive Allocation Failure Testing
|
## Exhaustive Allocation Failure Testing
|
||||||
|
|
||||||
`checkAllAllocationFailures` tests that your code handles `OutOfMemory` at every allocation point without leaking:
|
`checkAllAllocationFailures` tests that your code handles `OutOfMemory` at every allocation point without leaking. The tested function must take an allocator as its first argument and return `!void`; reset any shared state between runs:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn myFunction(allocator: std.mem.Allocator, size: usize) !void {
|
fn myFunction(allocator: std.mem.Allocator, size: usize) !void {
|
||||||
|
|
@ -190,11 +192,13 @@ test "no leaks on allocation failure" {
|
||||||
2. Runs N more times, failing allocation 0, then 1, then 2...
|
2. Runs N more times, failing allocation 0, then 1, then 2...
|
||||||
3. Verifies `OutOfMemory` is returned and no memory leaked
|
3. Verifies `OutOfMemory` is returned and no memory leaked
|
||||||
|
|
||||||
**Errors returned:**
|
**Harness-specific errors include:**
|
||||||
- `error.MemoryLeakDetected` - allocation failed but memory wasn't freed
|
- `error.MemoryLeakDetected` - allocation failed but memory wasn't freed
|
||||||
- `error.SwallowedOutOfMemoryError` - `OutOfMemory` was caught but not propagated
|
- `error.SwallowedOutOfMemoryError` - `OutOfMemory` was caught but not propagated
|
||||||
- `error.NondeterministicMemoryUsage` - allocation count varies between runs
|
- `error.NondeterministicMemoryUsage` - allocation count varies between runs
|
||||||
|
|
||||||
|
Other errors returned by the tested function are propagated unchanged.
|
||||||
|
|
||||||
## Temporary Directory
|
## Temporary Directory
|
||||||
|
|
||||||
Create an isolated temp directory for file system tests:
|
Create an isolated temp directory for file system tests:
|
||||||
|
|
@ -205,12 +209,18 @@ test "file operations" {
|
||||||
defer tmp.cleanup();
|
defer tmp.cleanup();
|
||||||
|
|
||||||
// Write and read files
|
// Write and read files
|
||||||
var file = try tmp.dir.createFile("test.txt", .{});
|
const io = std.testing.io;
|
||||||
defer file.close();
|
{
|
||||||
try file.writeAll("hello");
|
const file = try tmp.dir.createFile(io, "test.txt", .{});
|
||||||
|
defer file.close(io);
|
||||||
|
var write_buf: [256]u8 = undefined;
|
||||||
|
var file_writer = file.writer(io, &write_buf);
|
||||||
|
try file_writer.interface.writeAll("hello");
|
||||||
|
try file_writer.interface.flush();
|
||||||
|
}
|
||||||
|
|
||||||
// Use tmp.dir for all operations
|
// Use tmp.dir for all operations
|
||||||
const content = try tmp.dir.readFileAlloc(std.testing.allocator, "test.txt", 1024);
|
const content = try tmp.dir.readFileAlloc(io, "test.txt", std.testing.allocator, .limited(1024));
|
||||||
defer std.testing.allocator.free(content);
|
defer std.testing.allocator.free(content);
|
||||||
try testing.expectEqualStrings("hello", content);
|
try testing.expectEqualStrings("hello", content);
|
||||||
}
|
}
|
||||||
|
|
@ -237,10 +247,9 @@ comptime {
|
||||||
std.testing.refAllDecls(@This());
|
std.testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursive version for nested types
|
// refAllDecls visits the immediate declarations of the supplied type.
|
||||||
comptime {
|
// Zig 0.16 has no std.testing.refAllDeclsRecursive helper; recurse through
|
||||||
std.testing.refAllDeclsRecursive(@This());
|
// selected nested types explicitly when that is part of the test's intent.
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Skip Tests
|
## Skip Tests
|
||||||
|
|
@ -265,7 +274,7 @@ test "skip if feature unavailable" {
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
test "with logging" {
|
test "with logging" {
|
||||||
// Only shown when test fails or with --verbose
|
// std.debug.print writes directly to stderr; it is not gated by log_level.
|
||||||
std.debug.print("Debug info: {}\n", .{value});
|
std.debug.print("Debug info: {}\n", .{value});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,8 +303,9 @@ test "fuzz parser" {
|
||||||
try std.testing.fuzz(
|
try std.testing.fuzz(
|
||||||
{}, // context (passed to test function)
|
{}, // context (passed to test function)
|
||||||
struct {
|
struct {
|
||||||
fn testOne(_: void, input: []const u8) !void {
|
fn testOne(_: void, smith: *std.testing.Smith) !void {
|
||||||
// This runs with many different inputs
|
var storage: [4096]u8 = undefined;
|
||||||
|
const input = storage[0..smith.slice(&storage)];
|
||||||
_ = myParser.parse(input) catch |err| switch (err) {
|
_ = myParser.parse(input) catch |err| switch (err) {
|
||||||
error.InvalidInput => return, // expected
|
error.InvalidInput => return, // expected
|
||||||
else => return err,
|
else => return err,
|
||||||
|
|
@ -376,6 +386,5 @@ test "arena for test allocations" {
|
||||||
zig build test # Run all tests
|
zig build test # Run all tests
|
||||||
zig test src/lib.zig # Test single file
|
zig test src/lib.zig # Test single file
|
||||||
zig test --test-filter "name" # Filter by name substring
|
zig test --test-filter "name" # Filter by name substring
|
||||||
zig test -fsummary # Show test summary
|
zig test --help # List options supported by this Zig version
|
||||||
zig test --verbose # Show debug output
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ Useful thread utilities:
|
||||||
```zig
|
```zig
|
||||||
const id = std.Thread.getCurrentId();
|
const id = std.Thread.getCurrentId();
|
||||||
const cpu_count = std.Thread.getCpuCount() catch 1;
|
const cpu_count = std.Thread.getCpuCount() catch 1;
|
||||||
std.Thread.sleep(10 * std.time.ns_per_ms);
|
|
||||||
std.Thread.yield() catch {};
|
std.Thread.yield() catch {};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -49,7 +48,7 @@ Release-note migration map:
|
||||||
| `std.Thread.RwLock` | `std.Io.RwLock` |
|
| `std.Thread.RwLock` | `std.Io.RwLock` |
|
||||||
| `std.Thread.ResetEvent` | `std.Io.Event` |
|
| `std.Thread.ResetEvent` | `std.Io.Event` |
|
||||||
| `std.Thread.WaitGroup` | `std.Io.Group` |
|
| `std.Thread.WaitGroup` | `std.Io.Group` |
|
||||||
| `std.Thread.Futex` | `std.Io.Futex` |
|
| `std.Thread.Futex` operations | `io.futexWait` / `io.futexWaitTimeout` / `io.futexWaitUncancelable` / `io.futexWake` |
|
||||||
|
|
||||||
Lock-free atomics do not need `std.Io`.
|
Lock-free atomics do not need `std.Io`.
|
||||||
|
|
||||||
|
|
@ -68,7 +67,9 @@ fn increment(io: std.Io) !void {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Use uncancelable locking for short critical sections where interruption would corrupt state or skip required cleanup:
|
Use uncancelable locking when waiting to acquire the lock must not be a
|
||||||
|
cancelation point. Cancelation does not interrupt an already-entered critical
|
||||||
|
section:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
mutex.lockUncancelable(io);
|
mutex.lockUncancelable(io);
|
||||||
|
|
@ -186,7 +187,8 @@ Rules:
|
||||||
## Application Guidance
|
## Application Guidance
|
||||||
|
|
||||||
- Queues, allocators, registries, timers, and worker systems that use blocking sync should accept/store `std.Io`.
|
- Queues, allocators, registries, timers, and worker systems that use blocking sync should accept/store `std.Io`.
|
||||||
- Use `lockUncancelable(io)` for queue integrity and allocator metadata updates when cancellation cannot safely interrupt the critical section.
|
- Use `lockUncancelable(io)` only when waiting for queue or allocator locks must
|
||||||
|
not observe cancelation; keep the acquired critical section short.
|
||||||
- Keep OS threads for code that is explicitly thread-owned, such as dedicated worker threads or external API callbacks.
|
- Keep OS threads for code that is explicitly thread-owned, such as dedicated worker threads or external API callbacks.
|
||||||
- Prefer an application's existing scheduler for application work unless the standard `std.Io` task API is explicitly the better fit.
|
- Prefer an application's existing scheduler for application work unless the standard `std.Io` task API is explicitly the better fit.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,9 @@ Zig 0.16 moves time APIs that depend on the runtime behind `std.Io`. Use `std.Io
|
||||||
|
|
||||||
## Migration Summary
|
## Migration Summary
|
||||||
|
|
||||||
Release-note map:
|
Choose an explicit clock when migrating old time APIs. Use
|
||||||
|
`std.Io.Timestamp.now(io, .real)` for wall time and `.boot` or `.awake` for
|
||||||
- `std.time.Instant` -> `std.Io.Timestamp`
|
elapsed-time measurements; `Timestamp` is not a one-for-one timer replacement.
|
||||||
- `std.time.Timer` -> `std.Io.Timestamp`
|
|
||||||
- `std.time.timestamp` -> `std.Io.Timestamp.now`
|
|
||||||
- `{D}` duration formatting -> format `std.Io.Duration` with `{f}`
|
- `{D}` duration formatting -> format `std.Io.Duration` with `{f}`
|
||||||
|
|
||||||
`std.time` still provides constants and calendar helpers such as `ns_per_ms`, `ns_per_s`, and `std.time.epoch`.
|
`std.time` still provides constants and calendar helpers such as `ns_per_ms`, `ns_per_s`, and `std.time.epoch`.
|
||||||
|
|
@ -79,7 +77,8 @@ const elapsed = start.untilNow(io);
|
||||||
|
|
||||||
## Sleeping
|
## Sleeping
|
||||||
|
|
||||||
Use clock-aware durations/timestamps rather than `std.Thread.sleep` when the code should cooperate with the selected `std.Io` backend.
|
Use clock-aware durations/timestamps so sleeping cooperates with the selected
|
||||||
|
`std.Io` backend and propagates cancelation.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
try std.Io.Clock.Duration{
|
try std.Io.Clock.Duration{
|
||||||
|
|
@ -88,8 +87,6 @@ try std.Io.Clock.Duration{
|
||||||
}.sleep(io);
|
}.sleep(io);
|
||||||
```
|
```
|
||||||
|
|
||||||
For low-level OS-thread code that deliberately blocks a thread and is not part of I/O task scheduling, `std.Thread.sleep` is still available.
|
|
||||||
|
|
||||||
## Resolution
|
## Resolution
|
||||||
|
|
||||||
Clock resolution may fail or return zero for unsupported clocks.
|
Clock resolution may fail or return zero for unsupported clocks.
|
||||||
|
|
@ -112,6 +109,9 @@ const seconds: u64 = @intCast(now.toSeconds());
|
||||||
const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = seconds };
|
const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = seconds };
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This conversion is only valid for non-negative Unix timestamps and truncates
|
||||||
|
sub-second precision. Guard pre-epoch values or retain a signed representation.
|
||||||
|
|
||||||
## Application Guidance
|
## Application Guidance
|
||||||
|
|
||||||
- Put common wall-clock timestamp reads behind a shared application helper when consistent clock selection matters.
|
- Put common wall-clock timestamp reads behind a shared application helper when consistent clock selection matters.
|
||||||
|
|
@ -125,4 +125,4 @@ const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = seconds };
|
||||||
- Is the clock choice documented by usage (`.real` for wall time, monotonic clocks for elapsed time)?
|
- Is the clock choice documented by usage (`.real` for wall time, monotonic clocks for elapsed time)?
|
||||||
- Is duration formatting using `{f}` with `std.Io.Duration`?
|
- Is duration formatting using `{f}` with `std.Io.Duration`?
|
||||||
- Does the API receive or store `io` rather than constructing a fallback locally?
|
- Does the API receive or store `io` rather than constructing a fallback locally?
|
||||||
- Is `std.Thread.sleep` only used for deliberate OS-thread blocking?
|
- Does sleeping use a clock-aware `std.Io` API and handle `error.Canceled`?
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ var treap: MyTreap = .{};
|
||||||
|
|
||||||
Nodes are user-managed (intrusive design):
|
Nodes are user-managed (intrusive design):
|
||||||
|
|
||||||
|
An inserted node must remain alive at a stable address until it is removed or
|
||||||
|
replaced; the treap stores raw parent/child pointers.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var nodes: [100]MyTreap.Node = undefined;
|
var nodes: [100]MyTreap.Node = undefined;
|
||||||
|
|
||||||
|
|
@ -55,7 +58,8 @@ if (entry.node) |node| {
|
||||||
// found, node.key == key
|
// found, node.key == key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get entry for existing node (O(1) if you have the node)
|
// O(1), but node must currently belong to this same treap. Passing a stale,
|
||||||
|
// removed, or foreign node is illegal behavior.
|
||||||
var entry = treap.getEntryForExisting(node);
|
var entry = treap.getEntryForExisting(node);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -74,7 +78,7 @@ entry.set(null);
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var entry = treap.getEntryForExisting(old_node);
|
var entry = treap.getEntryForExisting(old_node);
|
||||||
entry.set(&new_node); // replaces old with new (same key)
|
entry.set(&new_node); // copies the old entry's key/links into new_node
|
||||||
```
|
```
|
||||||
|
|
||||||
## Min/Max Access
|
## Min/Max Access
|
||||||
|
|
@ -168,4 +172,5 @@ pub fn main() !void {
|
||||||
- No allocator needed (nodes are user-managed)
|
- No allocator needed (nodes are user-managed)
|
||||||
- Balancing uses randomized priorities (xorshift PRNG)
|
- Balancing uses randomized priorities (xorshift PRNG)
|
||||||
- `node.priority == 0` indicates node is not in treap
|
- `node.priority == 0` indicates node is not in treap
|
||||||
- Entry API allows atomic check-and-modify patterns
|
- Entry API provides a lookup-and-update slot; it does not add synchronization
|
||||||
|
or atomic memory operations.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# std.Tz - TZif Timezone Database Parsing (Zig 0.16.0)
|
# std.Tz - TZif Timezone Database Parsing (Zig 0.16.0)
|
||||||
|
|
||||||
Parse IANA Time Zone Database files (TZif format, RFC 8536). Used to look up UTC offsets, DST rules, and timezone abbreviations.
|
Parse IANA Time Zone Database files (TZif format, RFC 8536) into transitions, time types, leap seconds, and an optional POSIX footer. `std.Tz` stores this data; applications perform their own timestamp lookup and, if needed, interpret future rules from the footer.
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
|
|
@ -20,17 +20,15 @@ When reading timezone files in Zig 0.16, use `std.Io.Dir`/`std.Io.File` and expl
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn main() !void {
|
fn load(io: std.Io, allocator: std.mem.Allocator) !void {
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
|
||||||
defer _ = gpa.deinit();
|
|
||||||
const allocator = gpa.allocator();
|
|
||||||
|
|
||||||
// Open system timezone file
|
// Open system timezone file
|
||||||
const file = try std.fs.openFileAbsolute("/usr/share/zoneinfo/America/New_York", .{});
|
const file = try std.Io.Dir.openFileAbsolute(io, "/usr/share/zoneinfo/America/New_York", .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
var read_buf: [4096]u8 = undefined;
|
||||||
|
var file_reader = file.reader(io, &read_buf);
|
||||||
|
|
||||||
// Parse TZif data
|
// Parse TZif data
|
||||||
var tz = try std.Tz.parse(allocator, file.reader());
|
var tz = try std.Tz.parse(allocator, &file_reader.interface);
|
||||||
defer tz.deinit();
|
defer tz.deinit();
|
||||||
|
|
||||||
// Access timezone information
|
// Access timezone information
|
||||||
|
|
@ -47,9 +45,9 @@ const std = @import("std");
|
||||||
// Embed TZif file at compile time
|
// Embed TZif file at compile time
|
||||||
const tokyo_tz = @embedFile("tz/asia_tokyo.tzif");
|
const tokyo_tz = @embedFile("tz/asia_tokyo.tzif");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn parseEmbedded(allocator: std.mem.Allocator) !void {
|
||||||
var stream = std.io.fixedBufferStream(tokyo_tz);
|
var reader: std.Io.Reader = .fixed(tokyo_tz);
|
||||||
var tz = try std.Tz.parse(std.heap.page_allocator, stream.reader());
|
var tz = try std.Tz.parse(allocator, &reader);
|
||||||
defer tz.deinit();
|
defer tz.deinit();
|
||||||
|
|
||||||
// Use timezone data...
|
// Use timezone data...
|
||||||
|
|
@ -66,7 +64,7 @@ pub const Tz = struct {
|
||||||
leapseconds: []const Leapsecond,
|
leapseconds: []const Leapsecond,
|
||||||
footer: ?[]const u8, // POSIX TZ string for future dates
|
footer: ?[]const u8, // POSIX TZ string for future dates
|
||||||
|
|
||||||
pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz
|
pub fn parse(allocator: std.mem.Allocator, reader: *std.Io.Reader) !Tz
|
||||||
pub fn deinit(self: *Tz) void
|
pub fn deinit(self: *Tz) void
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
@ -115,7 +113,7 @@ pub const Leapsecond = struct {
|
||||||
```zig
|
```zig
|
||||||
fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 {
|
fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 {
|
||||||
// Find the last transition before or at the given timestamp
|
// Find the last transition before or at the given timestamp
|
||||||
var result: ?*const std.Timetype = null;
|
var result: ?*const std.Tz.Timetype = null;
|
||||||
|
|
||||||
for (tz.transitions) |t| {
|
for (tz.transitions) |t| {
|
||||||
if (t.ts <= unix_timestamp) {
|
if (t.ts <= unix_timestamp) {
|
||||||
|
|
@ -134,8 +132,9 @@ fn getUtcOffset(tz: *const std.Tz, unix_timestamp: i64) i32 {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage
|
// Usage for a timestamp supplied by the caller
|
||||||
const offset = getUtcOffset(&tz, std.time.timestamp());
|
const unix_timestamp: i64 = 1_700_000_000;
|
||||||
|
const offset = getUtcOffset(&tz, unix_timestamp);
|
||||||
const local_time = unix_timestamp + offset;
|
const local_time = unix_timestamp + offset;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -143,14 +142,16 @@ const local_time = unix_timestamp + offset;
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool {
|
fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool {
|
||||||
|
var active: ?*const std.Tz.Timetype = null;
|
||||||
for (tz.transitions) |t| {
|
for (tz.transitions) |t| {
|
||||||
if (t.ts <= unix_timestamp) {
|
if (t.ts <= unix_timestamp) {
|
||||||
if (t.timetype.isDst()) return true;
|
active = t.timetype;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
if (active) |tt| return tt.isDst();
|
||||||
|
return tz.timetypes.len > 0 and tz.timetypes[0].isDst();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -158,7 +159,7 @@ fn isDstActive(tz: *const std.Tz, unix_timestamp: i64) bool {
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn getTimezoneAbbrev(tz: *const std.Tz, unix_timestamp: i64) []const u8 {
|
fn getTimezoneAbbrev(tz: *const std.Tz, unix_timestamp: i64) []const u8 {
|
||||||
var result: ?*const std.Timetype = null;
|
var result: ?*const std.Tz.Timetype = null;
|
||||||
|
|
||||||
for (tz.transitions) |t| {
|
for (tz.transitions) |t| {
|
||||||
if (t.ts <= unix_timestamp) {
|
if (t.ts <= unix_timestamp) {
|
||||||
|
|
@ -194,14 +195,16 @@ fn printTransitions(tz: *const std.Tz) void {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parse Errors
|
## Selected Parse Errors
|
||||||
|
|
||||||
| Error | Cause |
|
| Error | Cause |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| `error.BadHeader` | Invalid TZif magic bytes (not "TZif") |
|
| `error.BadHeader` | Invalid TZif magic bytes (not "TZif") |
|
||||||
| `error.BadVersion` | Unsupported TZif version (only 0, 2, 3 supported) |
|
| `error.BadVersion` | Unsupported TZif version (only 0, 2, 3 supported) |
|
||||||
| `error.Malformed` | RFC 8536 validation failure |
|
| `error.Malformed` | RFC 8536 validation failure |
|
||||||
| `error.OverlargeFooter` | POSIX TZ string exceeds 128 bytes |
|
| `error.OverlargeFooter` | Footer exceeded the active reader's capacity while scanning to its newline |
|
||||||
|
|
||||||
|
Allocation failures and `std.Io.Reader` failures such as `EndOfStream` or `ReadFailed` can also propagate.
|
||||||
|
|
||||||
## System Timezone Paths
|
## System Timezone Paths
|
||||||
|
|
||||||
|
|
@ -218,7 +221,7 @@ Common timezone identifiers:
|
||||||
|
|
||||||
## POSIX TZ Footer
|
## POSIX TZ Footer
|
||||||
|
|
||||||
Modern TZif files (v2+) include a POSIX TZ string in the footer for calculating offsets beyond the last transition:
|
Modern TZif files (v2+) may include a POSIX TZ string in the footer. `std.Tz` stores a non-empty footer but does not interpret it to calculate future offsets:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
if (tz.footer) |posix_tz| {
|
if (tz.footer) |posix_tz| {
|
||||||
|
|
|
||||||
|
|
@ -49,14 +49,15 @@ while (it2.nextCodepointSlice()) |slice| {
|
||||||
// slice is []const u8: "h", "é", "l", "l", "o", " ", "世", "界"
|
// slice is []const u8: "h", "é", "l", "l", "o", " ", "世", "界"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek ahead without advancing
|
// Peek ahead without advancing a fresh iterator
|
||||||
const next3 = it.peek(3); // next 3 codepoints as UTF-8 bytes
|
var peek_it = view.iterator();
|
||||||
|
const next3 = peek_it.peek(3); // next 3 codepoints as UTF-8 bytes
|
||||||
|
|
||||||
// Comptime-validated view
|
// Comptime-validated view
|
||||||
const view = unicode.Utf8View.initComptime("hello");
|
const comptime_view = unicode.Utf8View.initComptime("hello");
|
||||||
|
|
||||||
// Unchecked (when you know it's valid)
|
// Unchecked (when you know it's valid)
|
||||||
const view = unicode.Utf8View.initUnchecked(trusted_utf8);
|
const unchecked_view = unicode.Utf8View.initUnchecked(trusted_utf8);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Encoding/Decoding Codepoints
|
## Encoding/Decoding Codepoints
|
||||||
|
|
@ -64,17 +65,17 @@ const view = unicode.Utf8View.initUnchecked(trusted_utf8);
|
||||||
```zig
|
```zig
|
||||||
// Encode codepoint to UTF-8
|
// Encode codepoint to UTF-8
|
||||||
var buf: [4]u8 = undefined;
|
var buf: [4]u8 = undefined;
|
||||||
const len = try unicode.utf8Encode('é', &buf); // len = 2
|
const encoded_len = try unicode.utf8Encode('é', &buf); // len = 2
|
||||||
// buf[0..len] contains UTF-8 bytes
|
// buf[0..encoded_len] contains UTF-8 bytes
|
||||||
|
|
||||||
// Comptime encoding (returns fixed-size array)
|
// Comptime encoding (returns fixed-size array)
|
||||||
const bytes = unicode.utf8EncodeComptime('世'); // [3]u8
|
const bytes = unicode.utf8EncodeComptime('世'); // [3]u8
|
||||||
|
|
||||||
// Get UTF-8 sequence length for a codepoint
|
// Get UTF-8 sequence length for a codepoint
|
||||||
const len = try unicode.utf8CodepointSequenceLength('世'); // 3
|
const codepoint_len = try unicode.utf8CodepointSequenceLength('世'); // 3
|
||||||
|
|
||||||
// Get sequence length from first byte
|
// Get sequence length from first byte
|
||||||
const len = try unicode.utf8ByteSequenceLength(0xE4); // 3 (for 3-byte sequence)
|
const sequence_len = try unicode.utf8ByteSequenceLength(0xE4); // 3 (for 3-byte sequence)
|
||||||
```
|
```
|
||||||
|
|
||||||
## UTF-8 ↔ UTF-16 Conversion
|
## UTF-8 ↔ UTF-16 Conversion
|
||||||
|
|
@ -109,23 +110,25 @@ defer allocator.free(utf8z);
|
||||||
// UTF-8 to UTF-16LE (caller provides buffer)
|
// UTF-8 to UTF-16LE (caller provides buffer)
|
||||||
var utf16_buf: [128]u16 = undefined;
|
var utf16_buf: [128]u16 = undefined;
|
||||||
const len = try unicode.utf8ToUtf16Le(&utf16_buf, "hello");
|
const len = try unicode.utf8ToUtf16Le(&utf16_buf, "hello");
|
||||||
const utf16 = utf16_buf[0..len];
|
const utf16_result = utf16_buf[0..len];
|
||||||
|
|
||||||
// UTF-16LE to UTF-8 (caller provides buffer)
|
// UTF-16LE to UTF-8 (caller provides buffer)
|
||||||
var utf8_buf: [256]u8 = undefined;
|
var utf8_buf: [256]u8 = undefined;
|
||||||
const len = try unicode.utf16LeToUtf8(&utf8_buf, utf16_data);
|
const utf8_len = try unicode.utf16LeToUtf8(&utf8_buf, utf16_data);
|
||||||
const utf8 = utf8_buf[0..len];
|
const utf8_result = utf8_buf[0..utf8_len];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
These functions assert that the destination buffer is large enough; insufficient capacity is not reported as a recoverable error. Compute or conservatively bound the required capacity first.
|
||||||
|
|
||||||
### ArrayList Conversion
|
### ArrayList Conversion
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var list = std.ArrayList(u16).empty;
|
var list = std.array_list.Managed(u16).init(allocator);
|
||||||
defer list.deinit(allocator);
|
defer list.deinit();
|
||||||
try unicode.utf8ToUtf16LeArrayList(&list, "hello");
|
try unicode.utf8ToUtf16LeArrayList(&list, "hello");
|
||||||
|
|
||||||
var list8 = std.ArrayList(u8).empty;
|
var list8 = std.array_list.Managed(u8).init(allocator);
|
||||||
defer list8.deinit(allocator);
|
defer list8.deinit();
|
||||||
try unicode.utf16LeToUtf8ArrayList(&list8, utf16_data);
|
try unicode.utf16LeToUtf8ArrayList(&list8, utf16_data);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -166,7 +169,8 @@ while (try it.nextCodepoint()) |cp| {
|
||||||
WTF-8 is like UTF-8 but allows unpaired surrogates (for Windows compatibility).
|
WTF-8 is like UTF-8 but allows unpaired surrogates (for Windows compatibility).
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
// Validate WTF-8 (allows surrogates)
|
// Validate the accepted byte encoding. This does not reject paired surrogate
|
||||||
|
// halves, so acceptance alone does not establish well-formed WTF-8.
|
||||||
unicode.wtf8ValidateSlice(data) // bool
|
unicode.wtf8ValidateSlice(data) // bool
|
||||||
|
|
||||||
// WTF-8 iteration
|
// WTF-8 iteration
|
||||||
|
|
@ -178,13 +182,17 @@ while (it.nextCodepoint()) |cp| {
|
||||||
|
|
||||||
// WTF-8 ↔ WTF-16 conversion
|
// WTF-8 ↔ WTF-16 conversion
|
||||||
const wtf8 = try unicode.wtf16LeToWtf8Alloc(allocator, wtf16_data);
|
const wtf8 = try unicode.wtf16LeToWtf8Alloc(allocator, wtf16_data);
|
||||||
|
defer allocator.free(wtf8);
|
||||||
const wtf16 = try unicode.wtf8ToWtf16LeAlloc(allocator, wtf8_data);
|
const wtf16 = try unicode.wtf8ToWtf16LeAlloc(allocator, wtf8_data);
|
||||||
|
defer allocator.free(wtf16);
|
||||||
|
|
||||||
// Convert WTF-8 to UTF-8 (lossy - replaces surrogates with U+FFFD)
|
// Convert WTF-8 to UTF-8 (lossy - replaces surrogates with U+FFFD)
|
||||||
const utf8 = try unicode.wtf8ToUtf8LossyAlloc(allocator, wtf8_data);
|
const utf8 = try unicode.wtf8ToUtf8LossyAlloc(allocator, wtf8_data);
|
||||||
|
defer allocator.free(utf8);
|
||||||
|
|
||||||
// In-place lossy conversion
|
// In-place is supported when input and output are exactly the same slice.
|
||||||
try unicode.wtf8ToUtf8Lossy(buffer, wtf8_data);
|
// Otherwise output must be at least as long as input.
|
||||||
|
try unicode.wtf8ToUtf8Lossy(buffer, buffer);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Formatting
|
## Formatting
|
||||||
|
|
@ -230,7 +238,9 @@ fn callWindowsApi(path: []const u8) !void {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Grapheme-aware truncation
|
### Codepoint-counted truncation
|
||||||
|
|
||||||
|
This does not implement Unicode grapheme segmentation. It can split combining sequences, emoji ZWJ sequences, and other user-perceived characters made from multiple codepoints.
|
||||||
```zig
|
```zig
|
||||||
fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
|
fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
|
||||||
const view = try unicode.Utf8View.init(s);
|
const view = try unicode.Utf8View.init(s);
|
||||||
|
|
@ -246,7 +256,7 @@ fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Error Types
|
## Selected Error Types
|
||||||
|
|
||||||
| Error | Meaning |
|
| Error | Meaning |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
|
|
@ -255,7 +265,7 @@ fn truncateCodepoints(s: []const u8, max_codepoints: usize) ![]const u8 {
|
||||||
| `Utf8InvalidStartByte` | Invalid first byte in sequence |
|
| `Utf8InvalidStartByte` | Invalid first byte in sequence |
|
||||||
| `Utf8ExpectedContinuation` | Missing continuation byte |
|
| `Utf8ExpectedContinuation` | Missing continuation byte |
|
||||||
| `Utf8OverlongEncoding` | Overlong encoding detected |
|
| `Utf8OverlongEncoding` | Overlong encoding detected |
|
||||||
| `Utf8EncodesSurrogateHalf` | Surrogate in UTF-8 (use WTF-8) |
|
| `Utf8CannotEncodeSurrogateHalf` | Attempted to encode a surrogate as UTF-8 (use WTF-8 if intentional) |
|
||||||
| `CodepointTooLarge` | Codepoint > 0x10FFFF |
|
| `CodepointTooLarge` | Codepoint > 0x10FFFF |
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/releas
|
||||||
|
|
||||||
URI parsing remains mostly independent of the I/O migration. For network or file operations derived from URIs, pass/use `std.Io`.
|
URI parsing remains mostly independent of the I/O migration. For network or file operations derived from URIs, pass/use `std.Io`.
|
||||||
|
|
||||||
URI parsing and formatting conforming to RFC 3986, with percent-encoding/decoding and resolution support.
|
URI parsing and formatting that roughly adhere to RFC 3986, with percent-encoding/decoding and resolution support. The parser intentionally does not perform complete grammar or character-class validation.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Parsing URIs](#parsing-uris)
|
- [Parsing URIs](#parsing-uris)
|
||||||
|
|
@ -64,6 +64,10 @@ const uri = std.Uri.parse(input) catch |err| switch (err) {
|
||||||
std.debug.print("Port not a valid u16\n", .{});
|
std.debug.print("Port not a valid u16\n", .{});
|
||||||
return err;
|
return err;
|
||||||
},
|
},
|
||||||
|
error.InvalidHostName => {
|
||||||
|
std.debug.print("Invalid host name\n", .{});
|
||||||
|
return err;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -81,8 +85,6 @@ const Uri = struct {
|
||||||
path: Component = Component.empty,
|
path: Component = Component.empty,
|
||||||
query: ?Component = null,
|
query: ?Component = null,
|
||||||
fragment: ?Component = null,
|
fragment: ?Component = null,
|
||||||
|
|
||||||
pub const host_name_max = 255;
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -106,13 +108,14 @@ const Component = union(enum) {
|
||||||
### Getting Host
|
### Getting Host
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
var buffer: [std.Uri.host_name_max]u8 = undefined;
|
var buffer: [std.Io.net.HostName.max_len]u8 = undefined;
|
||||||
const host = uri.getHost(&buffer) catch |err| switch (err) {
|
const host = uri.getHost(&buffer) catch |err| switch (err) {
|
||||||
error.UriMissingHost => return error.NoHost,
|
error.UriMissingHost => return error.NoHost,
|
||||||
error.UriHostTooLong => return error.HostTooLong,
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The result is a `std.Io.net.HostName`. A URI host is validated during parsing, so the fixed buffer is sized to the hostname limit and `getHost` exposes only `error.UriMissingHost`.
|
||||||
|
|
||||||
With allocation:
|
With allocation:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -152,6 +155,8 @@ const raw = try component.toRaw(&buf); // "hello world"
|
||||||
const raw_alloc = try component.toRawMaybeAlloc(allocator); // "hello world"
|
const raw_alloc = try component.toRawMaybeAlloc(allocator); // "hello world"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`toRawMaybeAlloc` may return the component's original borrowed slice or arena-allocated decoded bytes. Treat the result as tied to both lifetimes and do not free it individually.
|
||||||
|
|
||||||
## Formatting URIs
|
## Formatting URIs
|
||||||
|
|
||||||
### Full URI
|
### Full URI
|
||||||
|
|
@ -236,7 +241,7 @@ const decoded = std.Uri.percentDecodeInPlace(&buffer);
|
||||||
// decoded == "hello world!"
|
// decoded == "hello world!"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Decode Backwards (Safe for Aliasing)
|
### Decode Backwards (Conditionally Safe for Aliasing)
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const input = "%48%65%6C%6C%6F";
|
const input = "%48%65%6C%6C%6F";
|
||||||
|
|
@ -245,6 +250,8 @@ const decoded = std.Uri.percentDecodeBackwards(&output, input);
|
||||||
// decoded == "Hello"
|
// decoded == "Hello"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The output must be large enough. Aliasing is supported only when `output.ptr <= input.ptr`; there is no recoverable undersized-buffer error.
|
||||||
|
|
||||||
### Encode with Component
|
### Encode with Component
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -264,7 +271,7 @@ var buf: [256]u8 = undefined;
|
||||||
var writer: std.Io.Writer = .fixed(&buf);
|
var writer: std.Io.Writer = .fixed(&buf);
|
||||||
|
|
||||||
// Encode with custom character validation
|
// Encode with custom character validation
|
||||||
std.Uri.Component.percentEncode(&writer, "custom data", struct {
|
try std.Uri.Component.percentEncode(&writer, "custom data", struct {
|
||||||
fn isValid(c: u8) bool {
|
fn isValid(c: u8) bool {
|
||||||
return std.ascii.isAlphanumeric(c);
|
return std.ascii.isAlphanumeric(c);
|
||||||
}
|
}
|
||||||
|
|
@ -338,103 +345,101 @@ const name = getQueryParam(uri, "name"); // "alice"
|
||||||
### Build URL with Query Parameters
|
### Build URL with Query Parameters
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn buildUrl(allocator: Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 {
|
fn buildUrl(allocator: std.mem.Allocator, base: []const u8, params: []const [2][]const u8) ![]u8 {
|
||||||
var result: std.ArrayList(u8) = .empty;
|
var result: std.Io.Writer.Allocating = .init(allocator);
|
||||||
defer result.deinit(allocator);
|
errdefer result.deinit();
|
||||||
|
|
||||||
try result.appendSlice(allocator, base);
|
try result.writer.writeAll(base);
|
||||||
|
|
||||||
for (params, 0..) |param, i| {
|
for (params, 0..) |param, i| {
|
||||||
try result.append(allocator, if (i == 0) '?' else '&');
|
try result.writer.writeByte(if (i == 0) '?' else '&');
|
||||||
|
try (std.Uri.Component{ .raw = param[0] }).formatEscaped(&result.writer);
|
||||||
// Encode key
|
try result.writer.writeByte('=');
|
||||||
for (param[0]) |c| {
|
try (std.Uri.Component{ .raw = param[1] }).formatEscaped(&result.writer);
|
||||||
if (std.Uri.isUnreserved(c)) {
|
|
||||||
try result.append(allocator, c);
|
|
||||||
} else {
|
|
||||||
try result.appendSlice(allocator, try std.fmt.allocPrint(allocator, "%{X:0>2}", .{c}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try result.append(allocator, '=');
|
return result.toOwnedSlice();
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Normalize URI
|
### Serialize a Parsed URI
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn normalizeUri(allocator: Allocator, uri_str: []const u8) ![]u8 {
|
fn serializeUri(allocator: std.mem.Allocator, uri_str: []const u8) ![]u8 {
|
||||||
const uri = try std.Uri.parse(uri_str);
|
const uri = try std.Uri.parse(uri_str);
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var writer: std.Io.Writer = .fixed(&buf);
|
var writer: std.Io.Writer = .fixed(&buf);
|
||||||
|
|
||||||
// Format with all components to normalize encoding
|
// Parsed percent-encoded components are preserved when formatted.
|
||||||
try uri.format(&writer);
|
try uri.format(&writer);
|
||||||
|
|
||||||
return try allocator.dupe(u8, writer.buffered());
|
return try allocator.dupe(u8, writer.buffered());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This is serialization, not general normalization: existing escape spelling is preserved. Formatting with all components also emits `/` for an empty included path.
|
||||||
|
|
||||||
### Validate URI
|
### Validate URI
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn isValidUri(str: []const u8) bool {
|
fn isAcceptedByUriParser(str: []const u8) bool {
|
||||||
_ = std.Uri.parse(str) catch return false;
|
_ = std.Uri.parse(str) catch return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isValidHttpUri(str: []const u8) bool {
|
fn hasHttpSchemeAndHost(str: []const u8) bool {
|
||||||
const uri = std.Uri.parse(str) catch return false;
|
const uri = std.Uri.parse(str) catch return false;
|
||||||
return std.mem.eql(u8, uri.scheme, "http") or std.mem.eql(u8, uri.scheme, "https");
|
return uri.host != null and
|
||||||
|
(std.mem.eql(u8, uri.scheme, "http") or std.mem.eql(u8, uri.scheme, "https"));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
These remain lightweight parser/scheme checks, not complete URI or HTTP-URL validation.
|
||||||
|
|
||||||
### Join Path Segments
|
### Join Path Segments
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn joinPath(allocator: Allocator, base_uri: std.Uri, segments: []const []const u8) !std.Uri {
|
const OwnedUri = struct {
|
||||||
var path: std.ArrayList(u8) = .empty;
|
value: std.Uri,
|
||||||
defer path.deinit(allocator);
|
path_storage: []u8,
|
||||||
|
|
||||||
// Start with base path (remove trailing slash if any)
|
fn deinit(self: *OwnedUri, allocator: std.mem.Allocator) void {
|
||||||
const base_path = base_uri.path.percent_encoded;
|
allocator.free(self.path_storage);
|
||||||
if (base_path.len > 0 and base_path[base_path.len - 1] == '/') {
|
self.* = undefined;
|
||||||
try path.appendSlice(allocator, base_path[0 .. base_path.len - 1]);
|
|
||||||
} else {
|
|
||||||
try path.appendSlice(allocator, base_path);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Append segments
|
fn joinPath(allocator: std.mem.Allocator, base_uri: std.Uri, segments: []const []const u8) !OwnedUri {
|
||||||
|
var path: std.Io.Writer.Allocating = .init(allocator);
|
||||||
|
errdefer path.deinit();
|
||||||
|
|
||||||
|
// Preserve/encode the existing component according to path rules.
|
||||||
|
try base_uri.path.formatPath(&path.writer);
|
||||||
|
|
||||||
|
// Each segment is escaped as a segment, so '/' inside a segment becomes %2F.
|
||||||
for (segments) |seg| {
|
for (segments) |seg| {
|
||||||
try path.append(allocator, '/');
|
const current = path.written();
|
||||||
try path.appendSlice(allocator, seg);
|
if (current.len == 0 or current[current.len - 1] != '/')
|
||||||
|
try path.writer.writeByte('/');
|
||||||
|
try (std.Uri.Component{ .raw = seg }).formatEscaped(&path.writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const owned_path = try path.toOwnedSlice();
|
||||||
var result = base_uri;
|
var result = base_uri;
|
||||||
result.path = .{ .percent_encoded = try path.toOwnedSlice(allocator) };
|
result.path = .{ .percent_encoded = owned_path };
|
||||||
result.query = null;
|
result.query = null;
|
||||||
result.fragment = null;
|
result.fragment = null;
|
||||||
return result;
|
return .{ .value = result, .path_storage = owned_path };
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The returned URI owns its assembled path; call `OwnedUri.deinit` after use.
|
||||||
|
|
||||||
### Extract Base URL
|
### Extract Base URL
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn getBaseUrl(allocator: Allocator, uri: std.Uri) ![]u8 {
|
fn getBaseUrl(allocator: std.mem.Allocator, uri: std.Uri) ![]u8 {
|
||||||
var buf: [1024]u8 = undefined;
|
var buf: [1024]u8 = undefined;
|
||||||
var writer: std.Io.Writer = .fixed(&buf);
|
var writer: std.Io.Writer = .fixed(&buf);
|
||||||
|
|
||||||
|
|
@ -461,6 +466,7 @@ pub const ParseError = error{
|
||||||
UnexpectedCharacter, // Invalid character in URI component
|
UnexpectedCharacter, // Invalid character in URI component
|
||||||
InvalidFormat, // Malformed URI structure
|
InvalidFormat, // Malformed URI structure
|
||||||
InvalidPort, // Port not a valid u16
|
InvalidPort, // Port not a valid u16
|
||||||
|
InvalidHostName, // Host did not pass std.Io.net.HostName validation
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -477,7 +483,6 @@ pub const ResolveInPlaceError = ParseError || error{
|
||||||
```zig
|
```zig
|
||||||
// getHost errors
|
// getHost errors
|
||||||
error.UriMissingHost // URI has no host component
|
error.UriMissingHost // URI has no host component
|
||||||
error.UriHostTooLong // Host exceeds host_name_max (255)
|
|
||||||
|
|
||||||
// toRaw errors
|
// toRaw errors
|
||||||
error.NoSpaceLeft // Buffer too small for decoded string
|
error.NoSpaceLeft // Buffer too small for decoded string
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
Utilities for parsing, tokenizing, and working with Zig source code. Used for tooling, linters, formatters, and custom analysis.
|
Utilities for parsing, tokenizing, and working with Zig source code. Used for tooling, linters, formatters, and custom analysis.
|
||||||
|
|
||||||
|
These compiler-distributed utilities have no API-stability guarantee. Treat this page as a Zig 0.16-specific reference and expect tooling code to need updates across Zig releases.
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
When tooling reads/writes files or buffers output in Zig 0.16, use `std.Io.Reader`, `std.Io.Writer`, `std.Io.Dir`, and explicit `std.Io`. When searching source text, prefer `std.mem.find*` and `std.mem.cut*` APIs.
|
When tooling reads/writes files or buffers output in Zig 0.16, use `std.Io.Reader`, `std.Io.Writer`, `std.Io.Dir`, and explicit `std.Io`. When searching source text, prefer `std.mem.find*` and `std.mem.cut*` APIs.
|
||||||
|
|
@ -32,7 +34,7 @@ pub fn analyzeSource(allocator: std.mem.Allocator, source: [:0]const u8) !void {
|
||||||
if (tree.errors.len > 0) {
|
if (tree.errors.len > 0) {
|
||||||
for (tree.errors) |err| {
|
for (tree.errors) |err| {
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
var w: std.io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
try tree.renderError(err, &w);
|
try tree.renderError(err, &w);
|
||||||
std.debug.print("Error: {s}\n", .{w.buffered()});
|
std.debug.print("Error: {s}\n", .{w.buffered()});
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +166,7 @@ defer allocator.free(formatted);
|
||||||
|
|
||||||
// Or render to writer
|
// Or render to writer
|
||||||
var buf: [8192]u8 = undefined;
|
var buf: [8192]u8 = undefined;
|
||||||
var writer = std.fs.File.stdout().writer(&buf);
|
var writer = std.Io.File.stdout().writer(io, &buf);
|
||||||
try tree.render(allocator, &writer.interface, .{});
|
try tree.render(allocator, &writer.interface, .{});
|
||||||
try writer.interface.flush();
|
try writer.interface.flush();
|
||||||
```
|
```
|
||||||
|
|
@ -195,8 +197,19 @@ const token_tag = tree.tokenTag(token_index);
|
||||||
for (tree.rootDecls()) |decl| {
|
for (tree.rootDecls()) |decl| {
|
||||||
switch (tree.nodeTag(decl)) {
|
switch (tree.nodeTag(decl)) {
|
||||||
.fn_decl => handleFunction(tree, decl),
|
.fn_decl => handleFunction(tree, decl),
|
||||||
.global_var_decl, .simple_var_decl => handleVariable(tree, decl),
|
.global_var_decl, .simple_var_decl, .local_var_decl, .aligned_var_decl => {
|
||||||
.container_decl, .container_decl_two => handleStruct(tree, decl),
|
// A normal top-level `const Name = struct { ... };` is a variable
|
||||||
|
// declaration. Inspect its initializer to find the container expression.
|
||||||
|
const var_decl = tree.fullVarDecl(decl).?;
|
||||||
|
if (var_decl.ast.init_node.unwrap()) |init_node| {
|
||||||
|
var container_buf: [2]std.zig.Ast.Node.Index = undefined;
|
||||||
|
if (tree.fullContainerDecl(&container_buf, init_node)) |container| {
|
||||||
|
handleStruct(tree, init_node, container);
|
||||||
|
} else {
|
||||||
|
handleVariable(tree, decl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -499,7 +512,7 @@ if (tree.errors.len > 0) {
|
||||||
|
|
||||||
// Format error message
|
// Format error message
|
||||||
var buf: [512]u8 = undefined;
|
var buf: [512]u8 = undefined;
|
||||||
var w: std.io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
try tree.renderError(err, &w);
|
try tree.renderError(err, &w);
|
||||||
|
|
||||||
std.debug.print("{s}:{d}:{d}: error: {s}\n", .{
|
std.debug.print("{s}:{d}:{d}: error: {s}\n", .{
|
||||||
|
|
@ -530,7 +543,7 @@ var bundle = try wip_errors.toOwnedBundle("");
|
||||||
defer bundle.deinit(allocator);
|
defer bundle.deinit(allocator);
|
||||||
|
|
||||||
// Render to stderr
|
// Render to stderr
|
||||||
bundle.renderToStdErr(.{ .ttyconf = .no_color });
|
try bundle.renderToStderr(io, .{}, .off);
|
||||||
|
|
||||||
// Or iterate errors
|
// Or iterate errors
|
||||||
for (bundle.getMessages()) |msg_idx| {
|
for (bundle.getMessages()) |msg_idx| {
|
||||||
|
|
@ -578,7 +591,7 @@ switch (result) {
|
||||||
```zig
|
```zig
|
||||||
// Format identifier, escaping if needed
|
// Format identifier, escaping if needed
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
var w: std.io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
try w.print("{f}", .{std.zig.fmtId("while")}); // @"while"
|
try w.print("{f}", .{std.zig.fmtId("while")}); // @"while"
|
||||||
try w.print("{f}", .{std.zig.fmtId("hello")}); // hello
|
try w.print("{f}", .{std.zig.fmtId("hello")}); // hello
|
||||||
try w.print("{f}", .{std.zig.fmtId("123abc")}); // @"123abc"
|
try w.print("{f}", .{std.zig.fmtId("123abc")}); // @"123abc"
|
||||||
|
|
@ -596,7 +609,7 @@ std.zig.isValidId("a b") // false (contains space)
|
||||||
```zig
|
```zig
|
||||||
// Escape string for Zig string literal
|
// Escape string for Zig string literal
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
var w: std.io.Writer = .fixed(&buf);
|
var w: std.Io.Writer = .fixed(&buf);
|
||||||
try w.print("\"{f}\"", .{std.zig.fmtString("hello\nworld")});
|
try w.print("\"{f}\"", .{std.zig.fmtString("hello\nworld")});
|
||||||
// Output: "hello\nworld"
|
// Output: "hello\nworld"
|
||||||
|
|
||||||
|
|
@ -621,17 +634,18 @@ const hash = std.zig.hashSrc(source);
|
||||||
|
|
||||||
// Compare hashes
|
// Compare hashes
|
||||||
if (std.zig.srcHashEql(hash1, hash2)) {
|
if (std.zig.srcHashEql(hash1, hash2)) {
|
||||||
// Sources are identical
|
// The 128-bit source hashes match (appropriate as a cache signal, but
|
||||||
|
// hash equality is not mathematical proof that the source bytes match).
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Read Source File
|
### Read Source File
|
||||||
```zig
|
```zig
|
||||||
// Read and decode source file (handles UTF-16LE BOM)
|
// Read and decode source file (handles UTF-16LE BOM)
|
||||||
const file = try std.fs.cwd().openFile("source.zig", .{});
|
const file = try std.Io.Dir.cwd().openFile(io, "source.zig", .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var reader = file.reader(&buf);
|
var reader = file.reader(io, &buf);
|
||||||
const source = try std.zig.readSourceFileToEndAlloc(allocator, &reader);
|
const source = try std.zig.readSourceFileToEndAlloc(allocator, &reader);
|
||||||
defer allocator.free(source);
|
defer allocator.free(source);
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ ZIP archive reading and extraction. Zig 0.16 file and stream APIs use `std.Io.Di
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
Old examples below may contain 0.15 `std.fs` or `std.io` patterns; translate them to `std.Io` before using in new code.
|
The examples below use Zig 0.16's explicit `std.Io` file and directory APIs.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Module Structure](#module-structure)
|
- [Module Structure](#module-structure)
|
||||||
|
|
@ -33,11 +33,11 @@ std.zip.CompressionMethod // .store, .deflate
|
||||||
Extract all files from a ZIP archive to a directory:
|
Extract all files from a ZIP archive to a directory:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const file = try std.fs.cwd().openFile("archive.zip", .{});
|
const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var file_reader = file.reader(&buf);
|
var file_reader = file.reader(io, &buf);
|
||||||
|
|
||||||
try std.zip.extract(output_dir, &file_reader, .{});
|
try std.zip.extract(output_dir, &file_reader, .{});
|
||||||
```
|
```
|
||||||
|
|
@ -65,10 +65,12 @@ if (diagnostics.root_dir.len > 0) {
|
||||||
pub const ExtractOptions = struct {
|
pub const ExtractOptions = struct {
|
||||||
allow_backslashes: bool = false, // normalize \ to / in filenames
|
allow_backslashes: bool = false, // normalize \ to / in filenames
|
||||||
diagnostics: ?*Diagnostics = null, // track extraction metadata
|
diagnostics: ?*Diagnostics = null, // track extraction metadata
|
||||||
verify_checksums: bool = false, // TODO: not yet implemented
|
verify_checksums: bool = false, // true currently panics: TODO unimplemented
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Leave `verify_checksums` false in Zig 0.16. Setting it true immediately panics, and normal extraction does not otherwise verify each entry's CRC-32 payload checksum.
|
||||||
|
|
||||||
## Iterating Over Entries
|
## Iterating Over Entries
|
||||||
|
|
||||||
### Iterator API
|
### Iterator API
|
||||||
|
|
@ -76,15 +78,15 @@ pub const ExtractOptions = struct {
|
||||||
For more control, iterate over entries individually:
|
For more control, iterate over entries individually:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const file = try std.fs.cwd().openFile("archive.zip", .{});
|
const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var file_reader = file.reader(&buf);
|
var file_reader = file.reader(io, &buf);
|
||||||
|
|
||||||
var iter = try std.zip.Iterator.init(&file_reader);
|
var iter = try std.zip.Iterator.init(&file_reader);
|
||||||
|
|
||||||
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
|
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
while (try iter.next()) |entry| {
|
while (try iter.next()) |entry| {
|
||||||
// Read filename from archive
|
// Read filename from archive
|
||||||
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
|
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
|
||||||
|
|
@ -99,12 +101,14 @@ while (try iter.next()) |entry| {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Iterator.Entry Structure
|
### Iterator.Entry Fields
|
||||||
|
|
||||||
```zig
|
```text
|
||||||
pub const Entry = struct {
|
// Descriptive field inventory, not a declaration to copy: the concrete type
|
||||||
|
// of `flags` is private to std.zip.
|
||||||
|
struct {
|
||||||
version_needed_to_extract: u16,
|
version_needed_to_extract: u16,
|
||||||
flags: GeneralPurposeFlags,
|
flags: /* private general-purpose-flags type */,
|
||||||
compression_method: CompressionMethod, // .store or .deflate
|
compression_method: CompressionMethod, // .store or .deflate
|
||||||
last_modification_time: u16, // DOS time format
|
last_modification_time: u16, // DOS time format
|
||||||
last_modification_date: u16, // DOS date format
|
last_modification_date: u16, // DOS date format
|
||||||
|
|
@ -124,7 +128,7 @@ pub const Entry = struct {
|
||||||
```zig
|
```zig
|
||||||
var iter = try std.zip.Iterator.init(&file_reader);
|
var iter = try std.zip.Iterator.init(&file_reader);
|
||||||
|
|
||||||
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
|
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
while (try iter.next()) |entry| {
|
while (try iter.next()) |entry| {
|
||||||
// Extract this entry to destination directory
|
// Extract this entry to destination directory
|
||||||
try entry.extract(&file_reader, .{}, &filename_buf, output_dir);
|
try entry.extract(&file_reader, .{}, &filename_buf, output_dir);
|
||||||
|
|
@ -138,7 +142,7 @@ Extract only specific files:
|
||||||
```zig
|
```zig
|
||||||
var iter = try std.zip.Iterator.init(&file_reader);
|
var iter = try std.zip.Iterator.init(&file_reader);
|
||||||
|
|
||||||
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
|
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
while (try iter.next()) |entry| {
|
while (try iter.next()) |entry| {
|
||||||
// Read filename first
|
// Read filename first
|
||||||
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
|
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
|
||||||
|
|
@ -234,15 +238,15 @@ std.zip.end_locator64_sig // "PK\x06\x07"
|
||||||
### Extract ZIP to Directory
|
### Extract ZIP to Directory
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn extractZip(allocator: Allocator, zip_path: []const u8, dest_path: []const u8) !void {
|
fn extractZip(io: std.Io, allocator: std.mem.Allocator, zip_path: []const u8, dest_path: []const u8) !void {
|
||||||
const file = try std.fs.cwd().openFile(zip_path, .{});
|
const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var file_reader = file.reader(&buf);
|
var file_reader = file.reader(io, &buf);
|
||||||
|
|
||||||
var dest = try std.fs.cwd().makeOpenPath(dest_path, .{});
|
var dest = try std.Io.Dir.cwd().createDirPathOpen(io, dest_path, .{});
|
||||||
defer dest.close();
|
defer dest.close(io);
|
||||||
|
|
||||||
var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator };
|
var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator };
|
||||||
defer diagnostics.deinit();
|
defer diagnostics.deinit();
|
||||||
|
|
@ -257,16 +261,16 @@ fn extractZip(allocator: Allocator, zip_path: []const u8, dest_path: []const u8)
|
||||||
### List ZIP Contents
|
### List ZIP Contents
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn listZip(zip_path: []const u8) !void {
|
fn listZip(io: std.Io, zip_path: []const u8) !void {
|
||||||
const file = try std.fs.cwd().openFile(zip_path, .{});
|
const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var file_reader = file.reader(&buf);
|
var file_reader = file.reader(io, &buf);
|
||||||
|
|
||||||
var iter = try std.zip.Iterator.init(&file_reader);
|
var iter = try std.zip.Iterator.init(&file_reader);
|
||||||
|
|
||||||
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
|
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
var total_size: u64 = 0;
|
var total_size: u64 = 0;
|
||||||
var file_count: u64 = 0;
|
var file_count: u64 = 0;
|
||||||
|
|
||||||
|
|
@ -299,13 +303,13 @@ fn listZip(zip_path: []const u8) !void {
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn extractFile(
|
fn extractFile(
|
||||||
file_reader: *std.fs.File.Reader,
|
file_reader: *std.Io.File.Reader,
|
||||||
target_name: []const u8,
|
target_name: []const u8,
|
||||||
dest: std.fs.Dir,
|
dest: std.Io.Dir,
|
||||||
) !bool {
|
) !bool {
|
||||||
var iter = try std.zip.Iterator.init(file_reader);
|
var iter = try std.zip.Iterator.init(file_reader);
|
||||||
|
|
||||||
var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
|
var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
while (try iter.next()) |entry| {
|
while (try iter.next()) |entry| {
|
||||||
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
|
try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
|
||||||
const filename = filename_buf[0..entry.filename_len];
|
const filename = filename_buf[0..entry.filename_len];
|
||||||
|
|
@ -323,12 +327,12 @@ fn extractFile(
|
||||||
### Check if File is ZIP
|
### Check if File is ZIP
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn isZipFile(path: []const u8) !bool {
|
fn isZipFile(io: std.Io, path: []const u8) !bool {
|
||||||
const file = std.fs.cwd().openFile(path, .{}) catch return false;
|
const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch return false;
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
var file_reader = file.reader(&buf);
|
var file_reader = file.reader(io, &buf);
|
||||||
|
|
||||||
_ = std.zip.EndRecord.findFile(&file_reader) catch return false;
|
_ = std.zip.EndRecord.findFile(&file_reader) catch return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -337,7 +341,7 @@ fn isZipFile(path: []const u8) !bool {
|
||||||
|
|
||||||
## Supported Features
|
## Supported Features
|
||||||
|
|
||||||
**Formats**: ZIP, ZIP64 (large files > 4GB, > 65535 entries)
|
**Formats**: ZIP plus core single-disk ZIP64 records/extents. ZIP64 end-record extra data, unsupported versions, and multi-disk/locator variants are rejected, so this is not unrestricted ZIP64 compatibility.
|
||||||
|
|
||||||
**Compression**: Store (uncompressed), Deflate
|
**Compression**: Store (uncompressed), Deflate
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# std.zon - ZON Parsing and Serialization
|
# std.zon - ZON Parsing and Serialization
|
||||||
|
|
||||||
ZON ("Zig Object Notation") parsing and stringification. ZON's grammar is a subset of Zig's syntax. In Zig 0.16, examples that read/write files should use `std.Io.Dir`/`std.Io.File` and an explicit `std.Io`.
|
ZON ("Zig Object Notation") parsing and stringification. Its grammar is a subset of Zig's syntax except for the supported `nan` and `inf` literals. In Zig 0.16, file examples use `std.Io.Dir`/`std.Io.File` and an explicit `std.Io`.
|
||||||
|
|
||||||
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
||||||
|
|
||||||
|
|
@ -70,7 +70,7 @@ pub fn main() !void {
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
const config = try std.zon.parse.fromSlice(Config, allocator, zon_str, null, .{});
|
const config = try std.zon.parse.fromSliceAlloc(Config, allocator, zon_str, null, .{});
|
||||||
defer std.zon.parse.free(allocator, config);
|
defer std.zon.parse.free(allocator, config);
|
||||||
|
|
||||||
// config.name == "server"
|
// config.name == "server"
|
||||||
|
|
@ -85,7 +85,7 @@ pub fn main() !void {
|
||||||
var diag: std.zon.parse.Diagnostics = .{};
|
var diag: std.zon.parse.Diagnostics = .{};
|
||||||
defer diag.deinit(allocator);
|
defer diag.deinit(allocator);
|
||||||
|
|
||||||
const result = std.zon.parse.fromSlice(Config, allocator, zon_str, &diag, .{}) catch |err| {
|
const result = std.zon.parse.fromSliceAlloc(Config, allocator, zon_str, &diag, .{}) catch |err| {
|
||||||
// Print diagnostic errors
|
// Print diagnostic errors
|
||||||
var errors = diag.iterateErrors();
|
var errors = diag.iterateErrors();
|
||||||
while (errors.next()) |parse_err| {
|
while (errors.next()) |parse_err| {
|
||||||
|
|
@ -104,7 +104,7 @@ defer std.zon.parse.free(allocator, result);
|
||||||
### Parse Options
|
### Parse Options
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const result = try std.zon.parse.fromSlice(T, allocator, zon_str, diag, .{
|
const result = try std.zon.parse.fromSliceAlloc(T, allocator, zon_str, diag, .{
|
||||||
// Ignore unknown fields (default: false - errors on unknown)
|
// Ignore unknown fields (default: false - errors on unknown)
|
||||||
.ignore_unknown_fields = true,
|
.ignore_unknown_fields = true,
|
||||||
|
|
||||||
|
|
@ -128,10 +128,12 @@ const version = build_zon.version;
|
||||||
### Free Parsed Values
|
### Free Parsed Values
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const result = try std.zon.parse.fromSlice(T, allocator, zon_str, null, .{});
|
const result = try std.zon.parse.fromSliceAlloc(T, allocator, zon_str, null, .{});
|
||||||
defer std.zon.parse.free(allocator, result);
|
defer std.zon.parse.free(allocator, result);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Use `fromSlice` only when `T` contains no pointers; that result owns no allocations and needs no `free`. Use `fromSliceAlloc` for pointer-containing values such as structs with string slices, and release the result with `std.zon.parse.free`.
|
||||||
|
|
||||||
## Serializing to ZON
|
## Serializing to ZON
|
||||||
|
|
||||||
### Simple Serialization
|
### Simple Serialization
|
||||||
|
|
@ -252,10 +254,12 @@ try tuple.end();
|
||||||
var container = try s.beginStruct(.{
|
var container = try s.beginStruct(.{
|
||||||
.whitespace_style = .{ .wrap = true }, // Always wrap fields
|
.whitespace_style = .{ .wrap = true }, // Always wrap fields
|
||||||
// .whitespace_style = .{ .wrap = false }, // Never wrap (single line)
|
// .whitespace_style = .{ .wrap = false }, // Never wrap (single line)
|
||||||
// .whitespace_style = .{ .fields = 2 }, // Auto-wrap if > 2 fields
|
// .whitespace_style = .{ .fields = 2 }, // caller says this container has 2 fields; no wrap
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For `.fields = n`, the caller supplies the expected field count. Values greater than two select wrapped output; the serializer does not look ahead and count future fields.
|
||||||
|
|
||||||
### Nested Containers
|
### Nested Containers
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -394,42 +398,40 @@ const Config = struct {
|
||||||
debug: bool = false,
|
debug: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn loadConfig(allocator: std.mem.Allocator, path: []const u8) !Config {
|
fn loadConfig(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !Config {
|
||||||
const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
|
const content = std.Io.Dir.cwd().readFileAllocOptions(
|
||||||
|
io,
|
||||||
|
path,
|
||||||
|
allocator,
|
||||||
|
.limited(1024 * 1024),
|
||||||
|
.of(u8),
|
||||||
|
0,
|
||||||
|
) catch |err| switch (err) {
|
||||||
error.FileNotFound => return Config{},
|
error.FileNotFound => return Config{},
|
||||||
else => return err,
|
else => return err,
|
||||||
};
|
};
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
const content = try file.readToEndAllocOptions(
|
|
||||||
allocator,
|
|
||||||
1024 * 1024,
|
|
||||||
null,
|
|
||||||
@alignOf(u8),
|
|
||||||
0, // null terminator
|
|
||||||
);
|
|
||||||
defer allocator.free(content);
|
defer allocator.free(content);
|
||||||
|
|
||||||
return std.zon.parse.fromSlice(Config, allocator, content, null, .{
|
return std.zon.parse.fromSliceAlloc(Config, allocator, content, null, .{
|
||||||
.ignore_unknown_fields = true,
|
.ignore_unknown_fields = true,
|
||||||
.free_on_error = true,
|
.free_on_error = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The caller owns a successfully loaded `Config` and should pass it to `std.zon.parse.free` when finished.
|
||||||
|
|
||||||
### Serialize to File
|
### Serialize to File
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn saveConfig(allocator: std.mem.Allocator, config: Config, path: []const u8) !void {
|
fn saveConfig(io: std.Io, config: Config, path: []const u8) !void {
|
||||||
var aw: std.Io.Writer.Allocating = .init(allocator);
|
const file = try std.Io.Dir.cwd().createFile(io, path, .{});
|
||||||
defer aw.deinit();
|
defer file.close(io);
|
||||||
|
var buffer: [4096]u8 = undefined;
|
||||||
|
var file_writer = file.writer(io, &buffer);
|
||||||
|
|
||||||
try std.zon.stringify.serialize(config, .{ .whitespace = true }, &aw.writer);
|
try std.zon.stringify.serialize(config, .{ .whitespace = true }, &file_writer.interface);
|
||||||
|
try file_writer.interface.flush();
|
||||||
const file = try std.fs.cwd().createFile(path, .{});
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
try file.writeAll(aw.written());
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -472,7 +474,7 @@ try std.zon.stringify.serialize(settings, .{
|
||||||
### Round-Trip ZON Data
|
### Round-Trip ZON Data
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn roundTrip(comptime T: type, allocator: std.mem.Allocator, value: T) !T {
|
fn roundTripAlloc(comptime T: type, allocator: std.mem.Allocator, value: T) !T {
|
||||||
// Serialize
|
// Serialize
|
||||||
var aw: std.Io.Writer.Allocating = .init(allocator);
|
var aw: std.Io.Writer.Allocating = .init(allocator);
|
||||||
defer aw.deinit();
|
defer aw.deinit();
|
||||||
|
|
@ -484,6 +486,8 @@ fn roundTrip(comptime T: type, allocator: std.mem.Allocator, value: T) !T {
|
||||||
const terminated: [:0]const u8 = zon_str[0 .. zon_str.len - 1 :0];
|
const terminated: [:0]const u8 = zon_str[0 .. zon_str.len - 1 :0];
|
||||||
|
|
||||||
// Parse back
|
// Parse back
|
||||||
return std.zon.parse.fromSlice(T, allocator, terminated, null, .{});
|
return std.zon.parse.fromSliceAlloc(T, allocator, terminated, null, .{});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The returned value may own allocations; the caller must eventually call `std.zon.parse.free(allocator, result)`. For a pointer-free `T`, a `fromSlice`-based variant can return a value that needs no cleanup.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Zig Style Guide
|
# Zig Style Guide
|
||||||
|
|
||||||
Official coding conventions from the Zig language reference. These are implemented and enforced by `zig fmt`.
|
Coding conventions based on the Zig language reference. `zig fmt` enforces syntactic layout; naming, documentation, and API-design conventions still require human judgment. Interoperability and established external APIs can justify exceptions.
|
||||||
|
|
||||||
## Naming Conventions
|
## Naming Conventions
|
||||||
|
|
||||||
|
|
@ -169,22 +169,22 @@ fn processRequest(
|
||||||
|
|
||||||
- **Omit redundant information** that's already clear from the name
|
- **Omit redundant information** that's already clear from the name
|
||||||
- **Duplicate information** across similar functions (helps IDEs)
|
- **Duplicate information** across similar functions (helps IDEs)
|
||||||
- Use **"assume"** for invariants that cause *unchecked* illegal behavior when violated
|
- Use **"assume"** for unchecked preconditions whose violation may cause illegal behavior
|
||||||
- Use **"assert"** for invariants that cause *safety-checked* illegal behavior when violated
|
- Use **"assert"** when the implementation actively checks an invariant and panics when it is violated
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
/// Reads a little-endian u32 from the buffer.
|
/// Reads a little-endian u32 from the buffer.
|
||||||
///
|
///
|
||||||
/// Caller must **assume** buffer has at least 4 bytes remaining.
|
/// Caller must provide at least 4 bytes. The slice expression performs a
|
||||||
/// This is not checked and will cause undefined behavior if violated.
|
/// bounds check in safety-enabled builds and panics if the buffer is shorter.
|
||||||
fn readU32Le(buf: []const u8) u32 {
|
fn readU32Le(buf: []const u8) u32 {
|
||||||
return std.mem.readInt(u32, buf[0..4], .little);
|
return std.mem.readInt(u32, buf[0..4], .little);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pops the last element from the list.
|
/// Pops the last element from the list.
|
||||||
///
|
///
|
||||||
/// **Asserts** the list is not empty. In safe modes, returns an error
|
/// **Asserts** the list is not empty. Assertion failure panics; this function
|
||||||
/// or panics if the list is empty.
|
/// has no error return.
|
||||||
fn pop(self: *Self) T {
|
fn pop(self: *Self) T {
|
||||||
std.debug.assert(self.items.len > 0);
|
std.debug.assert(self.items.len > 0);
|
||||||
// ...
|
// ...
|
||||||
|
|
@ -197,7 +197,7 @@ fn pop(self: *Self) T {
|
||||||
- **LF** (`\n`, 0x0a) line endings (CRLF discouraged but tolerated)
|
- **LF** (`\n`, 0x0a) line endings (CRLF discouraged but tolerated)
|
||||||
- End files with a newline
|
- End files with a newline
|
||||||
- No hard tabs (spaces only)
|
- No hard tabs (spaces only)
|
||||||
- `zig fmt` enforces all these conventions
|
- `zig fmt` normalizes source layout; encoding, naming, and documentation rules are separate checks
|
||||||
|
|
||||||
## Applying the Style Guide
|
## Applying the Style Guide
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,6 @@ For ABI-sensitive bindings:
|
||||||
- `@Struct`
|
- `@Struct`
|
||||||
- `@Union`
|
- `@Union`
|
||||||
- `@Enum`
|
- `@Enum`
|
||||||
- `@Opaque`
|
|
||||||
|
|
||||||
Migration rule:
|
Migration rule:
|
||||||
|
|
||||||
|
|
@ -81,7 +80,9 @@ Practical notes:
|
||||||
- Runtime vector indexes are forbidden. Use scalar extraction patterns, compile-time indexes, or restructure the vector operation.
|
- Runtime vector indexes are forbidden. Use scalar extraction patterns, compile-time indexes, or restructure the vector operation.
|
||||||
- Vectors and arrays no longer support in-memory coercion. Make conversions explicit with loads, stores, or element-wise construction.
|
- Vectors and arrays no longer support in-memory coercion. Make conversions explicit with loads, stores, or element-wise construction.
|
||||||
- Unary float builtins forward result type. Code such as `const x: f64 = @sqrt(@floatFromInt(n));` now works as intended.
|
- Unary float builtins forward result type. Code such as `const x: f64 = @sqrt(@floatFromInt(n));` now works as intended.
|
||||||
- `@floor`, `@ceil`, `@round`, and `@trunc` can convert floats to integer result types. `@intFromFloat` is now redundant with `@trunc` and is deprecated.
|
- `@floor`, `@ceil`, `@round`, and `@trunc` perform result-typed floating
|
||||||
|
operations. Use `@intFromFloat` for finite, in-range float-to-integer
|
||||||
|
conversion.
|
||||||
|
|
||||||
### Returning Local Addresses
|
### Returning Local Addresses
|
||||||
|
|
||||||
|
|
@ -132,7 +133,8 @@ Important removals/renames:
|
||||||
- `std.fmt.format` is replaced by `std.Io.Writer.print`.
|
- `std.fmt.format` is replaced by `std.Io.Writer.print`.
|
||||||
- `std.fmt.Formatter` is renamed to `std.fmt.Alt`.
|
- `std.fmt.Formatter` is renamed to `std.fmt.Alt`.
|
||||||
- `std.fmt.FormatOptions` is renamed to `std.fmt.Options`.
|
- `std.fmt.FormatOptions` is renamed to `std.fmt.Options`.
|
||||||
- `std.fmt.bufPrintZ` is renamed to `std.fmt.bufPrintSentinel`.
|
- `std.fmt.bufPrintZ` remains as a deprecated zero-sentinel wrapper; use
|
||||||
|
`std.fmt.bufPrintSentinel` for new code.
|
||||||
- `std.DynLib` removed Windows support; use platform APIs (`LoadLibraryExW`, `GetProcAddress`) directly or through a local abstraction.
|
- `std.DynLib` removed Windows support; use platform APIs (`LoadLibraryExW`, `GetProcAddress`) directly or through a local abstraction.
|
||||||
- `BitSet` and `EnumSet` use decl literals instead of `initEmpty` / `initFull`.
|
- `BitSet` and `EnumSet` use decl literals instead of `initEmpty` / `initFull`.
|
||||||
|
|
||||||
|
|
@ -145,9 +147,11 @@ Important error changes:
|
||||||
|
|
||||||
### I/O as an Interface
|
### I/O as an Interface
|
||||||
|
|
||||||
The core 0.16 rule: all input/output functionality requires an `std.Io` instance.
|
The core 0.16 direction is to route new blocking, OS-facing, and
|
||||||
|
nondeterministic APIs through an `std.Io` instance. Entropy, time, networking,
|
||||||
Anything that might block, interact with the outside world, depend on the OS, wait on concurrency, use entropy, or introduce nondeterminism belongs under the `Io` interface.
|
process, and file operations generally follow this design, but existing stdlib
|
||||||
|
APIs are not uniformly parameterized by `std.Io`; verify the installed
|
||||||
|
signature before applying the rule mechanically.
|
||||||
|
|
||||||
Main patterns:
|
Main patterns:
|
||||||
|
|
||||||
|
|
@ -203,7 +207,8 @@ Map:
|
||||||
|
|
||||||
- `std.Thread.ResetEvent` -> `std.Io.Event`
|
- `std.Thread.ResetEvent` -> `std.Io.Event`
|
||||||
- `std.Thread.WaitGroup` -> `std.Io.Group`
|
- `std.Thread.WaitGroup` -> `std.Io.Group`
|
||||||
- `std.Thread.Futex` -> `std.Io.Futex`
|
- `std.Thread.Futex` operations -> `io.futexWait`, `io.futexWaitTimeout`,
|
||||||
|
`io.futexWaitUncancelable`, and `io.futexWake`
|
||||||
- `std.Thread.Mutex` -> `std.Io.Mutex`
|
- `std.Thread.Mutex` -> `std.Io.Mutex`
|
||||||
- `std.Thread.Condition` -> `std.Io.Condition`
|
- `std.Thread.Condition` -> `std.Io.Condition`
|
||||||
- `std.Thread.Semaphore` -> `std.Io.Semaphore`
|
- `std.Thread.Semaphore` -> `std.Io.Semaphore`
|
||||||
|
|
@ -246,11 +251,10 @@ Use `io.randomSecure(...)` when fresh OS-backed cryptographic entropy is require
|
||||||
|
|
||||||
The old wall-clock/monotonic split is now routed through `std.Io` time types for clock operations that may depend on the runtime.
|
The old wall-clock/monotonic split is now routed through `std.Io` time types for clock operations that may depend on the runtime.
|
||||||
|
|
||||||
Migration highlights from the release notes:
|
Migration requires an explicit clock choice. Use
|
||||||
|
`std.Io.Timestamp.now(io, .real)` for wall-clock timestamps and `.boot` or
|
||||||
- `std.time.Instant` -> `std.Io.Timestamp`
|
`.awake` for elapsed-time measurement; `Timestamp` is not a one-for-one timer
|
||||||
- `std.time.Timer` -> `std.Io.Timestamp`
|
replacement.
|
||||||
- `std.time.timestamp` -> `std.Io.Timestamp.now`
|
|
||||||
|
|
||||||
Application preference:
|
Application preference:
|
||||||
|
|
||||||
|
|
@ -382,7 +386,9 @@ Several low-level stdlib wrappers were removed as part of moving blocking/nondet
|
||||||
|
|
||||||
### Allocators
|
### Allocators
|
||||||
|
|
||||||
`heap.ArenaAllocator` is now thread-safe and lock-free.
|
`heap.ArenaAllocator`'s allocator interface is thread-safe when its child
|
||||||
|
allocator is thread-safe. The stdlib does not promise unconditional lock-free
|
||||||
|
behavior.
|
||||||
|
|
||||||
`heap.ThreadSafe` allocator was removed. If shared allocation still needs protection, choose an allocator that is already thread-safe or place synchronization at the owner of the shared data.
|
`heap.ThreadSafe` allocator was removed. If shared allocation still needs protection, choose an allocator that is already thread-safe or place synchronization at the owner of the shared data.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue