c3c icon indicating copy to clipboard operation
c3c copied to clipboard

Improve `fault`

Open data-man opened this issue 3 years ago • 1 comments

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!")
}

data-man avatar Oct 13 '22 08:10 data-man

Already today of course, you can get "A" and "B" at runtime.

lerno avatar Oct 13 '22 09:10 lerno

So the question is if this is necessary? I think it doesn't seem that essential.

lerno avatar Feb 13 '23 14:02 lerno

Is it sufficient with the ordinal which is in?

lerno avatar Mar 31 '23 22:03 lerno

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 */
}

lerno avatar Apr 11 '23 07:04 lerno

"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);

lerno avatar Apr 11 '23 07:04 lerno

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.

lerno avatar May 22 '23 07:05 lerno

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")
}

data-man avatar May 23 '23 12:05 data-man

But how would you retrieve that information? @data-man

lerno avatar May 23 '23 20:05 lerno

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?

pierrec avatar Jun 18 '23 10:06 pierrec

There are two primary ways of doing this:

  1. Store the extended information in a thread local
  2. 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:

  1. If the pointer was allocated, who was freeing it?
  2. "Two pointer" sized errors are much more expensive to pass across function boundaries.
  3. 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!

lerno avatar Jun 19 '23 09:06 lerno

All valid points, thanks for giving the rationale!

pierrec avatar Jun 19 '23 09:06 pierrec

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.

lerno avatar Jun 19 '23 10:06 lerno

I might close this issue if there aren't any additional ideas on how to work with this.

lerno avatar Jul 09 '23 22:07 lerno

Closed due to lack of interest.

lerno avatar Jul 20 '23 17:07 lerno