7.4 KiB
7.4 KiB
Agent Solutions Guide: Zig 0.15 Issues
This document catalogs common issues encountered when working with Zig 0.15 and their solutions. Written for AI coding assistants to avoid repeating mistakes.
Critical: ArrayList API Changed in Zig 0.15
Problem
std.ArrayList is now an alias to std.ArrayListUnmanaged in Zig 0.15. The managed version has been removed.
Old (Pre-0.15) Code - DOES NOT WORK
var list = std.ArrayList(u8).init(allocator);
defer list.deinit();
try list.append(item);
New (0.15+) Code - CORRECT
// Empty initialization
var list = std.ArrayList(u8){};
defer list.deinit(allocator);
try list.append(allocator, item);
// Or with capacity
var list = try std.ArrayList(u8).initCapacity(allocator, 100);
defer list.deinit(allocator);
try list.append(allocator, item);
Key Changes
- Initialization: Use
{}orinitCapacity(), notinit() - All methods take allocator:
append(allocator, item)notappend(item) - Deinit takes allocator:
deinit(allocator)notdeinit()
AST Rendering API Changed
Problem
The ast.render() function signature changed in Zig 0.15.
Old Code - DOES NOT WORK
var ast = try std.zig.Ast.parse(allocator, source, .zig);
const output = try ast.render(allocator);
New Code - CORRECT
var ast = try std.zig.Ast.parse(allocator, source, .zig);
const output = try ast.renderAlloc(allocator);
defer allocator.free(output);
The API
renderAlloc(allocator)- Returns allocated stringrender(tree, gpa, writer, fixups)- Low-level version for custom output
Type Conversion: SDL Types to Zig
Pointer Types
| C Type | Zig Type | Notes |
|---|---|---|
const char * |
[*c]const u8 |
C string |
void * |
?*anyopaque |
Nullable any pointer |
const void * |
?*const anyopaque |
Const version |
SDL_Type * |
?*Type |
Nullable pointer to opaque/struct |
const SDL_Type * |
*const Type |
Non-null const pointer |
SDL_Type ** |
?*?*Type |
Output parameter (double pointer) |
SDL_Type *const * |
[*c]*const Type |
Array of const pointers |
Uint32 * |
*u32 |
Output parameter (primitive) |
Key Principles
- Non-nullable by default for const pointers to structs
- Nullable (
?*) for pointers that can be NULL - Use
*not[*c]when you know it's not a C-style array - Double pointers:
?*?*Typefor output parameters
Function Signature Formatting
Trailing Commas
Only use trailing commas for functions with more than 3 parameters. This triggers multi-line formatting.
// 1-3 parameters: single line, no trailing comma
pub fn foo(a: i32, b: i32, c: i32) void {}
// 4+ parameters: multi-line with trailing comma
pub fn bar(
a: i32,
b: i32,
c: i32,
d: i32,
) void {}
Why?
- Trailing comma with no parameters:
(,)is syntax error - Trailing comma with 1-3 params: unnecessary, wastes vertical space
- Trailing comma with 4+ params: makes diffs cleaner, easier to read
Method Organization
Place Methods Inside Opaque Types
Functions where the first parameter is a pointer to an opaque type should be methods:
// Good - method syntax
pub const GPUDevice = opaque {
pub fn destroy(device: *GPUDevice) void {
c.SDL_DestroyGPUDevice(device);
}
};
// Usage: device.destroy()
// Bad - standalone function
pub fn destroyGPUDevice(device: ?*GPUDevice) void {
c.SDL_DestroyGPUDevice(device);
}
// Usage: destroyGPUDevice(device)
Benefits
- Cleaner API:
device.create()vscreateGPUDevice(device) - IDE autocomplete works better
- Namespacing prevents naming conflicts
- More idiomatic Zig
Casting Guidelines
When to Cast
| Scenario | Cast | Example |
|---|---|---|
| Opaque pointer | @ptrCast |
@ptrCast(device) |
| Flags (packed struct) | @bitCast |
@bitCast(flags) |
| Enum to int | @intFromEnum |
@intFromEnum(enum_val) |
| Struct passed by value | None | Just pass it |
| Const pointer to struct | @ptrCast |
@ptrCast(info) |
Don't Over-Cast
// Bad - unnecessary cast for value type
fn setColor(color: FColor) void {
c.SDL_SetColor(@bitCast(color)); // Wrong!
}
// Good - no cast needed
fn setColor(color: FColor) void {
c.SDL_SetColor(color); // Correct
}
StringHashMap Usage
Correct Pattern
var map = std.StringHashMap(ValueType).init(allocator);
defer map.deinit(); // No allocator needed for deinit
try map.put("key", value);
const val = map.get("key");
Iteration
var it = map.keyIterator();
while (it.next()) |key| {
// Use key.*
}
var it = map.valueIterator();
while (it.next()) |value| {
// Use value.* if needed
}
Common Pitfalls
1. Forgetting Allocator in Unmanaged Collections
// Wrong
list.append(item);
// Right
list.append(allocator, item);
2. Using .init() on ArrayList
// Wrong
var list = std.ArrayList(u8).init(allocator);
// Right
var list = std.ArrayList(u8){};
// or
var list = try std.ArrayList(u8).initCapacity(allocator, size);
3. Not Checking AST Errors Before Rendering
// Wrong - will panic if there are errors
const output = try ast.renderAlloc(allocator);
// Right - check first
if (ast.errors.len > 0) {
// Handle errors
return error.ParseError;
}
const output = try ast.renderAlloc(allocator);
4. Incorrect Double Pointer Types
// Wrong - C-style for output params
texture: [*c]*GPUTexture
// Right - Zig optional pointers
texture: ?*?*GPUTexture
Testing Patterns
Simple Test
test "description" {
const result = try someFunction();
try std.testing.expectEqual(expected, result);
}
Test with Allocator
test "with allocator" {
const allocator = std.testing.allocator;
const result = try allocateAndDoSomething(allocator);
defer allocator.free(result);
try std.testing.expectEqualStrings("expected", result);
}
Build System Integration
Adding Parser to Dependencies
// build.zig.zon
.dependencies = .{
.sdl3_parser = .{ .path = "parser/" },
},
// build.zig
const parser_dep = b.dependency("sdl3_parser", .{
.target = target,
.optimize = optimize,
});
const parser_exe = parser_dep.artifact("sdl-parser");
Run Step
const run_parser = b.addRunArtifact(parser_exe);
run_parser.addFileArg(b.path("input.h"));
run_parser.addArg("--output=output.zig");
const step = b.step("generate", "Generate bindings");
step.dependOn(&run_parser.step);
Quick Reference Card
// Collections
var list = std.ArrayList(T){};
defer list.deinit(allocator);
try list.append(allocator, item);
var map = std.StringHashMap(V).init(allocator);
defer map.deinit();
try map.put("key", value);
// AST
var ast = try std.zig.Ast.parse(allocator, source, .zig);
defer ast.deinit(allocator);
const formatted = try ast.renderAlloc(allocator);
defer allocator.free(formatted);
// Type Patterns
?*Type // Nullable pointer
*const Type // Non-null const pointer
?*?*Type // Output parameter
[*c]*const Type // C array of const pointers
// Casts
@ptrCast(ptr) // Pointers
@bitCast(value) // Packed structs, flags
@intFromEnum(e) // Enum to int
// No cast for value types!
Version Info
- Zig Version: 0.15.2
- Date: 2025-01-22
- SDL Version: 3.2.0
References
- Zig 0.15 Release Notes: https://ziglang.org/download/0.15.0/release-notes.html
- Zig Standard Library Docs: https://ziglang.org/documentation/master/std/