| name | zig-coding |
| description | Enforces Zig coding conventions from the Zig Zero to Hero guide. Use when writing, reviewing, or refactoring Zig code. Applies naming conventions, error handling patterns, resource management, and module organization best practices. |
Zig Coding Conventions
This skill ensures all Zig code follows the conventions from Zig: Zero to Hero guide.
When to Use
- Writing new Zig code
- Reviewing Zig code changes
- Refactoring existing Zig code
- Designing new modules or types
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Types (struct, enum, union) | PascalCase | const NodeConfig = struct { ... } |
| Functions returning values | camelCase | fn parseConfig(path: []const u8) !Config |
| Functions returning types | PascalCase | fn ArrayList(comptime T: type) type |
| Variables, parameters, constants | snake_case | const max_retry_count = 5 |
| File names | snake_case | compose_parser.zig |
| Units in identifiers | Suffix with unit | timeout_ms, buffer_size_bytes |
| Acronyms | Fully capitalized | CRDTState, SWIMProtocol |
Error Handling Patterns
Use Error Unions (!T) for Failures
fn loadConfig(path: []const u8) !Config {
const file = try std.fs.openFileAbsolute(path, .{});
defer file.close();
// ...
}
Use Optionals (?T) for Valid Absence
fn findService(name: []const u8) ?*Service {
return services.get(name); // null is valid - service doesn't exist
}
Error Propagation
// Propagate with try
const config = try loadConfig(path);
// Provide fallback with catch
const port = parsePort(input) catch 7946;
// Handle optionals with orelse
const service = findService(name) orelse return error.ServiceNotFound;
Resource Management
Immediate Defer Pattern
fn processFile(path: []const u8) !void {
const file = try std.fs.openFileAbsolute(path, .{});
defer file.close(); // Immediately after acquisition
const buffer = try allocator.alloc(u8, 4096);
defer allocator.free(buffer);
// Work with file and buffer...
}
Partial Failure Rollback with errdefer
fn initializeNode(allocator: Allocator) !*Node {
const node = try allocator.create(Node);
errdefer allocator.destroy(node); // Cleanup if subsequent operations fail
node.config = try loadConfig();
errdefer node.config.deinit();
node.network = try initNetwork();
return node;
}
Nested Blocks in Loops
// WRONG: defers accumulate
for (items) |item| {
const resource = try acquire(item);
defer release(resource); // All defers run at function end!
}
// CORRECT: nested block scopes the defer
for (items) |item| {
{
const resource = try acquire(item);
defer release(resource); // Runs at block end
process(resource);
}
}
Module Organization
Visibility Control
// Public API - exported
pub fn startNode(config: Config) !*Node { ... }
// Internal implementation - not exported
fn validateConfig(config: Config) !void { ... }
Explicit Re-exports
// In orchestrator/orchestrator.zig
pub const Scheduler = @import("scheduler.zig").Scheduler;
pub const HealthMonitor = @import("health.zig").HealthMonitor;
pub const StateManager = @import("state.zig").StateManager;
Interface Pattern (for Fizz simulation testing)
pub const Time = struct {
nowFn: *const fn () i64,
pub fn now(self: Time) i64 {
return self.nowFn();
}
};
// Production implementation
pub fn realTime() Time {
return .{ .nowFn = struct {
fn now() i64 {
return std.time.timestamp();
}
}.now };
}
Anti-Patterns to Avoid
- Optionals for errors - Use
!Twhen absence indicates failure - Defer in loops - Use nested blocks to scope cleanup
- Unmarked comptime - Always mark type parameters with
comptime - Direct syscalls in core logic - Use injected interfaces for testability
Fizz-Specific Conventions
Allocator Naming
gpa- General Purpose Allocator (explicit deinit required)arena- Arena Allocator (bulk-free at end)page_allocator- Direct page allocation
Interface Injection
All core logic must accept interfaces rather than using system calls directly:
pub const Orchestrator = struct {
time: Time,
network: Network,
runtime: Runtime,
pub fn init(time: Time, network: Network, runtime: Runtime) Orchestrator {
return .{ .time = time, .network = network, .runtime = runtime };
}
};
Review Checklist
- Naming follows conventions (PascalCase types, camelCase functions, snake_case variables)
- Error unions used for failures, optionals for valid absence
- Resources have immediate defer cleanup
- errdefer used for partial failure rollback
- No defer inside loops without nested blocks
- Core logic uses injected interfaces (no direct syscalls)
- Public API marked with
pub - File names use snake_case