c3c icon indicating copy to clipboard operation
c3c copied to clipboard

Faults with values

Open erenjanje opened this issue 10 months ago • 12 comments

Currently, faults behave pretty similar to enums, but they do not have associated values unlike them. Associated values in faults may make it possible to have some additional info about faults such as detailed descriptions or error codes (so that returning the error code of a fault should make the program exit with a suitable error code instead of returning something like 1). The syntax may be the same as enum values without declaring the underlying type:

fault MyFault : (String description, int error_code) {
    SOMETHING_WENT_WRONG = { "something went wrong", 1 },
    FILE_NOT_FOUND = { "the requested file was not found", 0xDEADBEEF }
}

Runtime associated values for faults (but probably not enums) would be awesome, since then more info about the error can be propagated like the name of the not found file or the stacktrace when the error happened, but I think this may be too much as I guess the design of the implementation would be a nightmare, e.g. since anyerror would become a pointer to support any kind of error, but the place it points cannot be allocated on stack, needing an allocator etc.

erenjanje avatar Jan 26 '25 21:01 erenjanje

This has been suggested before, see #632. In particular why the idea to use associated values with faults isn't solving the problem of adding information to a fault (you need to know what fault it is to get the data, and then you already know the data)

lerno avatar Jan 26 '25 23:01 lerno

Do you feel something can be added to that discussion?

lerno avatar Jan 27 '25 10:01 lerno

I just checked that discussion and everything I was wondering was already discussed there. I searched issues about faults but I forgot to check closed issues, so this duplicate issue is a fault of mine. For static associated values, I did not think that faults were implemented so differently compared to enums, and now it makes sense why they don't have associated values unlike enums. I was also wondering how to handle error payload in my application (and I was stuck hence this question), so finding other ways to do payloads in that discussion was very helpful.

erenjanje avatar Jan 27 '25 14:01 erenjanje

Interesting discussion, I would onboard the request. But let's discuss it at a different angle.

First of all, I'd agree with @lerno that it's better place to report error information at the error if (catch err = optional), but when we speak about development process and many re-throwing calls in the code (foo return Foo.ERROR? -> bar() ! -> baz()! -> fuzz()! -> main()! -- prints "Opps Foo.ERROR) the result is hard to diagnose.

What about having an optional error tracebacks? It could be on language level (enabled with c3c flags) or @macro level, so in order to diagnose the full path of the error the macro could print a simple call stack:

foo return Foo.ERROR? -> bar() ! -> baz()! -> fuzz()! -> main()! -- prints "Opps Foo.ERROR can be converted to:

void! foo(int a) {
    if(a == 0) return @throw(Foo.ERROR)?; // or without @throw if it's in language
}

void! baz(int a) {
   @except(foo(a)!); // rethrow and prints current file:line traceback
}

void! fuzz(int a) {
   @except(baz(a)!);  // rethrow and prints current file:line traceback
}

int main() {
   if( catch err = fuzz(a)) {
     io::printfn("Ooops %s", err);
   }
}

The code above may print the following traceback:

foo.c3:23 foo(): thrown Foo.ERROR;
baz.c3:33 baz():  ^^^ (foo(a)) 
fuzz.c3:33 fuzz():  ^^^ (baz(a)) 
Opps Foo.ERROR

It's too verbose for production, but in test environment it could be beneficial to see the full error path.

Again, it looks it fully doable with macros. Maybe c3c could add a hook function for every error thrown and re-thrown via !. So the tooling could inject its code into this and print diagnostic messages when necessary.

alexveden avatar Jan 27 '25 15:01 alexveden

Are you proposing to print a trace even if they are handled?

lerno avatar Jan 27 '25 20:01 lerno

I would propose introducing a similar mechanism we have for panic functions at first and experiment with it:

fn void unhandled_error(Anyfault fault, bool is_rethrown, String file, String function, uint line)

This function can be empty by default, or contain code which is activated by compiler flag for example --error-verb: 0 - no prints, 1 - print who fails, 2 - print all re-throws too.

So optionally with some tooling one can alter this function (as we do with test suite runners) and print unhandled errors stacks when necessary, without interfering with default c3 behavior.

alexveden avatar Jan 28 '25 05:01 alexveden

How would this be working in reality? How would it be implemented?

lerno avatar Feb 03 '25 09:02 lerno

How would this be working in reality? How would it be implemented?

The main purpose of my proposal to improve debugability of unhandled errors. I don't know if it's possible by c3c compiler design to detect if the error was handled or not. Or if it's possible to trace all returned errors in the code on the code generation level.

In production code, this will be disabled, of course.

alexveden avatar Feb 03 '25 15:02 alexveden

The problem is that it isn't easily achieved, at least not accurately.

lerno avatar Feb 03 '25 21:02 lerno

Is there some proposal how to solve this?

lerno avatar Feb 13 '25 20:02 lerno

What do you think of adding instrumentation:

  • Optionally create a global array to store details of program execution (function calls and any other required information, it may make sense to have some options here of varying cost?)
  • Functions are compiled with additional instrumentation to record information into those arrays as the program runs
  • Faults in this optional mode could include the index into this/these global arrays of different runtime information, such that the context of the error could be reproduced and played back with the required level of detail
  • Global arrays have a known lifetime so could be automatically cleaned up on error, index has a fixed size making the fault a fixed known size.

joshring avatar Feb 27 '25 13:02 joshring

The more I think about this, the less feasible it seems.

lerno avatar May 15 '25 21:05 lerno

Given that we now have even simpler faults I think this is out of the question. We could possibly consider adding some payload that is definable for every error, but what would be the use? Use a result type when more data is needed.

lerno avatar Aug 01 '25 22:08 lerno

I'm willing to reopen this, but I would need a solid proposal on how it would work and how it would retain zero overhead compared to the current faults, and what problems it would solve.

lerno avatar Aug 01 '25 22:08 lerno