Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I loved this deep-dive of zig.

> There’s a catch, though. Unlike Rust, ErrorType is global to your whole program, and is nominally typed.

What does "global to your whole program" mean? I'd expect types to be available to the whole compilation unit. I'm also weirded out by the fact that zig has a distinct error type. Why? Why not represent errors as normal records?



> global to your whole program

Zig automatically does what most languages call LTO, so "whole program" and "compilation unit" are effectively the same thing (these error indices don't propagate across, e.g., dynamically linked libraries). If you have a bunch of ZIg code calling other Zig code and using error types, they'll all resolve to the same global error type (and calling different code would likely result in a different global error type).

> distinct error type, why?

The langage is very against various kinds of hidden "magic." If you take for granted that (1) error paths should have language support for being easily written correctly, and (2) userspace shouldn't be able to do too many shenanigans with control flow, then a design that makes errors special is a reasonable result.

It also adds some homogeneity to the code you read. I don't have to go read how _your_ `Result` type works just to use it correctly in an async context.

The obvious downside is that your use case might not map well to the language's blessed error type. In that case, you just make a normal record type to carry the information you want.


What they're trying to convey is that errors are structurally typed. If you declare:

    const MyError = error{Foo}
in one library and:

    const TheirError = error{Foo}
in another library, these types are considered equal. Unlike structs/unions/enums which are nominal in zig, like most languages.

The reason for this, and the reason that errors are not regular records, is to allow type inference to union and subtract error types like in https://news.ycombinator.com/item?id=42943942. (They behave like ocamls polymorphic variants - https://ocaml.org/manual/5.3/polyvariant.html) This largely avoids the problems described in https://sled.rs/errors.html#why-does-this-matter.

On the other hand zig errors can't have any associated value (https://github.com/ziglang/zig/issues/2647). I often find this requires me to store those values in some other big sum type somewhere which leads to all the same problems/boilerplate that the special error type should have saved me from.


if you need values associated with your error you can stash them in an in-out parameter


If I have multiple errors then that in-out parameter has to be a union(enum). And then I'm back to creating dozens of slightly different unions for functions which return slightly different sets of errors. Which is the same problem I have in rust. All of the nice inference that zig does doesn't apply to my in-out parameter either. And the compiler won't check that every path that returns error.Foo always initializes error_info.Foo.


i dont think you can make a union that has an error enum tag. I dont see why the in-out has to be dependent on the error.


> What does "global to your whole program" mean? I'd expect types to be available to the whole compilation unit.

I think they mean you only have one global/shared ErrorType . You can't write the type of function that may yeet one particular, specific type of error but not any other types of error.


They're really just enum variants. You can easily capture the error and conditionally handle it:

fn failFn() error{Oops}!i32 { try failingFunction(); return 12; }

test "try" { const v = failFn() catch |err| { try expect(err == error.Oops); return; }; try expect(v == 12); // is never reached }


> You can easily capture the error and conditionally handle it

Sure. But the compiler won't help you check that your function only throws the errors that you think it does, or that your try block is handling all the errors that can be thrown inside it.


> ...the compiler won't help you check that your function only throws the errors that you think it does, or that your try block is handling all the errors that can be thrown inside it.

It will do both of those:

    const std = @import("std");

    fn throws(i: usize) !void {
        return switch (i) {
            0 => error.zero,
            1 => error.one,
            else => error.many,
        };
    }

    fn catches(i: usize) !void {
        throws(i) catch |err| {
            return switch (err) {
                error.one => error.uno,
                else => |other| other,
            };
        };
    }

    pub fn main() void {
        catches(std.os.argv.len) catch |err| {
            switch (err) {
                // Type error if you comment out any of these:
                // note: unhandled error value: 'error.zero'
                error.zero => std.debug.print("0\n", .{}),
                error.uno => std.debug.print("1\n", .{}),
                error.many => std.debug.print("2\n", .{}),
                // Type error if you uncomment this:
                // 'error.one' not a member of destination error set
                //error.one => std.debug.print("1\n", .{}),
            }
        };
    }
It wouldn't hurt to just read the docs before making confident claims.


I'm not speaking for Zig, but in principle errors are not values, and often have different control flow and sometimes even data flow constraints.


Can you elaborate more?



There it says "errors are values" so now that contradicts what OP said.


I said I wasn't speaking for Zig specifically, just on general principle that errors are not really values. Many languages reify errors as values to avoid having different semantics for errors, but errors probably should have their own semantics. Zig seems to take a middle ground here, where errors are a special type of value but that still sort of has its own semantics.


> I said I wasn't speaking for Zig specifically

Lol, you are right. My brain just skipped that part somehow.


I was generally responding to the whole thread and pointing to how Zig sees errors. Enums are a type of value, yes, but they're typically dealt with differently than other data types.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: