The Zig Programming Language category provides comprehensive documentation on Zig’s role in Geode, including why Zig was chosen as the implementation language, how to build Geode from source, contributing guidelines, and insights into Zig’s unique features that make Geode performant and reliable.
Why Zig for Geode?
Geode is written in Zig, a modern systems programming language that prioritizes safety, performance, and developer experience. This choice was deliberate and fundamental to Geode’s architecture, enabling capabilities that would be difficult or impossible with other languages.
Memory Safety Without Garbage Collection
Zig provides compile-time memory safety checks without the runtime overhead of garbage collection. Every allocation and deallocation is explicit, giving Geode precise control over memory usage - critical for database performance where predictable latency matters.
Compile-time safety checks prevent:
- Buffer overflows
- Use-after-free bugs
- Double frees
- Memory leaks (with proper review)
- Null pointer dereferences (optional types)
Unlike C/C++, Zig makes unsafe operations explicit with @ builtins, making code review straightforward. Unlike Rust, Zig’s approach is simpler and more direct, reducing cognitive overhead while maintaining safety.
const std = @import("std");
pub fn processQuery(allocator: std.mem.Allocator, query: []const u8) ![]const u8 {
// Explicit allocation - compiler tracks ownership
var result = try allocator.alloc(u8, 1024);
errdefer allocator.free(result); // Automatic cleanup on error
// Safe array access - bounds checking in debug mode
if (query.len > 0) {
result[0] = query[0];
}
return result; // Ownership transferred to caller
}
Performance: Zero-Cost Abstractions
Zig compiles to optimized machine code comparable to C, with no hidden control flow, no hidden allocations, and no runtime overhead from language features. What you write is what executes.
Performance characteristics:
- Direct memory access without runtime indirection
- SIMD operations for vectorized processing
- Inline assembly when needed for critical paths
- Compile-time code generation (comptime)
- No vtable overhead for polymorphism
Geode’s query engine benefits from Zig’s performance - graph traversals, index lookups, and transaction processing all run at maximum speed with minimal memory overhead.
// Compile-time code generation for type-safe operations
fn genericTraversal(comptime T: type, node: *T) void {
// Code specialized for each type at compile time
// Zero runtime cost for abstraction
}
Cross-Platform Compilation
Zig’s advanced cross-compilation makes Geode trivially portable. From a single development machine, you can build for Linux, macOS, Windows, and even embedded targets without installing separate toolchains.
# Build for Linux from macOS
zig build -Dtarget=x86_64-linux-gnu
# Build for Windows from Linux
zig build -Dtarget=x86_64-windows-gnu
# Build for ARM from x86_64
zig build -Dtarget=aarch64-linux-gnu
This simplifies CI/CD pipelines and ensures consistent builds across platforms.
Explicit Error Handling
Zig’s error handling is explicit and compile-time checked, eliminating hidden failure paths and making error recovery straightforward.
const QueryError = error{
SyntaxError,
AuthenticationFailed,
ConstraintViolation,
OutOfMemory,
};
pub fn executeQuery(query: []const u8) QueryError!Result {
// Compiler enforces error handling at every call site
const parsed = try parseQuery(query); // Propagate errors with 'try'
const authorized = try checkPermissions(parsed);
return executeInternal(authorized);
}
Every possible error path is documented in the function signature and must be handled by callers. This makes Geode’s error handling robust and predictable.
Comptime: Compile-Time Execution
Zig’s comptime feature allows arbitrary code to run during compilation, enabling powerful metaprogramming without macros or templates.
// Generate code at compile time
pub fn generateIndexImpl(comptime IndexType: type) type {
return struct {
data: IndexType,
pub fn insert(self: *@This(), key: []const u8, value: u64) !void {
// Implementation specialized for IndexType
}
pub fn lookup(self: *const @This(), key: []const u8) ?u64 {
// Optimized for specific index type
}
};
}
// Use compile-time generated code
const BTreeIndex = generateIndexImpl(BTree);
const HashIndex = generateIndexImpl(HashMap);
Geode uses comptime extensively for:
- Type-safe query execution
- Optimized data structure generation
- Protocol serialization/deserialization
- Test generation
Simple, Predictable Language
Zig’s syntax is straightforward and familiar to C programmers, but with modern conveniences. There are no hidden behaviors, no mysterious runtime systems, and no complex language features that obscure what the code actually does.
Language principles:
- No hidden control flow
- No hidden allocations
- No preprocessor
- No macros (comptime instead)
- No operator overloading
- No exceptions (explicit error values)
- No RAII (explicit defer/errdefer)
This simplicity makes Geode’s codebase approachable for contributors and maintainable long-term.
Building Geode from Source
Prerequisites
Zig 0.1.0 or later: Download from ziglang.org
# Verify Zig installation
zig version
# Should output: 0.1.0 or later
System dependencies:
- Git for source control
- Make for build automation
- QUIC library (bundled in Geode)
Build Process
# Clone repository
git clone https://github.com/codeprosorg/geode
cd geode
# Build debug version (fast compile, includes debug symbols)
make build
# Build release version (optimized, slower compile)
make release
# Run tests
make test
# Install system-wide
make install
Build Configuration
Zig’s build system is written in Zig itself (build.zig):
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "geode",
.root_source_file = .{ .path = "src/cli/main.zig" },
.target = target,
.optimize = optimize,
});
// Add dependencies
exe.linkLibC();
exe.addIncludePath(.{ .path = "include" });
b.installArtifact(exe);
}
Build modes:
Debug: Fast compilation, runtime safety checks, debug symbolsReleaseSafe: Optimized with safety checks (recommended for production)ReleaseFast: Maximum optimization, safety checks disabledReleaseSmall: Optimize for binary size
# Specify build mode explicitly
zig build -Doptimize=ReleaseSafe
Development Workflow
# Fast iteration during development
zig build && ./zig-out/bin/geode serve
# Run specific tests
zig build test --summary all
# Check for issues without building
zig build check
# Format code
zig fmt src/
# Generate documentation
zig build docs
Contributing to Geode
Geode welcomes contributions from the community. The codebase follows evidence-based development practices with CANARY markers tracking requirements and implementations.
Getting Started
- Read the contribution guide: See
geode/CLAUDE.mdandgeode/docs/CONTRIBUTING.md - Set up development environment: Install Zig 0.1.0+
- Build and test: Ensure
make testpasses - Pick an issue: Check GitLab issues for good first issues
Code Style
Geode follows Zig’s standard formatting:
# Format code automatically
zig fmt src/
zig fmt geode/
Naming conventions:
camelCasefor local variables and functionsPascalCasefor types and namespacesSCREAMING_SNAKE_CASEfor constants- Descriptive names preferred over abbreviations
// Good
const QueryExecutor = struct {
const MAX_QUERY_DEPTH = 10;
pub fn executeQuery(self: *QueryExecutor, query: []const u8) !Result {
var resultBuffer = try self.allocator.alloc(u8, 1024);
defer self.allocator.free(resultBuffer);
// ...
}
};
// Avoid
const QE = struct {
const mqd = 10;
pub fn eq(s: *QE, q: []const u8) !R { ... }
};
Memory Management Patterns
Always use explicit allocators:
pub fn processData(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
// Allocate memory
var buffer = try allocator.alloc(u8, input.len * 2);
errdefer allocator.free(buffer); // Cleanup on error
// Process data
@memcpy(buffer[0..input.len], input);
return buffer; // Caller owns memory
}
// Caller's responsibility to free
const result = try processData(allocator, data);
defer allocator.free(result);
Arena allocators for temporary allocations:
pub fn handleQuery(gpa: std.mem.Allocator, query: []const u8) !void {
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit(); // Frees all allocations at once
const allocator = arena.allocator();
// All allocations automatically freed when arena is destroyed
const parsed = try parseQuery(allocator, query);
const optimized = try optimizeQuery(allocator, parsed);
const result = try executeQuery(allocator, optimized);
}
Error Handling Patterns
Define error sets clearly:
const DatabaseError = error{
ConnectionFailed,
QueryTimeout,
ConstraintViolation,
OutOfMemory,
};
pub fn connect(config: Config) DatabaseError!Connection {
if (config.host.len == 0) {
return error.ConnectionFailed;
}
// ...
}
Use error unions and handle appropriately:
// Propagate errors
const result = try riskyOperation();
// Handle specific errors
const result = riskyOperation() catch |err| switch (err) {
error.FileNotFound => return default,
error.OutOfMemory => return error.OutOfMemory,
else => |e| return e,
};
// Provide context with error traces
const result = riskyOperation() catch |err| {
std.log.err("Failed to process: {}", .{err});
return err;
};
Testing
Write comprehensive tests:
const testing = std.testing;
test "query execution handles syntax errors" {
const query = "INVALID GQL SYNTAX";
const result = executeQuery(query);
try testing.expectError(error.SyntaxError, result);
}
test "index lookup returns correct values" {
var index = try Index.init(testing.allocator);
defer index.deinit();
try index.insert("key1", 100);
try index.insert("key2", 200);
try testing.expectEqual(@as(u64, 100), index.lookup("key1").?);
try testing.expectEqual(@as(u64, 200), index.lookup("key2").?);
try testing.expectEqual(@as(?u64, null), index.lookup("key3"));
}
Run tests with coverage:
zig build test --summary all
CANARY Markers
Geode uses CANARY governance markers to track requirements:
// CANARY: REQ=REQ-XXX; FEATURE="PatternMatching"; ASPECT=BasicMatch; STATUS=TESTED; TEST=TestBasicNodePattern; OWNER=engine; UPDATED=2026-01-24
// Requirement: Implement basic node pattern matching
// Evidence: TestBasicNodePattern
pub fn matchNodePattern(pattern: NodePattern) ![]Node {
// Implementation
}
When implementing features:
- Find or create CANARY marker
- Implement functionality
- Add tests as evidence
- Reference tests in CANARY comment
Documentation
Document public APIs with doc comments:
/// Executes a GQL query and returns results.
///
/// Parameters:
/// - allocator: Memory allocator for result allocation
/// - query: GQL query string
/// - params: Query parameters (optional)
///
/// Returns:
/// Query results on success, error on failure.
///
/// Errors:
/// - SyntaxError: Invalid GQL syntax
/// - AuthenticationFailed: User not authenticated
/// - OutOfMemory: Insufficient memory
pub fn executeQuery(
allocator: std.mem.Allocator,
query: []const u8,
params: ?QueryParams,
) !QueryResult {
// Implementation
}
Zig Resources for Geode Contributors
Official Documentation:
Learning Resources:
- Zig Learn - Interactive tutorial
- Zig By Example - Code examples
- Zig News - Community articles
Community:
- Zig Discord server
- Zig subreddit (r/zig)
- Zig forum
Zig Client Library
Geode provides a native Zig client library:
const std = @import("std");
const geode = @import("geode_client");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Connect to Geode
var client = try geode.Client.connect(allocator, .{
.host = "localhost",
.port = 3141,
});
defer client.deinit();
// Execute query
const result = try client.execute(
\\MATCH (n:Node)
\\RETURN n.name
\\LIMIT 10
, .{});
defer result.deinit();
// Process results
while (try result.next()) |row| {
const name = row.get("n.name").?.asString();
std.debug.print("Name: {s}\n", .{name});
}
}
Best Practices for Zig in Geode
- Use explicit allocators: Pass allocators as parameters
- Leverage comptime: Generate code at compile time when possible
- Handle errors explicitly: Never ignore error returns
- Prefer defer for cleanup: Resource cleanup with defer/errdefer
- Write comprehensive tests: Test both happy and error paths
- Document public APIs: Use doc comments for public functions
- Follow Zig idioms: Study standard library for patterns
- Profile performance: Use Zig’s built-in profiling tools
Related Topics
- Development - Development workflows
- Contributing - Contribution guidelines
- Architecture - Geode’s system architecture
- Development Guide - Building and development instructions
- Testing - Testing strategies
Further Reading
- Zig Language Guide - Official learning resources
- Geode Architecture - How Geode is structured
- Contributing Guide - Detailed contribution process
- Testing Guide - Testing strategies and patterns