Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

w568w/diag.zig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Diag.zig

Try to make Zig error handling with payload more ergonomic, by utilizing the diagnostic pattern.

🚧 Still under development, NOT USABLE! 🚧

Features

  • Support from simple string messages to complex objects as payloads.
  • Don't repeat yourself. Automatically create error sets from tagged unions.
  • Compatible with Zig's error handling mechanism. Use try and catch |err| as usual. We keep no-intrusion as a goal.
  • Allocator-covariant. If you decide not to allocate, just pass null when initializing the diagnostic. Zero thing will be allocated, e.g., messages will not be created.
  • Optional-covariant. If you decide you don't want any diagnostics, just pass null. (Nearly) zero runtime overhead will be added.

Example

Create a simple error with a message:

const Diagnostic = @import("diag_zig").Diagnostic;
const raiseMessage = @import("diag_zig").raiseMessage;
const ErrorSetFromUnion = @import("diag_zig").ErrorSetFromUnion;

// Step 1: Define your error as a tagged union
const MyError = union(enum) {
    LessThanZero: i32,
    LargerThanTen: i32,
};

// Step 2: Define your function with a diag parameter, type `?*Diagnostic(MyError)`
fn foo(arg: i32, diag: ?*Diagnostic(MyError)) !void {
    if (arg < 0) {
        // Step 3: Use `raiseMessage` to create an error with a message
        try raiseMessage(diag, .{ .LessThanZero = arg }, "arg is less than zero: {}\n", .{arg});
    }
}

// Then let's call the function:
// Step 4: Initialize the diagnostic
var diag = Diagnostic(MyError).init(allocator);
defer diag.deinit();
// Step 5: Call the function with the diagnostic, and enjoy your error!
foo(-1, &diag) catch |errcode| {
    std.debug.assert(errcode == ErrorSetFromUnion(MyError).LessThanZero); // the error name is generated from the union above
    std.debug.assert(diag.getError().?.LessThanZero == -1); // the payload is available
    std.debug.assert(std.mem.eql(u8, diag.message.?, "arg is less than zero: -1\n")); // the message is also available
};

And you can also propagate the diagnostic with try:

fn bar(arg: i32, diag: ?*Diagnostic(MyError)) !void {
    try foo(arg, diag);
}

Have complex ownership in the payload? No problem!

// A complex error with a payload that owns dynamic memory
const MyComplexError = union(enum) {
    InvalidInput: struct {
        input: i32,
        reason: []u8,
    },

    // Step 1: Define your init function to create the error pointer as you need
    fn init(alloc: std.mem.Allocator, comptime reason: []const u8, reasonArgs: anytype, input: i32) *MyComplexError {
        const err = alloc.create(@This()) catch unreachable;
        err.InvalidInput = .{
            .input = input,
            .reason = std.fmt.allocPrint(alloc, reason, reasonArgs) catch unreachable,
        };
        return err;
    }

    // Step 2: Define a cleanup function to free the allocated memory and destroy the error
    // Will be called in `Diagnostic.deinit()`
    fn cleanUp(self: **anyopaque, userData: ?*anyopaque) void {
        const err: **MyComplexError = @alignCast(@ptrCast(self));
        const alloc: *std.mem.Allocator = @alignCast(@ptrCast(userData.?));

        alloc.free(err.*.InvalidInput.reason); // free the dynamically allocated reason
        alloc.destroy(err.*); // destroy the error itself
    }
};

fn complexFoo(alloc: *const std.mem.Allocator, arg: i32, diag: ?*Diagnostic(MyComplexError)) !void {
    if (arg < 0) {
        try raiseRef(diag, MyComplexError.init(
            alloc.*,
            "arg is less than zero: {}",
            .{arg},
            arg,
            ), 
            @constCast(alloc), // pass the allocator as user data to cleanUp()
            MyComplexError.cleanUp // specify the cleanup function to run
        );
    }
}

// Then let's call the function as usual:
complexFoo(&alloc, -1, &diag) catch |errcode| {
    std.debug.assert(errcode == ErrorSetFromUnion(MyComplexError).InvalidInput);
    const err = diag.getError().?; // get the error payload
    std.debug.assert(err.InvalidInput.input == -1);
    std.debug.assert(std.mem.eql(u8, err.InvalidInput.reason, "arg is less than zero: -1"));
};

At any time, you can turn off diagnostics by passing null:

try foo(arg, null) catch |errcode| {
    // No diagnostic will be created, so no message or payload will be available,
    // but you can still get the error code
    std.debug.assert(errcode == ErrorSetFromUnion(MyError).LessThanZero);
};

About

Ergonomic error handling in Zig

Resources

Stars

Watchers

Forks

Releases

No releases published

Languages