Replies: 1 comment 1 reply
-
This is not allowed in Zig, C3, or Swift, as an example in Zig const DivideError = error {
DivideByZero
};
fn divide(dividend: u32, divisor: u32) !u32 {
if (divisor == 0) {
return DivideError.DivideByZero;
}
return dividend / divisor;
}
pub fn main() !void {
const result = try divide(100, 10);
_ = result; // use result
} If you call divide, you are always required to somehow handle divide should it be an error, whether it be saving it as an error optional type or evaluating it while handling the error case. Either way its an error to call divide without handling it's potential error behavior.
This is functionally just a reiteration of "Error Codes Can Be Easily Missed", which again is solved in the language demonstration I gave.
Hidden behavior is just as bad a solution, exceptions don't fix this problem, they just move this problem. Where as having expected/error-sets/faults as part of both the type system you don't need to hide the behavior while still addressing it without boilerplate.
You rarely ever want a game to crash even with errors when you have a dynamically language, and if you must there are dedicated scenarios to do so, exceptions are actually not a great solution in scripting for throwaway code most of the time.
First off that's wrong, secondly you can't define what exceptional means, its a buzzword without a definition, people already don't use exceptions for their intended purpose, even the standard libraries of numerous languages violate that rule in exceptions, it even includes cases where recovery for the program is impossible. If my program runs into an out of memory issue there is no feasible manner for which that program can respond aside from crashing, yet exceptions are used for out of memory issues. Exceptional cases are subjectively defined by nature and no one knows how to define what is and isn't exceptional. Especially when that doesn't even work as an argument anyway because allocations while out of memory is exceptional and yet unrecoverable. People use exception handling in performance-centric code routinely because they have no alternative.
Expected/error-sets/faults are type-safe, they use integers but they are not themselves integers.
They should, a state that can't be recovered from is not worth considering, and every case that a function could not be predicted to have that would inherently be in an unrecoverable state, if a stream can't write to a file, that's a recoverable state and predictable in the type system, memory being unable to allocate inside the stream calls is an unrecoverable state, it be even more catastrophic for the program, the unpredictable errors like such are that which puts the program in an unrecoverable state which is meaningless to the programmer because by that point none of his code would work anyway.
https://www.aolium.com/karlseguin/4013ac14-2457-479b-e59b-e603c04673c8 Also as a side point, exceptions require a complete rewrite of the entire runtime and language that itself would be completely incompatible with Godot's GDScript, we are simply not gonna do that and its not up for debate. It also demands recognition by Godot's language binding and marshaling system which requires rewriting almost every function binding and breaking compatibility with every language and existing project. This would never be considered for 4.x at all and I don't see reason to consider it beyond 4.x. In comparison error handling akin to Zig or Swift is comparatively trivial to implement, is just as robust, however limited in implementation still as we will never simply rewrite every function or function binding to take advantage of this. Perhaps useful as a language feature but it would be a pure GDScript feature which doesn't sit well with me honestly. |
Beta Was this translation helpful? Give feedback.
-
Judging by the reactions in this issue, a lot of people seem to agree that exception throwing and try/catch is useful. I also agree and will explain why Redot should implement them.
The current error-code-return-value system is a relic of C++, which is a language designed for extreme performance. While Redot is coded in C++, there is no reason for quirks of the language to be shown to users who are coding in GDScript, C#, and potentially other languages.
There are a number of problems with error-code-return-values, including with Godot/Redot's implementation of them:
Error Codes Can Be Easily Missed
If a function call is erroneous, it is often problematic for it to be ignored. Imagine the following example:
In an exception-based system, if
open_database
errors because, for example, the database does not exist, then that error will be thrown and the code will stop executing from there. The user can anticipate this error and do what they like with it (for example, wait before re-opening the database, or inform the user to create it).But in an error-code-based system,
open_database
will either return an error code, or even worse,null
. Because C++ and GDScript don't have good support for multiple return values (unless you want to return an array),null
is often returned, which is the worst of both worlds. You can see this scenario unfold withFileAccess.get_open_error
.Even if GDScript were to implement tuple return values, the solution is still far worse than exceptions.
Code Cannot Be Atomic
The developer is strong-armed into one of two problematic approaches.
The first is to ignore error-codes and write code as if the language supported exceptions. But this means that
connection.fetch_player()
would throw a hard-to-debug null reference error, and the code would keep on going. In some cases, that could be a big problem if code needs to be atomic:Obviously that is an unlikely example, but in almost every programming language, the developer expects code to run in sequence without changing execution order. This includes not skipping steps.
The Async-Everywhere Problem
Error-codes run into the same problem that
async/await
runs into, which is having chains of exception handling. Imagine the following:If
f4
needs to error, then all four functions have to support it.This violates DRY (Don't Repeat Yourself) and currently butchers all four functions' ability to return their own values.
So to avoid this, some developers end up resorting to...
No Choice But To Crash
When proposing exceptions to developers, I was told that exceptions promote using them redundantly as they're the "easy way out". But conversely, error codes result in a lot of developers end up taking the much worse easy-way-out and crashing (or logging an error and calling it a day.)
Error-logging doesn't work well either. It doesn't halt code execution (as explained before) and errors can be easily buried if you're dealing with a lot of errors already. Due to unsolvable engine bugs, I'm often dealing with 70+ unpreventable errors, and the solvable errors are logged somewhere in the middle. Perhaps most problematically, errors are not easily observed in production.
Exception Handling Doesn't Have To Be Fast
As the name implies, exceptions are exceptional. It is not common for a file to be un-openable or for some potentially faulty code to error. So, it's OK if you can only throw 100,000 exceptions per second.
Error Codes Should Not Be Integers
This is more of a gripe with the current implementation of error codes in Godot/Redot, but it's significant nonetheless. Imagine trying to log an error:
Don't Forget About C#
Since C# is a first-class citizen of Godot/Redot, it would be desirable for the features of the engine to acknowledge it. Currently, you have this unhelpful blend where engine APIs use error-codes and .NET libraries use exceptions:
Functions Should Not Need Permission To Error
I'm going to conclude the problems section with a statement that functions and APIs should not need "permission" to error in the form of a return type. Since in reality, pretty much any function can be used erroneously (e.g. Math.Sin(X) erroring if X is not a number), exceptions better support the idea that you can't code away every error.
Alternatives
There are several alternatives to error codes that could be considered.
Try/Catch
This would be the most familiar to a lot of users, coming from a variety of languages like Python, JavaScript, C#, Ruby and Java. It allows catching certain exceptions only.
Protected Calls
This comes from Lua. It might be easier to implement and uses no keywords.
Try/Else
This is an idea that I came up with which reduces boilerplate when catching general exceptions.
Alternatively:
Beta Was this translation helpful? Give feedback.
All reactions