Improve `fault`
The common pattern in many languages is something like this (in pseudo-code):
fault TheError
{
A,
B,
C
}
fn string err_to_string(TheError err)
{
switch (err)
{
case A: return "error A";
case B: return "error B";
case C: return "error C";
}
}
Proposal 1:
fault TheError
{
A: "The A error!",
B: "The B error!",
C // Here description is empty
}
println(TheError.A.description); //or A.desc
Proposal 2:
fault TheError(en: char[], ru: char[])
{
A: (en: "The A error!", ru: "Ошибка A!"),
B: (en: "The B error!", ru: "Ошибка B!")
}
Already today of course, you can get "A" and "B" at runtime.
So the question is if this is necessary? I think it doesn't seem that essential.
Is it sufficient with the ordinal which is in?
So the drawback of "Proposal 2" is that you would not have that property for all faults:
fault Abc (String s) { FOO("foo here"), BAR("was bar") }
fault Def { BAZ, XYZ }
...
if (catch f = something()) {
/* f is now type anyfault, so we can't do f.s unless we cast it to Abc */
}
"Proposal 1" is better:
if (catch f = something()) {
io::printfn("Error said: '%s'", f.description);
}
However, it is in this case probably better to have a general hash map:
translation_repository.register(Abc.FOO, .en = "The FOO error!", .ru = "Ошибка FOO!");
translation_repository.register(Abc.BAR, .en = "The BAR error!", .ru = "Ошибка BAR!");
...
anyfault x = ...
String desc = translation_repository.localized_name(x);
I'm unsure of this. @data-man what are your opinions in regards to using a hash map instead, esp now that the faults have ordinals.
It was probably not a good example.
Another use case:
fault OSError(code: int, desc: char[])
{
EPERM: (code: 1, desc: "Operation not permitted"),
ENOENT: (code: 2, desc: "No such file or directory")
ESRCH: (code: 3, desc: "No such process")
}
But how would you retrieve that information? @data-man
I am more interested in being able to decorate errors with contextual information (either source code position and/or runtime information based on the failed operation (e.g. file name that could not be open)). Any thoughts on how to do this today or in the future?
There are two primary ways of doing this:
- Store the extended information in a thread local
- Store the extended information in a passed-by-reference variable
If one would extend the fault semantics to allow appending information, it would by necessity need to be hacked on top of (1) to work transparently with C code. (2) Is in general more useful and flexible.
The reason this hasn't been addressed in the language, is that (2) is exceedingly cheap to implement by the user and would be recommended in pretty much all cases. In addition to this (1) would need to incur additional cost or require more restricted semantics.
Originally a fault had a pointer "payload". This could be used to store / reference additional information. This had additional problems:
- If the pointer was allocated, who was freeing it?
- "Two pointer" sized errors are much more expensive to pass across function boundaries.
- It was pretty much never used.
I mean yes, it is important to know what the filename was that didn't work, but if you write file::open(file_name, "rb") then you probably don't need that information. The main usecase for storing error data is when traversing a structure (json, xml, directory structure etc) and want to "bubble up" the error + where it occurred. In such a case you probably want to send down a "context" anyway, which is an excellent place to store "the last error", and it has the added flexibility of being able to store multiple errors and the actual path down to the error location. (For an example of the latter, say you encounter an error at foo.bar.baz, now locally, this element may not now the parents, so can only report its own "name" baz unless this is passed down in a context variable, at which point we can already use that for the error data)
So that's how it came to be that the fault payloads were removed: they were not good enough, they had to be managed, they weren't used, and even when used there was a better alternative!
All valid points, thanks for giving the rationale!
It's a reasonable question you ask @pierrec, because if there would have been no added runtime cost it would of course have been preferable to allow a payload.
I might close this issue if there aren't any additional ideas on how to work with this.
Closed due to lack of interest.