4 ms·
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
by taurknaut 2y ago
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?
- naasking 2y agoI'm not speaking for Zig, but in principle errors are not values, and often have different control flow and sometimes even data flow constraints.
- valenterry 2y agoCan you elaborate more?
- chrisco255 2y agoThey're enums. See: https://zig.guide/language-basics/errors https://zig.guide/language-basics/errors
- valenterry 2y agoThere it says "errors are values" so now that contradicts what OP said.
- chrisco255 2y agoI 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.
- naasking 2y agoI 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.
- valenterry 2y ago> I said I wasn't speaking for Zig specifically Lol, you are right. My brain just skipped that part somehow.
- lmm 2y ago> 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.
- chrisco255 2y agoThey'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 }
- lmm 2y ago> 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.
- jamii 2y ago> ...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.
- jamii 2y agoWhat 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 https://news.ycombinator.com/item?id=42943942. (They behave like ocamls polymorphic variants - https://ocaml.org/manual/5.3/polyvariant.html https://ocaml.org/manual/5.3/polyvariant.html) This largely avoids the problems described in https://sled.rs/errors.html#why-does-this-matter 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 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.
- throwawaymaths 2y agoif you need values associated with your error you can stash them in an in-out parameter
- jamii 2y agoIf 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.
- throwawaymaths 2y agoi 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.
- hansvm 2y ago> 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.