Faults with values
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.
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)
Do you feel something can be added to that discussion?
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.
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.
Are you proposing to print a trace even if they are handled?
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.
How would this be working in reality? How would it be implemented?
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.
The problem is that it isn't easily achieved, at least not accurately.
Is there some proposal how to solve this?
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.
The more I think about this, the less feasible it seems.
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.
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.