godot: GDScript error checking

I’ve been testing out the new multiplayer (I’m using the master branch), and as I was trying to make a basic server and client, I noticed that an invalid IP would cause an error to be printed in the Debugger. Issue is, that I really dont know how to detect the error, if it is even possible, and deal with it in code, as exceptions simply dont exist and the function returns 0 (which is the code for OK, if I’m not mistaken).

Seeing this problem, and a lot of others, maybe exceptions could be added in gdscript? I know that this has been addressed before, and rejected as godot aims to be difficult to crash. That is why I’m proposing the following:

  1. Have exceptions be non-fatal. That means that an exception would just go to the debugger and print out an error message if not caught, instead of crashing the program
  2. Exceptions should not travel back the call stack. This should:
    • Make it easy for godot to tell whether or not to print a debug message
    • Prevent unwanted behaviour, as the exception would not have to terminate the function, and keep doing so, until it reaches a try block.
    • Make it easier for developers to anticipate exceptions, as they would only have to worry about the immediate context of the try block, instead of whatever functions the functions inside the try block call
  3. To throw exceptions, the throw command would be used, which would accept an exception object, or for ease of use, the following parameters in order:
    • A brief description (string) - required
    • Any clarifications that may be useful (string) - optional
    • An error code (int) - optional The same parameters could be used to construct exception objects
    func error_prone_function():
        throw "Invalid IP", "<user-supplied IP here>", ERR_INVALID_IP
    
  4. To catch exceptions (pretty standard):
    try:
        error_prone_function()
    catch exception:
        print(exception)
    
  5. Optionally, as exceptions do not travel back the call stack, a command that would rethrow exceptions if they occur could be useful:
    # This would throw an exception with a description "Error!"
    func error_prone_function2():
        attempt:
            error_prone_function()
    
    Instead of
    # This would throw an exception with a description "Error!"
    func error_prone_function2():
        try:
            error_prone_function()
        catch exception:
            throw exception
    

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 11
  • Comments: 21 (11 by maintainers)

Most upvoted comments

I’ve already seen #3516, and @reduz’s response, and that’s why I’m proposing this altered model for exceptions. The in point is that exceptions should not travel back the callstack, so as to ensure that godot would keep working even if errors occur. This way, the engine’s stability would be preserved and error checking would be made a hole lot easier for developers.

this is going on and on… why can’t people who want exceptions just get them?

Implementing exceptions would add a significant amount of complexity to the GDScript implementation. At the end of the day, some people have to maintain it, so keeping it easy to work on is important 🙂

even though you say that, i don’t believe it’s the case

pcall in lua is simple enough, just add a function that executes another function by pointing to it

this function returns 0 or 1 (error codes, like they’ve already chosen)

i am pretty sure it’s a case of… they want it this way because they don’t like abusive programming patterns using error handling… but perhaps in the end we learn that, like perl, different users want different things

EXTRA: apparently godot is designed to keep going when you get errors but i find ANY script in the scene with one dang missing property or null reference crashes my entire game… not just the script like i had stuff set up in Unity

oh yeah, a lack of error handling does keep you on your toes! but it’s a woeful lack of understanding of dynamic programming patterns

this is going on and on… why can’t people who want exceptions just get them?

i’m not asking that reduz uses them… i want them, for MY program design that follow Pythons philosophy (duck typing, asking forgiveness instead of permission)

Currently i have to keep checking over and over is_instance_valid()… what you’re doing is forcing the creation of a new javascript by maintaining this stance. I constantly get crashes that would have been fine in the last world (script crashes, that’s okay, program continues)… but in this one, they kill the ENTIRE program (i can’t release a game like that!!!)

Designing complex systems under this restriction is just getting stressful… these guys just whip out the C++ oh yeah good for them… and keep us noobs using the Basic without having the features we want

this is going on and on… why can’t people who want exceptions just get them?

Implementing exceptions would add a significant amount of complexity to the GDScript implementation. At the end of the day, some people have to maintain it, so keeping it easy to work on is important 🙂

Thank you for the response @bojidar-bg!

First I’d like to state that what I basically want to propose is something similar to what you suggested in the last part of you post, though my phrasing probably wasnt clear enough. Also, I probably shouldn’t have called it “exceptions”, but I really dont know how to call it… Let’s call it “errors” for now. So, I’ll try and rephrase my proposal, to make it clearer (Also with some new, and hopefully better ideas). If you agree, I’ll also update my original post:

  1. An error object should comprise of the following:
    • An id string, to easily identify the error (or maybe do something similar to signals, where you define the signal name like a variable?)
    • A detailed, human readable cause, which would default to “no description”, if not specified
    • A default return value for the function, null if unspecified (this should make more sense later)
    • The position of the error in source. This would be filled by godot, upon the creation of an Error object
  2. “Raising” (Basically returning) an error, would be done as follows:
    func error_prone_function():
        return Error("ERR_SOMETHING", "Some error")
    
    Though a raise command similar to the following might be usufull, for simplicity:
    func error_prone_function():
            raise "ERR_SOMETHING", "Some error"
    
  3. If a function returns an error and is in a try block, the corrisponding catch clause would be called, and the function would evaluate to the default value specified previously. After that, the execution would continue normally. If the error is not thrown inside a try block, the error would be logged at the debugger.
  4. A try block, must be followed by one or more catch block(s). Each catch block takes a “parameter”, the id of the error to catch, and the name of the variable the error object would be assigned to, or no parameters to catch every error raised inside the try block: For example:
        try:
            print("Step 1")
            function1()
            error_prone_function()
            print("Step2")
            function2()
        catch "ERR_SOMETHING" err:
            print("Some error occurred: ", err.get_description())
        catch "ERR_SOMETHING_ELSE" err:
            print("Some other error occurred: ", err.get_description())
        catch err:
            print("An unknown error occurred: ", err.get_description())
        print("Continuing on...")
    
  5. The order of execution inside a try block, in case of an error, would be a little different than standard, as an error would not interfere with the rest of the code, and would only ‘call’ the catch clause, to give the code some feedback, and to be able to respond accordingly: Let’s asume the same code as (4), and that the error_prone_function() always raises an ERR_SOMETHING error. The output would be:
    Step1
    Some error occurred: Some error
    Step2
    Continuing on...
    
    So, the order of execution would be:
    print("Step 1")
    function1()
    error_prone_function()    # An error occured, let's 'call' the catch clause
    print("Some error occurred")    # The code somehow handles the error
    print("Step2")    # Execution continues as normal
    function2()
    print("Continuing on...")
    
  6. Errors do not travel back the call stack. To make it clear, let’s make an example (and also illustrate the default value):
    func get_value():
        return Error("ERR_SOMETHING", "Some description", 42)
    
    func foo():
        return get_value()
    
    func test():
        var i1 = 0
        var i2 = 0        
        try:
            i1 = foo()
            i2 = get_value()
            print("Not crashed!")
        catch "ERR_SOMETHING" err:
            print("Error: ", err.get_description())
        print("i1: ", i1, ", i2: ", i2)
    
    So, calling test(), would yield the following output:
    Error: Some error
    Not crashed!
    i1: 42, i2: 42
    
    And the debugger would log the error from the first get_value() call (in foo())

Hopefully I’ve made my ideas clearer, and I’m looking forward to hearing your opinion.

@alex-evelyn Maybe, but honestly I dropped this idea, since the only viable solution that we came up with were the C-errno errors, which I was never a fan of because you never know with absolute certainty where the error originated from. To me, the existing Error enum with custom types seem like a better option (maybe with a Result Rust-like type that @bojidar-bg mentioned - the documentation would get all kinds of messed up without generics though, if a single type with a variant was introduced).

But if you want to start up a discussion, maybe try opening up a new issue, since this diverges a fair bit from the original topic.


On a completely unrelated note (This really isn’t a snarky remark - by all means do open an issue if you think it’s worth implementing): Reading back, a huge thank you to everyone I talked with for their patience. Oh, did I get annoyed from my own persistence (This may not be the place, but I had to say it).

In general, Godot always has a ways to check if there was an error manually becore the error is printed to the console. If this is not possible, then we made a mistake

On Sat, Feb 18, 2017 at 11:22 AM, Bojidar Marinov notifications@github.com wrote:

@gtsiam https://github.com/gtsiam About the error codes, I’m not sure how doable this is, since there are many error calls in Godot, and doing it would mean going through each of them.

Actually, errors in Godot core are usually done using the ERR_* macros: (using (|) as identical to bash’s {,})

ERR_FAIL(m_message)ERR_FAIL_V(m_message, m_return_value)ERR_FAIL_COND(m_condition, m_message)ERR_FAIL_COND_V(m_condition, m_message, m_return_value)ERR_CONTINUE(_COND|)((m_cond,|) m_message)ERR_BREAK(_COND|)((m_cond,|) m_message)// … A few more I think

Because errors don’t travel up the stack, nor they terminate the function always (_continue and _break), it is totally possible that a function call would produce more than one error. That’s why any method we devise would have to return an array of errors observed during execution (but mind the threads, they are going to be quirky).

Now, another option would be to catch only those errors that in usual work would break the debugger in script. This means that errors raised in c++ code would still go to output, just like before, but, probably it isn’t an issue, is it? 😄

Also, I think we might want to change the try-catch to something else, like do-catch or whatever, since it isn’t a real try-catch…

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/7643#issuecomment-280848576, or mute the thread https://github.com/notifications/unsubscribe-auth/AF-Z2xuijTj4NAq01jdNXgVd-OI9woMsks5rdv6WgaJpZM4LtyJM .

Ok, let’s see if I can answer your points:

  1. This is already the case in non-debug runs. It would just pass on, and forget about the error, that’s a virtue of Godot.
  2. Not sure about this one, but I think most would not terminate the GDScript stack, but would continue going on, executing (you can test this by clicking continue in the debugger).
  3. In case this is somehow implemented, it would have only one thing – optional string description. If you want to return an ERR constant, just return it. Example:
    func something_bad_happens_here():
      raise "Reason, cannot be empty"
      return ERR_FILE_NOT_FOUND
    
    Currently the thing nearest point 3 is assert in GDScript (which does nearly the same)
  4. Ok, so here we begin the real proposal 🎉 I will discuss this one in a second
  5. Rethrowing isn’t too useful I think, but might have it’s uses. This depends on how 4 is handled, but can probably be handled by just 3.

So, about 4… #7223 is one way that this might be done. Under that proposal one can only do thing() else default_value though, so it is useless for try-catching, though we can return the same default value for rethrowing.

One other way I was thinking about in the past is to have

CallResult Object::call_error(method_name, args...);
class CallResult {
   Variant result = NULL;
   Error error = OK;
   String error_text = "";
}

That way we can make a simple try catch already, with something like this:

func call_something_that_fails():
  var result = some_object.call_error("oh_no", argument1, argument2)
  if result.error != OK:
    raise "How totally expected"
    return result.error
  else:
    return result.result

(I agree that this can benefit from syntax sugar, even if it doesn’t need it a lot) (Note that other functions are free to disregard your raises, and that it behaves exactly like the current C++ version of it)

Finally, I don’t think if there are others or more sensible ways of doing it, but I would be happy to hear about it if it is so.

Are things different in the Godot/C# world?

Asking because none of the GDScript unit test frameworks I used so far are able to reliably report errors (e.g. typo in test code) due to the inability to catch any sort of exception or error.

See https://github.com/godotengine/godot-proposals/issues/432.