Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
  • Documentation
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Product
    • Security Analysis
    • Code Quality
    • Agentic Chat
    • RuleSets
    • Memories
    • Analytics
    • Command List
    • Configurations
    Patterns
    • Languages
      • Supported Languages
      • Python
      • Java
      • JavaScript
      • TypeScript
      • Node.js
      • React
      • Fastify
      • Next.js
      • Terraform
      • C#
      • C++
      • C
      • Go
      • Rust
      • Swift
      • React Native
      • Spring Boot
      • Kotlin
      • Flutter
      • Ruby
      • PHP
      • Scala
      • Perl
      • R
      • Dart
      • Elixir
      • Erlang
      • Haskell
      • Lua
      • Julia
      • Clojure
      • Groovy
      • Fortran
      • COBOL
      • Pascal
      • Assembly
      • Bash
      • PowerShell
      • SQL
      • PL/SQL
      • T-SQL
      • MATLAB
      • Objective-C
      • VBA
      • ABAP
      • Apex
      • Apache Camel
      • Crystal
      • D
      • Delphi
      • Elm
      • F#
      • Hack
      • Lisp
      • OCaml
      • Prolog
      • Racket
      • Scheme
      • Solidity
      • Verilog
      • VHDL
      • Zig
      • MongoDB
      • ClickHouse
      • MySQL
      • GraphQL
      • Redis
      • Cassandra
      • Elasticsearch
    • Security
    • Performance
    Integrations
    • Code Repositories
    • Team Messengers
    • Ticketing
    Enterprise
    • Enterprise Deployment Overview
    • Enterprise Configurations
    • Observability and Fallbacks
    • Create Your Own GitHub App
    • Self-Hosting Options
    • RBAC
    Patterns
    Languages

    Zig

    Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software. It emphasizes compile-time reflection, comptime, and provides low-level control with high-level safety features.

    Zig, despite being a modern language designed to avoid many common programming pitfalls, still has several anti-patterns that can lead to code that’s difficult to maintain, inefficient, or doesn’t follow the language’s idioms. Here are the most important anti-patterns to avoid when writing Zig code.

    // Anti-pattern: Excessive use of allocators for small, temporary data
    fn processData(allocator: std.mem.Allocator, data: []const u8) !void {
        var result = try allocator.alloc(u8, data.len);
        defer allocator.free(result);
        
        for (data, 0..) |byte, i| {
            result[i] = byte + 1;
        }
        
        // Process result...
    }
    
    // Better approach: Use stack memory for small, temporary data
    fn processData(data: []const u8) void {
        var result: [100]u8 = undefined;
        if (data.len > result.len) return error.BufferTooSmall;
        
        for (data, 0..) |byte, i| {
            result[i] = byte + 1;
        }
        
        // Process result...
    }

    Avoid excessive use of allocators for small, temporary data structures. Zig provides excellent support for stack allocation, which is often more efficient for small, short-lived objects. Reserve heap allocations for data whose size is not known at compile time or that needs to outlive the current scope.

    // Anti-pattern: Not using comptime features
    fn max(a: i32, b: i32) i32 {
        if (a > b) return a;
        return b;
    }
    
    // Usage
    const result = max(5, 10);
    
    // Better approach: Use comptime for generic functions
    fn max(comptime T: type, a: T, b: T) T {
        if (a > b) return a;
        return b;
    }
    
    // Usage
    const result_i32 = max(i32, 5, 10);
    const result_f64 = max(f64, 5.5, 10.1);
    
    // Even better: Use type inference
    fn max2(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
        if (a > b) return a;
        return b;
    }
    
    // Usage with type inference
    const result_inferred = max2(5, 10); // Type is inferred as i32

    Take advantage of Zig’s powerful comptime features. Use comptime for generic programming, metaprogramming, and compile-time evaluation. This leads to more flexible, type-safe, and efficient code.

    // Anti-pattern: Ignoring errors
    fn readFile(path: []const u8) void {
        const file = std.fs.cwd().openFile(path, .{}) catch {
            // Silently ignore error
            return;
        };
        defer file.close();
        
        // Read and process file...
    }
    
    // Better approach: Properly handle errors
    fn readFile(path: []const u8) !void {
        const file = try std.fs.cwd().openFile(path, .{});
        defer file.close();
        
        // Read and process file...
    }
    
    // Usage
    fn main() !void {
        try readFile("data.txt");
        
        // Or handle specific errors
        readFile("data.txt") catch |err| switch (err) {
            error.FileNotFound => std.debug.print("File not found\n", .{}),
            error.AccessDenied => std.debug.print("Access denied\n", .{}),
            else => return err,
        };
    }

    Don’t ignore errors in Zig. The language’s error handling system is designed to be explicit and comprehensive. Use try, catch, and error unions to properly handle and propagate errors. This makes your code more robust and easier to debug.

    // Anti-pattern: Excessive use of pointers
    fn modifyValue(value: *i32) void {
        value.* += 1;
    }
    
    fn processArray(array: []*i32) void {
        for (array) |item| {
            modifyValue(item);
        }
    }
    
    // Usage
    var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    var pointers: [5]*i32 = undefined;
    for (numbers, 0..) |*number, i| {
        pointers[i] = number;
    }
    processArray(&pointers);
    
    // Better approach: Use slices and references where appropriate
    fn modifyValue(value: *i32) void {
        value.* += 1;
    }
    
    fn processArray(array: []i32) void {
        for (array) |*item| {
            modifyValue(item);
        }
    }
    
    // Usage
    var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    processArray(&numbers);

    Avoid excessive use of pointers in Zig. While pointers are necessary in some cases, Zig provides safer alternatives like slices and references that are often more appropriate. Use pointers only when you need to represent optional values (?*T), share ownership, or interface with C code.

    // Anti-pattern: Poor memory management
    fn createString() ![]u8 {
        var allocator = std.heap.page_allocator;
        var result = try allocator.alloc(u8, 100);
        // No way for caller to free this memory
        return result;
    }
    
    // Usage
    fn main() !void {
        const str = try createString();
        // Memory leak: no way to free str
    }
    
    // Better approach: Pass allocator to functions
    fn createString(allocator: std.mem.Allocator) ![]u8 {
        var result = try allocator.alloc(u8, 100);
        return result;
    }
    
    // Usage
    fn main() !void {
        var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        defer _ = gpa.deinit();
        const allocator = gpa.allocator();
        
        const str = try createString(allocator);
        defer allocator.free(str);
        
        // Use str...
    }

    Use proper memory management in Zig. Pass allocators to functions that need to allocate memory, and make it clear who is responsible for freeing that memory. Use defer to ensure resources are properly cleaned up, even in the presence of errors.

    // Anti-pattern: Using anyerror instead of specific error sets
    fn readConfig(path: []const u8) anyerror!void {
        // Implementation...
    }
    
    // Usage
    fn main() !void {
        readConfig("config.txt") catch |err| {
            // No compile-time knowledge of possible errors
            std.debug.print("Error: {any}\n", .{err});
            return err;
        };
    }
    
    // Better approach: Use specific error sets
    const ConfigError = error{
        FileNotFound,
        InvalidFormat,
        PermissionDenied,
    };
    
    fn readConfig(path: []const u8) ConfigError!void {
        // Implementation...
    }
    
    // Usage
    fn main() !void {
        readConfig("config.txt") catch |err| switch (err) {
            // Compiler ensures all possible errors are handled
            ConfigError.FileNotFound => std.debug.print("Config file not found\n", .{}),
            ConfigError.InvalidFormat => std.debug.print("Invalid config format\n", .{}),
            ConfigError.PermissionDenied => std.debug.print("Permission denied\n", .{}),
        };
    }

    Define specific error sets for your functions instead of using anyerror. This provides better documentation, enables more precise error handling, and allows the compiler to ensure all possible errors are handled.

    // Anti-pattern: Not writing tests
    fn add(a: i32, b: i32) i32 {
        return a + b;
    }
    
    // Better approach: Write tests using the built-in test framework
    fn add(a: i32, b: i32) i32 {
        return a + b;
    }
    
    test "add function" {
        try std.testing.expectEqual(@as(i32, 5), add(2, 3));
        try std.testing.expectEqual(@as(i32, 0), add(0, 0));
        try std.testing.expectEqual(@as(i32, -1), add(2, -3));
    }

    Write tests for your Zig code using the built-in test framework. Zig makes testing easy with the test block syntax and the std.testing module. Tests help ensure your code works correctly and continues to work as you make changes.

    // Anti-pattern: Improper resource cleanup on errors
    fn processFile(path: []const u8) !void {
        const file = try std.fs.cwd().openFile(path, .{});
        // If an error occurs after this point, file will not be closed
        
        const data = try file.readToEndAlloc(std.heap.page_allocator, std.math.maxInt(usize));
        // If an error occurs after this point, data will not be freed
        
        // Process data...
        
        std.heap.page_allocator.free(data);
        file.close();
    }
    
    // Better approach: Use defer for resource cleanup
    fn processFile(path: []const u8) !void {
        const file = try std.fs.cwd().openFile(path, .{});
        defer file.close();
        
        const data = try file.readToEndAlloc(std.heap.page_allocator, std.math.maxInt(usize));
        defer std.heap.page_allocator.free(data);
        
        // Process data...
        // Resources will be cleaned up automatically, even if an error occurs
    }

    Use defer to ensure resources are properly cleaned up, even in the presence of errors. This prevents resource leaks and makes your code more robust.

    // Anti-pattern: Misusing undefined behavior
    fn dangerousFunction() void {
        var array: [10]u8 = undefined;
        // Using uninitialized memory
        std.debug.print("{any}\n", .{array});
        
        // Out-of-bounds access
        const value = array[10];
        
        // Integer overflow in safe mode
        var x: u8 = 255;
        x += 1;
    }
    
    // Better approach: Initialize memory and check bounds
    fn safeFunction() void {
        var array: [10]u8 = [_]u8{0} ** 10;
        std.debug.print("{any}\n", .{array});
        
        // Bounds checking
        const index = 10;
        if (index < array.len) {
            const value = array[index];
        } else {
            std.debug.print("Index out of bounds\n", .{});
        }
        
        // Overflow checking
        var x: u8 = 255;
        x = std.math.add(u8, x, 1) catch |err| {
            std.debug.print("Overflow: {any}\n", .{err});
            return;
        };
    }

    Avoid undefined behavior in Zig. Initialize variables before using them, check array bounds, and use safe arithmetic operations. While Zig provides tools for working with undefined memory, use them carefully and only when necessary.

    // Anti-pattern: Not using Zig's build system
    // Manually compiling with command-line flags
    // $ zig build-exe main.zig -O ReleaseFast -lc
    
    // Better approach: Use build.zig
    // 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 = "my_app",
            .root_source_file = .{ .path = "src/main.zig" },
            .target = target,
            .optimize = optimize,
        });
        
        // Add dependencies
        exe.linkLibC();
        
        // Add tests
        const tests = b.addTest(.{
            .root_source_file = .{ .path = "src/main.zig" },
            .target = target,
            .optimize = optimize,
        });
        
        const test_step = b.step("test", "Run tests");
        test_step.dependOn(&tests.step);
        
        b.installArtifact(exe);
    }

    Use Zig’s build system for your projects. The build system provides a clean, declarative way to specify build configurations, dependencies, and tests. It also makes cross-compilation easier and more consistent.

    // Anti-pattern: Poor or no documentation
    fn calculateScore(player: *Player, level: u32) u32 {
        return player.baseScore * level + player.bonus;
    }
    
    // Better approach: Include proper documentation
    /// Calculates the player's score for a given level.
    /// The score is calculated as (baseScore * level + bonus).
    /// 
    /// Parameters:
    ///   player: A pointer to the Player struct containing the player's stats.
    ///   level: The level number for which to calculate the score.
    /// 
    /// Returns: The calculated score as an unsigned 32-bit integer.
    fn calculateScore(player: *Player, level: u32) u32 {
        return player.baseScore * level + player.bonus;
    }

    Document your code with clear, concise comments. Good documentation helps others (and your future self) understand how to use your code correctly. Use doc comments (///) for public APIs and regular comments (//) for implementation details.

    // Anti-pattern: Poor error messages
    fn parseConfig(data: []const u8) !Config {
        if (data.len == 0) return error.InvalidConfig;
        // Parse config...
    }
    
    // Better approach: Use descriptive error messages
    const ConfigError = error{
        EmptyConfig,
        MissingRequiredField,
        InvalidFieldValue,
        // ...
    };
    
    fn parseConfig(data: []const u8) ConfigError!Config {
        if (data.len == 0) return ConfigError.EmptyConfig;
        
        // Check for required fields
        if (!hasField(data, "name")) return ConfigError.MissingRequiredField;
        
        // Validate field values
        const value = getValue(data, "count");
        if (value < 0) return ConfigError.InvalidFieldValue;
        
        // Parse config...
    }

    Provide clear, descriptive error messages in your code. Use specific error types and meaningful error names. This makes debugging easier and helps users of your code understand what went wrong.

    // Anti-pattern: Inconsistent naming
    fn calculate_score(PlayerData: *player_data, Level: u32) u32 {
        return PlayerData.base_score * Level + PlayerData.BONUS;
    }
    
    // Better approach: Follow Zig naming conventions
    fn calculateScore(player: *Player, level: u32) u32 {
        return player.baseScore * level + player.bonus;
    }

    Follow Zig’s naming conventions. Use camelCase for functions and variables, PascalCase for types, and SCREAMING_SNAKE_CASE for constants. Consistent naming makes your code more readable and idiomatic.

    // Anti-pattern: Global imports and aliases
    const std = @import("std");
    const print = std.debug.print;
    const mem = std.mem;
    const fs = std.fs;
    
    fn main() !void {
        print("Hello, world!\n", .{});
        var buffer: [100]u8 = undefined;
        mem.set(u8, &buffer, 0);
        const file = try fs.cwd().createFile("test.txt", .{});
        defer file.close();
    }
    
    // Better approach: Local imports and qualified names
    const std = @import("std");
    
    fn main() !void {
        std.debug.print("Hello, world!\n", .{});
        var buffer: [100]u8 = undefined;
        std.mem.set(u8, &buffer, 0);
        const file = try std.fs.cwd().createFile("test.txt", .{});
        defer file.close();
    }

    Use imports judiciously in Zig. Prefer qualified names (e.g., std.debug.print) over aliases to make it clear where functions and types come from. This improves code readability and helps avoid name conflicts.

    // Anti-pattern: Force-unwrapping optional values
    fn findUser(id: u64) ?*User {
        // Implementation...
    }
    
    fn processUser(id: u64) void {
        const user = findUser(id).?; // Will crash if user is null
        // Process user...
    }
    
    // Better approach: Properly handle optional values
    fn processUser(id: u64) void {
        const user_opt = findUser(id);
        if (user_opt) |user| {
            // Process user...
        } else {
            std.debug.print("User not found\n", .{});
        }
    }
    
    // Or using if-else
    fn processUser(id: u64) void {
        if (findUser(id)) |user| {
            // Process user...
        } else {
            std.debug.print("User not found\n", .{});
        }
    }

    Handle optional values properly in Zig. Use if (optional) |value| or if (optional) |value| else {} to safely unwrap optional values. Avoid force-unwrapping with .? unless you’re absolutely certain the value is non-null.

    // Anti-pattern: Unsafe concurrency
    var counter: usize = 0;
    
    fn incrementCounter() void {
        counter += 1; // Race condition in concurrent context
    }
    
    // Better approach: Use atomic operations
    const std = @import("std");
    const Atomic = std.atomic.Atomic;
    
    var counter = Atomic(usize).init(0);
    
    fn incrementCounter() void {
        _ = counter.fetchAdd(1, .Monotonic);
    }
    
    // Or use proper synchronization
    const std = @import("std");
    const Mutex = std.Thread.Mutex;
    
    var mutex = Mutex{};
    var counter: usize = 0;
    
    fn incrementCounter() void {
        mutex.lock();
        defer mutex.unlock();
        counter += 1;
    }

    Use proper concurrency patterns in Zig. For shared mutable state, use atomic operations or proper synchronization primitives like mutexes. This prevents race conditions and ensures thread safety.

    // Anti-pattern: Manual error propagation
    fn readConfig(path: []const u8) ConfigError!Config {
        const file = std.fs.cwd().openFile(path, .{}) catch |err| {
            return err;
        };
        defer file.close();
        
        const data = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
            return err;
        };
        defer allocator.free(data);
        
        return parseConfig(data);
    }
    
    // Better approach: Use try for error propagation
    fn readConfig(path: []const u8) ConfigError!Config {
        const file = try std.fs.cwd().openFile(path, .{});
        defer file.close();
        
        const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
        defer allocator.free(data);
        
        return parseConfig(data);
    }

    Use try for error propagation in Zig. The try keyword is a concise way to propagate errors up the call stack. It’s equivalent to x catch |err| return err, but more readable and idiomatic.

    VHDLMongoDB
    websitexgithublinkedin
    Powered by Mintlify
    Assistant
    Responses are generated using AI and may contain mistakes.