ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.93k stars 2.55k forks source link

Error Message Improvement: Introduce `@errorOrigin` #16819

Open joelonsql opened 1 year ago

joelonsql commented 1 year ago

Zig Version

0.11.0

Steps to Reproduce and Observed Output

Error Message Improvement: Introduce @errorOrigin

Hey Zig enthusiasts,

I've been spending quite some time wrangling with errors and their origins lately, and thought of a potential enhancement to our beloved language.

The Idea:

What if we had a way, sort of a middle-ground between the granular @errorReturnTrace and the succinct @errorName, to quickly pinpoint where an error was birthed? Enter @errorOrigin.

The Rationale:

Now, @errorReturnTrace is fantastic for in-depth investigations, but sometimes it can feel like using a sledgehammer to crack a nut. When issues stem from a third-party module, what often matters most is pinpointing where the error was first received in our module, rather than tracing its deeper origins. In these cases, a snapshot of the error's entry point into our code might be all we need.

The proposed @errorOrigin would produce a struct containing file and line. Here's how it works:

Perks of Having @errorOrigin:

Quick Peek:

To illustrate the utility of @errorOrigin, let's dive into a brief example. Imagine you're interacting with an external library (like parsing a UUID) and handling another routine error within the same module. With @errorOrigin, the source of these errors becomes immediately transparent:

const std = @import("std");
const UUID = @import("uuid.zig").UUID;

pub fn main() void {
    foo() catch |err| {
        std.debug.print("got error: {s} at {}\n", .{@errorName(err), @errorOrigin(err)});
    };
    baz() catch |err| {
        std.debug.print("got error: {s} at {}\n", .{@errorName(err), @errorOrigin(err)});
    };
}

fn foo() !void {
    try bar();
}

fn bar() !void {
    const uuidStr = "f47ac10b-58cc-4372-a567-0e02b2c3d47q";
    _ = try UUID.parse(uuidStr);
}

fn baz() !void {
    return error.FileNotFound;
}
% zig build run
got error: InvalidUUID from main.zig:19
got error: FileNotFound from main.zig:23

Line main.zig:19 is where the external library failed to parse the UUID:

    _ = try UUID.parse(uuidStr);

While line main.zig:23 directly signals our own error:

    return error.FileNotFound;

I'm aware that the project typically refrains from entertaining Language Proposals. However, seeing the designated "Error message improvement" category sparked my inclination to share this idea, given its relevance to the theme. My apologies if I've misinterpreted the scope or intent!

This is just me thinking aloud, "Wouldn't it be neat if Zig had this feature?" đŸ˜„

Cheers to the incredible work the Zig community does and for always pushing the boundaries!

Best wishes, Joel Jakobsson

Expected Output

got error: InvalidUUID from main.zig:19 got error: FileNotFound from main.zig:23

squeek502 commented 1 year ago

I think it might be possible to implement this with a different builtin, as the only thing missing to be able to implement this currently is the ability to get the path of the root source file and/or the main package path. If that were possible to get, then this could be implemented in e.g. std.debug via something like:

Note: If there's some way to do the "if the file_name starts with the main package path" part (or something similar) in status quo Zig that I'm missing, then this would be implementable in userland right now. There's @import("root") and @src() but I don't see a way to get them to interact in a usable way for this.