| name | description | license |
|---|---|---|
zig |
Ensures highest quality, well-tested, performant, and properly documented Zig code |
MIT |
This skill ensures you write highest quality, well-tested, highly performant, and properly documented Zig code for any project.
Load this skill automatically when:
- Writing or refactoring Zig code
- Implementing algorithms and data structures
- Designing types and APIs
- Managing memory and performance
- Writing tests
- Documenting public APIs
Modern Zig emphasizes:
- Explicitness - Make behavior visible in code
- Safety - Memory safety, no undefined behavior
- Performance - Zero-cost abstractions, no hidden allocations
- Simplicity - Simple, readable code over clever tricks
// Types: PascalCase
pub const URL = struct { ... };
pub const Host = union(enum) { ... };
pub const ParseError = error { ... };
// Functions and variables: snake_case
pub fn parse(allocator: Allocator, input: []const u8) !URL { ... }
pub fn percent_encode(input: []const u8) ![]u8 { ... }
const my_variable: i32 = 42;
const port: ?u16 = null;
// Constants: SCREAMING_SNAKE_CASE
pub const MAX_URL_LENGTH: usize = 1_000_000;
pub const DEFAULT_HTTP_PORT: u16 = 80;
// Private: No special prefix needed (use 'fn' without 'pub')
fn internal_helper() void { ... }// Define clear, specific errors
pub const ParseError = error{
InvalidURL,
InvalidScheme,
InvalidHost,
InvalidPort,
InvalidIPv4,
InvalidIPv6,
};
// Combine with Allocator.Error for functions that allocate
pub fn parse(
allocator: Allocator,
input: []const u8,
) (Allocator.Error || ParseError)!URL {
// Can fail with OutOfMemory OR parsing errors
}const infra = @import("infra");
// defer: ALWAYS runs when scope exits
pub fn process(allocator: Allocator) !Result {
var list = infra.List(u8).init(allocator);
defer list.deinit(); // Runs even on error
try list.append(42);
return computeResult(list);
}
// errdefer: ONLY runs on error
pub fn create(allocator: Allocator) !Resource {
var resource = try Resource.init(allocator);
errdefer resource.deinit(); // Only if we return error
try resource.setup();
return resource; // Success - errdefer doesn't run
}// Caller provides allocator, caller owns result
pub fn operation(allocator: Allocator, input: []const u8) ![]u8 {
const result = try allocator.alloc(u8, input.len);
errdefer allocator.free(result);
// Process...
return result; // Caller must free
}
// Usage
const result = try operation(allocator, input);
defer allocator.free(result); // Caller frees// ✅ GOOD: defer right after allocation
const infra = @import("infra");
pub fn example(allocator: Allocator) !void {
var list = infra.List(u8).init(allocator);
defer list.deinit(); // Declare cleanup immediately
try list.append(42);
// list automatically cleaned up
}
// ❌ BAD: Forgetting defer
pub fn example(allocator: Allocator) !void {
var list = infra.List(u8).init(allocator);
try list.append(42);
// Memory leak! list.deinit() never called
}// Use arena when you have many temporary allocations
pub fn algorithm(allocator: Allocator, input: []const u8) !Result {
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit(); // Free all at once
const temp_allocator = arena.allocator();
// All temporary allocations
const temp1 = try temp_allocator.alloc(u8, 1024);
const temp2 = try temp_allocator.alloc(u8, 2048);
// No need to free individually
// Final result uses original allocator (outlives arena)
const result = try allocator.dupe(Result, computed);
return result;
}In WHATWG codebase, ALWAYS use infra.List instead of std.ArrayList.
This avoids Zig version compatibility issues and provides consistent API across the codebase.
// ✅ GOOD: Use infra.List
const infra = @import("infra");
pub fn operation(allocator: Allocator) !void {
var list = infra.List(*Node).init(allocator);
defer list.deinit(); // No allocator parameter needed
try list.append(node);
// Access elements with index-based iteration
for (0..list.len) |i| {
const item = list.get(i) orelse continue;
// Process item
}
}
// ❌ BAD: Don't use std.ArrayList (Zig 0.15 breaking changes)
pub fn operation(allocator: Allocator) !void {
var list = std.ArrayList(*Node).init(allocator); // ❌ API changed in 0.15
defer list.deinit(allocator); // ❌ Requires allocator in 0.15
try list.append(node);
for (list.items) |item| { // ❌ Don't use .items pattern
// ...
}
}const infra = @import("infra");
// Initialization
var list = infra.List(T).init(allocator);
defer list.deinit(); // No allocator parameter
// Adding items
try list.append(item);
try list.prepend(item);
try list.insert(index, item);
// Accessing items
const item = list.get(index); // Returns ?T
const len = list.len; // Field, not method
// Iteration - Use index-based access
for (0..list.len) |i| {
const item = list.get(i) orelse continue;
// Process item
}
// Removing items
const removed = try list.remove(index); // Returns T
list.clear(); // Clear all items
// ⚠️ NOTE: infra.List does NOT have .items field
// Use index-based iteration insteadOnly use std.ArrayList when:
- Interfacing with external Zig code that requires it
- You need ArrayList-specific methods not in infra.List
If you must use ArrayList, be aware of Zig 0.15 API changes.
// Immutable data: []const u8
pub fn read_only(string: []const u8) usize {
return string.len; // Cannot modify
}
// Mutable data: []u8 or *T
pub fn modify(list: *std.ArrayList(u8)) !void {
try list.append(42); // Can modify
}// ✅ GOOD: Explicit types prevent errors
const count: usize = list.items.len;
const byte: u8 = data[index];
const code_point: u21 = 0x1F600;
// Use type casts when needed
try std.testing.expectEqual(@as(usize, 10), list.items.len);pub const Host = union(enum) {
domain: []const u8,
ipv4: u32,
ipv6: [8]u16,
opaque: []const u8,
pub fn deinit(self: Host, allocator: Allocator) void {
switch (self) {
.domain, .opaque => |s| allocator.free(s),
.ipv4, .ipv6 => {}, // No allocation
}
}
pub fn serialize(self: Host, allocator: Allocator) ![]u8 {
return switch (self) {
.domain => |d| try allocator.dupe(u8, d),
.ipv4 => |ip| try serializeIPv4(allocator, ip),
.ipv6 => |ip| try serializeIPv6(allocator, ip),
.opaque => |o| try allocator.dupe(u8, o),
};
}
};pub const URL = struct {
scheme: []const u8,
host: ?Host,
port: ?u16,
path: []const u8,
allocator: Allocator,
pub fn init(allocator: Allocator) URL {
return .{
.scheme = "",
.host = null,
.port = null,
.path = "",
.allocator = allocator,
};
}
pub fn deinit(self: *URL) void {
self.allocator.free(self.scheme);
if (self.host) |host| host.deinit(self.allocator);
self.allocator.free(self.path);
}
// Methods
pub fn serialize(self: *const URL, allocator: Allocator) ![]u8 {
// Implementation...
}
};// self: *const Type - Read-only methods
pub fn serialize(self: *const URL) ![]u8 { }
pub fn get_scheme(self: *const URL) []const u8 { }
// self: *Type - Mutating methods
pub fn set_host(self: *URL, host: Host) void { }
pub fn update_path(self: *URL, path: []const u8) !void { }
// No self: Static/factory functions
pub fn parse(allocator: Allocator, input: []const u8) !URL { }// Inline small, frequently called functions
pub inline fn is_ascii(byte: u8) bool {
return byte < 0x80;
}
pub inline fn is_ascii_alpha(c: u8) bool {
return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z');
}
// Don't inline large functions - let compiler decide
pub fn complex_parse(input: []const u8) !Result {
// Large function body...
}// ✅ GOOD: Preallocate capacity
pub fn process_items(allocator: Allocator, items: []Item) ![]Result {
var results = try std.ArrayList(Result).initCapacity(
allocator,
items.len
);
errdefer results.deinit();
for (items) |item| {
results.appendAssumeCapacity(process(item)); // No reallocation!
}
return results.toOwnedSlice();
}
// ❌ BAD: Let ArrayList grow incrementally (multiple reallocations)
pub fn process_items(allocator: Allocator, items: []Item) ![]Result {
var results = std.ArrayList(Result).init(allocator);
errdefer results.deinit();
for (items) |item| {
try results.append(process(item)); // May reallocate each time
}
return results.toOwnedSlice();
}// Fast path for ASCII (most common)
pub fn to_lowercase(allocator: Allocator, input: []const u8) ![]u8 {
// Fast path: Check if ASCII-only
var is_ascii_only = true;
for (input) |byte| {
if (byte >= 0x80) {
is_ascii_only = false;
break;
}
}
if (is_ascii_only) {
// Fast ASCII path
const result = try allocator.alloc(u8, input.len);
for (input, 0..) |byte, i| {
result[i] = if (byte >= 'A' and byte <= 'Z')
byte + ('a' - 'A')
else
byte;
}
return result;
}
// Slow path: Unicode handling
return unicode_to_lowercase(allocator, input);
}// ✅ GOOD: Reuse buffer
pub fn transform_multiple(allocator: Allocator, items: [][]const u8) ![][]u8 {
var buffer = std.ArrayList(u8).init(allocator);
defer buffer.deinit();
var results = std.ArrayList([]u8).init(allocator);
errdefer {
for (results.items) |item| allocator.free(item);
results.deinit();
}
for (items) |item| {
buffer.clearRetainingCapacity(); // Reuse allocation!
try process_into(item, &buffer);
try results.append(try buffer.toOwnedSlice());
}
return results.toOwnedSlice();
}// Check cheapest conditions first
pub fn validate(input: []const u8) !void {
// 1. Check length (cheapest)
if (input.len == 0) return error.EmptyInput;
if (input.len > MAX_LENGTH) return error.TooLong;
// 2. Check for invalid bytes (moderate cost)
for (input) |byte| {
if (byte < 0x20 and byte != '\t' and byte != '\n') {
return error.InvalidControlCharacter;
}
}
// 3. Full validation (most expensive)
try validate_utf8(input);
}Every function MUST have tests for:
- Happy path - Normal, expected usage
- Edge cases - Empty input, boundary conditions
- Error cases - Invalid input, error conditions
- Memory safety - No leaks (use std.testing.allocator)
- Spec compliance - Matches WHATWG specification
// Pattern: "type.function - specific behavior"
test "URL.parse - parses simple HTTP URL" { }
test "URL.parse - handles IPv6 address" { }
test "URL.parse - rejects invalid scheme" { }
test "percent_encode - encodes special characters" { }
test "percent_encode - preserves unreserved characters" { }
test "Host.deinit - frees domain name" { }
test "Host.deinit - handles IPv4 without allocation" { }test "URL.parse - parses complete URL" {
// Arrange
const allocator = std.testing.allocator;
const input = "https://example.com:8080/path?query#fragment";
// Act
const url = try URL.parse(allocator, input);
defer url.deinit();
// Assert
try std.testing.expectEqualStrings("https", url.scheme);
try std.testing.expectEqualStrings("example.com", url.host.?.domain);
try std.testing.expectEqual(@as(?u16, 8080), url.port);
try std.testing.expectEqualStrings("/path", url.path);
}// ALWAYS use std.testing.allocator
test "no memory leaks" {
const allocator = std.testing.allocator; // Detects leaks!
var list = std.ArrayList(u8).init(allocator);
defer list.deinit(); // Must clean up
try list.append(42);
// Test passes only if all allocations freed
}
// Test cleanup on error
test "no leaks on error" {
const allocator = std.testing.allocator;
_ = parse(allocator, "invalid") catch |err| {
try std.testing.expectEqual(error.InvalidInput, err);
// Even on error, no leaks allowed
};
}- Read spec completely - Understand algorithm
- Write failing test - Test what you want to implement
- Minimal implementation - Make it compile
- Make test pass - Implement algorithm
- Add edge case tests - Cover all cases
- Refactor - Never modify existing tests!
Public API MUST be documented. Private/temporary code MAY skip documentation.
✅ MUST document:
- Public functions (
pub fn) - Public types (
pub const) - Public constants
- Module-level documentation (
//!)
❌ MAY skip documentation:
- Private functions (
fnwithoutpub) - Temporary/mock implementations
- Internal helper functions
- Test code
//! URL Parsing - WHATWG URL Standard
//!
//! Implements URL parsing, serialization, and manipulation from the
//! WHATWG URL Standard. URLs are parsed into structured components.
//!
//! ## WHATWG Specification
//!
//! - §4 URL parsing: https://url.spec.whatwg.org/#url-parsing
//! - §5 URL serialization: https://url.spec.whatwg.org/#url-serializing
//!
//! ## Examples
//!
//! ```zig
//! const url = try URL.parse(allocator, "https://example.com/path");
//! defer url.deinit();
//!
//! const serialized = try url.serialize(allocator);
//! defer allocator.free(serialized);
//! ```
const std = @import("std");/// Parse a URL string into a URL record.
///
/// Implements WHATWG URL "basic URL parser" per §4.1.
///
/// ## Spec Reference
/// https://url.spec.whatwg.org/#concept-basic-url-parser
///
/// ## Algorithm (URL §4.1)
/// 1. Let url be a new URL.
/// 2. Let state be scheme start state.
/// 3. Parse each code point according to state machine.
/// 4. Return url or failure.
///
/// ## Parameters
/// - `allocator`: Memory allocator for URL components
/// - `input`: URL string to parse
/// - `base`: Optional base URL for relative resolution
///
/// ## Returns
/// Parsed URL record, or error if invalid.
///
/// ## Errors
/// - `error.InvalidScheme`: Scheme is invalid
/// - `error.InvalidHost`: Host parsing failed
/// - `error.OutOfMemory`: Allocation failed
///
/// ## Example
/// ```zig
/// const url = try URL.parse(allocator, "https://example.com");
/// defer url.deinit();
/// ```
pub fn parse(
allocator: Allocator,
input: []const u8,
base: ?*const URL,
) !URL {
// Implementation with numbered comments matching spec
}/// Host from URL Standard §4.2.
///
/// Represents the host component of a URL, which can be:
/// - Domain: DNS domain name (e.g., "example.com")
/// - IPv4: 32-bit address (e.g., 192.168.1.1)
/// - IPv6: 128-bit address (e.g., [2001:db8::1])
/// - Opaque: Non-parseable host string
pub const Host = union(enum) {
domain: []const u8,
ipv4: u32,
ipv6: [8]u16,
opaque: []const u8,
/// Free host resources.
pub fn deinit(self: Host, allocator: Allocator) void {
switch (self) {
.domain, .opaque => |s| allocator.free(s),
.ipv4, .ipv6 => {},
}
}
};// Private helper - no documentation needed
fn internal_validate(input: []const u8) bool {
return input.len > 0;
}
/// TEMPORARY MOCK: Fetch Response mock.
/// TODO: Replace with actual src/fetch/ when available.
///
/// This is temporary - detailed documentation not needed.
const MockResponse = struct {
status: u16,
};// orelse for default values
const value = optional orelse default_value;
// orelse with early return
const value = optional orelse return error.NotFound;
// if with unwrap
if (optional) |value| {
// Use value
} else {
// Handle null
}// Iterate over slice
for (slice) |item| {
// Process item
}
// Iterate with index
for (slice, 0..) |item, i| {
// Use item and index
}
// While loop for manual iteration
var i: usize = 0;
while (i < slice.len) : (i += 1) {
// Use slice[i]
}const result = switch (value) {
.variant1 => 10,
.variant2 => 20,
.variant3 => 30,
};
// With capture
const string = switch (host) {
.domain => |d| d,
.opaque => |o| o,
else => return error.NotAString,
};// Generic function
pub fn OrderedMap(comptime K: type, comptime V: type) type {
return struct {
keys: std.ArrayList(K),
values: std.ArrayList(V),
allocator: Allocator,
const Self = @This();
pub fn init(allocator: Allocator) Self {
return .{
.keys = std.ArrayList(K).init(allocator),
.values = std.ArrayList(V).init(allocator),
.allocator = allocator,
};
}
};
}
// Usage
var map = OrderedMap([]const u8, u32).init(allocator);//! Module-level documentation
// Standard library imports
const std = @import("std");
const Allocator = std.mem.Allocator;
// Cross-spec imports
const infra = @import("infra");
const webidl = @import("webidl");
// Local imports
const parser = @import("parser.zig");
const types = @import("types.zig");
// Public types
pub const URL = struct { ... };
pub const Host = union(enum) { ... };
// Public constants
pub const MAX_LENGTH: usize = 1_000_000;
// Public functions
pub fn parse(...) !URL { ... }
// Private functions
fn helper(...) void { ... }
// Tests (in separate test files, not here)// WRONG: Memory leak
var list = std.ArrayList(u8).init(allocator);
try list.append(42);
// Forgot list.deinit()!
// RIGHT:
var list = std.ArrayList(u8).init(allocator);
defer list.deinit();
try list.append(42);// WRONG: Crashes if null
const value = optional.?; // Panics if null!
// RIGHT: Handle null case
const value = optional orelse return error.NotFound;// WRONG: Can't detect leaks
test "example" {
const allocator = std.heap.page_allocator;
// ...
}
// RIGHT: Detects leaks
test "example" {
const allocator = std.testing.allocator;
// ...
}- Correct naming (snake_case for functions)
- Allocator parameter if allocates
- Error union return type
- defer for cleanup
- Documentation (if public)
- Tests (happy path, edge cases, errors, memory)
- Correct naming (PascalCase)
-
initfunction -
deinitfunction with proper cleanup - Methods use
self: *constorself: * - Documentation (if public)
- Tests for all methods
- Inline small, hot functions
- Preallocate when size known
- Fast path for common cases
- Minimize allocations
- Early exit for cheap checks
Remember: Quality over cleverness. Write explicit, safe, well-tested code. Document public APIs. Performance matters, but correctness comes first.