https://matklad.github.io/2025/11/06/error-codes-for-control...
Honestly I was quite convinced by that, because it kind of matches my own experiences that, even when using complex `Error` objects in languages with exceptions, it's still often useful to create a separate diagnostics channel to feed information back to the user. Even for application errors for servers and things, that diagnostics channel is often just logging information out when it happens, then returning an error.
Library developers tend to choose the path of least resistance, which is to not pass diagnostic information.
The most convenient diagonistic system is the good old logging. Logging is easy.
Maybe logging will be the de facto solution of passing error data in Zig ecosystem, due to psychological reasons.
If i found no consistency id be making a post like OP but from a different perspective.
I haven't seen anyone use a global allocator in the way you're talking about, and if you did I feel like it goes directly against the Zig ethos. Part of the benefit of allocators being passed around is that all allocation are explicit.
In this context, adding data to an error may be expedient but 1) it has a non-trivial overhead on average and 2) may be inadvisable in some circumstances due to system state. I haven't written any systems in Zig yet but in low-level high-performance C++20 code bases we basically do the same thing when it comes to error handling. The conditional late binding of error context lets you choose when and where to do it when it makes sense and is likely to be safe.
A fundamental caveat of systems languages is that expediency takes a back seat to precision, performance, and determinism. That's the nature of the thing.
I agree that in special states like OOM passing error data with allocation is not ok.
Have you used it in any large projects?
If Wikipedia has deadnamed Raku with grace then that might be a model to follow, but in general it's far from helpful unless it's done carefully. There's a reason why the community embarked on the somewhat painful multi decade process of renaming it. To try clarify my understanding I'll summarize it here.
Because of the original name for Raku, people assumed for a long time (long after it became problematically misleading) that it shared semantics, or syntax, or compiler tech, or libraries, or something technical like that, with some other programming language(s).
This was partly because Raku did indeed take some inspiration philosophically and/or technically from some existing languages (traces of a half dozen or so are evident), and partly because Raku and its compiler tech features the ability to use Python code and compilers, and C code and compilers, and Perl code and compilers, and so on, as if they were native Raku code/compilers.
But Raku was never C, or Python, or Perl, and the old name is unfortunately a form of deadnaming -- which is to say, something that is seldom helpful, especially if some commentary like this comment is not included.
At least, that's how I experience it.
That said, regardless of my view, I love your impulse of being helpful, which is why I've written this -- and I hope it does help any readers.
The problem with sigils is that they compose poorly when casting (refs, counts), and do not generalize to other types.
Plus, they seem to encourage the language designers to implement semantic that is "context aware" which would have been another billion dollars mistake if perl had become more popular.
In other words, that's unnecessary complexity bringing the attention to a poor type system. A bad idea that deserves to die, in my opinion.
What do you mean? All the keywords in the standard language are ASCII. And even if you meant some other part of the standard language, they're (almost) all ASCII too.
(The most notable exceptions are use of superscript 0 thru 9 for powers -- eg `2¹⁶-1 == 65535` -- and the `«` and `»` characters, which are part of the Latin-1 aka "8 bit ASCII" character set, are aliases for the (7 bit) ASCII `<<` and `>>` tokens. I understand these exceptions may concern you, but can assure you they're not really a problem in practice.)
> The problem with sigils is that they compose poorly when casting
Are you thinking Raku sigils are like sigils in other languages, eg Perl or Javascript or PHP?
From my perspective one of the several _strengths_ of Raku's sigils is that they combine succinct compile time type constraints and composition.
Just type `@` (reads as "at") to denote a compile time enforced type constraint for the `Positional` interface, an abstract/generic datatype for any iterable integer indexed collection.
So, if you have an array of points, Raku will happily let you store it in a variable named, say, `points-array`, but naming it `@points` means Raku will compile time enforce binding of that name to an integer indexed collection and visually reflect that that is so for any human glancing at the code.
As for "casting", if you want to treat `@points` as a Single Item ("a single array of points") then just write `$@points` -- the `$` reads as Single Item -- `S` over `I`.
(Technically speaking that's not time consuming casting, but just stripping off an indirection, the optimal optimization of that scenario, but I am guessing this is semantically the kind of thing you meant.)
> (refs, counts)
Again, are you thinking that Raku's sigils are like other languages'? (They're not.)
> and do not generalize to other types.
Again, are you thinking that Raku's sigils are like other language's sigils? They are not.
Raku's `$` sigil is the generic Single Item interface for any datatype. (It can be parameterized with a data structure's typed structure.)
The `&` sigil is the generic Single Item interface for any function type. (It can be parameterized with a function's type signature.)
The `@` sigil covers any integer indexed collection. (It can be parameterized with the collection's fixed length for a fixed size array, or shape for a multidimensional structure. To parameterize a nested heterogeneous data structure's type signature, use `$` instead.)
The `%` sigil covers any name indexed collection. (It can be parameterized with the collection's key/value types. To parameterize a nested heterogeneous data structure's type signature, use `$` instead.)
> Plus, they seem to encourage the language designers to implement semantic that is "context aware" which would have been another billion dollars mistake if perl had become more popular.
Why are you mentioning Perl in a subthread about Raku? Are you aware the language was renamed precisely because so many people were completely misunderstanding the nature of Raku?
> In other words, that's unnecessary complexity bringing the attention to a poor type system. A bad idea that deserves to die, in my opinion.
If you're thinking of Perl's type system and applying what you know of that to Raku's, that's like thinking Python's type system is like Haskell's. They are very different.
> how to handle errors and diagnostics, though it's an area of active exploration
I am flabbergasted and exasperated by this sentiment. Zig is over 9 years old at this point. This feels this same kind of circular arguments from Golang "defenders" about generics and error handling.If you look at the current Zig website the hello world example doesn’t compile because they changed the IO interface. Something as simple as writing to the console.
It’s easier to get things right if you have no issues breaking backward compatibility for a decade. It feels it’ll be well over 10 years before Zig is “1.0”.
https://github.com/ziglang/zig/issues/2647#issuecomment-2670...
IMO this is not a good reason at all.
We are free to do that as a return type like `Result(T)` and just forgo using `try`, but yeah, I wish this was in there.
And even if it did, interpreting the error generally doesn't every work with a microscope over attached data. You got an error from a write. What does the data contain? The file descriptor? Not great, since you really want to know the path to the file. But even then, it turns out it doesn't really matter because what really happened was the storage filled up due to a misbehaving process somewhere else.
"Error data" is one of those conceits that sounds like a good idea but in practice is mostly just busy work. Architect your systems to fail gracefully, don't fool yourself into pretending you can "handle" errors in clever ways.
Also, a line number is often helpful, which is why compilers include it. Some JSON parsers omit that, which is annoying.
That's not error data, that's (one level of) a stack trace. And you can do that in zig, but not by putting call stack data into error return codes.
The conflation between exception handling and error flagging (something that C++ did largely as a mistake, and that has been embraced by managed runtimes like Python or Java) is actually precisely what this feature is designed to untangle. Exception support actually turns out to have very non-trivial impact on the generated code, and there's a reason why languages like Rust and Zig don't include them.
Whereas lazy devs could just attach all possible data in a giant generic error if they don’t want to think about it.
Okay maybe theorically, but in the real world I would like to have the filename on a "file not found", an address on a "connection timeout", a retry count on a "too many failures", etc.
I’d like my parser library to be able to give me the exact file, line and column number an error occurred. But I’d also like to use the library in a “just give me an error if something failed, I don’t really care why” mode.
How useful is a file not found error type without data (the filename) when the program is looking for 50 files? Not very.
How useful is a generic error type with '{filename} not found' as generic string data packed in? Quite.
But that is not implemented.
In any case, when debugging annotating error with extra context often is not enough. One often needs a detailed trace of what happens before.
So what I would like to see in any programming language is ability to do a structured logging with extra context from the call stack (including asynchronous support in languages that have that) that has almost zero overhead when the log is not printed.
Various languages and runtimes have some libraries that try to do that, but the usage is awkward and the performance overhead is not trivial.
I want off this ride.
I find it really amusing that we have a language that has built its brand around "only one obvious way to do things", "reducing the amount one must remember", and passing allocators around so that callers can control the most suitable memory allocation strategy.
And yet in this language we supposedly can't have error payloads because not every error reporting strategy is suitable for every environment due to memory constraints, so we must rely on every library implementing its own, yet slightly unique version of the diagnostic pattern that should really be codified as some sort of a language construct where the caller decides which allocator to use for error payloads (if any).
Instead we must hope that library authors are experienced and curious enough to have gone out of their way to learn this pattern because it isn't mentioned in any official documentation and doesn't have any supporting language constructs and isn't standardized in any way.
There must be an argument against this (rather obvious) observation but I'm not aware of it?
I want a function like `diffFiles(path_a, path_b)` to have an error set of `error { ReadError }` with more detailed information in the payload (e.g. file path, translated error code). The alternative is: `error { FileAReadErrorNotFound, FileBReadErrorNotFound, FileAReadErrorPermissionDenied, FileBReadErrorPermissionDenied, ...}`
See, we were trying to make this data we had into a string, Rust says all strings are UTF-8 encoded - but, turns out the data wasn't UTF-8 after all, here's an error with the data inside it.
Or a really delicate piece of design, (nightly for now) Vec::push_within_capacity. We're hoping the growable array (Vec<T>) has enough space for this T, thus getting rid of it, but if not we don't want to grow the array, maybe we're bare metal software and can't afford to allocate on this hot path, so we get back an error with the T we were trying to push onto the array inside it, so we can do something else with that T but only when the problem happened, otherwise it's gone.
Somethings there are 100 things that possibly go wrong. With error data you can easily know which exact thing is wrong. But with error code you just know "something is wrong, don't know which exactly".
See: https://github.com/ziglang/zig/issues/2647#issuecomment-1444...
> I just spent way longer than I should have to debugging an issue of my project's build not working on Windows given that all I had to work with from the zig compiler was an error: AccessDenied and the build command that failed. When I finally gave up and switched to rewriting and then debugging things through Node the error that it returned was EBUSY and the specific path in question that Windows considered to be busy, which made the problem actually tractable ... I think the fact that even the compiler can't consistently implement this pattern points to it perhaps being too manual/tedious/unergonomic/difficult to expect the Zig ecosystem at large to do the same