Godot: GDScript error checking

Created on 25 Jan 2017  Â·  19Comments  Â·  Source: godotengine/godot

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

      GDScript func error_prone_function(): throw "Invalid IP", "<user-supplied IP here>", ERR_INVALID_IP

  4. To catch exceptions (pretty standard):
    GDScript 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:
    GDScript # This would throw an exception with a description "Error!" func error_prone_function2(): attempt: error_prone_function()
    Instead of
    GDScript # This would throw an exception with a description "Error!" func error_prone_function2(): try: error_prone_function() catch exception: throw exception
archived discussion feature proposal core gdscript

Most helpful comment

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.

All 19 comments

This looks like a duplicate of #3516, where @reduz said a firm No to exceptions.

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.

It's now been a month and almost nobody seems to have actually bothered to read my proposal. Aparently when most people saw the word "exception", they just quit reading (@neikeq, I'm refering to you). In another month, Im probably going to close this issue if nobody cares. If anyone has anything to say, please do.

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:
    gdscript 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 :tada: 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.

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:
    GDScript func error_prone_function(): return Error("ERR_SOMETHING", "Some error")
    Though a raise command similar to the following might be usufull, for simplicity:
    GDScript 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:
    GDScript 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):
    ````GDScript
    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 firstget_value()call (infoo()`)

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

Also, to respond to some of your points:

  1. Agreed, I just wanted to make it clear, that only unhandled errors would go to the debugger, so as to not spam the console with errors that are already handled in code.
  2. You are right again. Though I feel that my point on this is a bit vague (And not exactly what I wanted - I probably was too tired, sorry about that). I hope that my second proposal has fixed this.
  3. You mentioned that only description would be implemented for errors. Though I do understand why you would want to do that, I strongly urge you to reconsider: While it will be difficult to give an error code to every error in Godot, not doing so, would be a nightmare for developers that want to detect those specific errors in code. Would we have to check against the description? Not very practical. Also, regarding the point about returning the error code, what happens if a functions returns an int? do we pray that the error code is not the same as that int? Adding to that, would we have to do type checks on the return value, or would the return value be useless for returning data, on functions that might want to raise errors, if need be? What about functions like get_node()? Also, what If I want to get the error description in code, to create more readable error messages for the user?
  4. Again, I hope I've made my ideas clear on this on the second proposal
  5. Your probably right about this one. Maybe rethrowing is not really worth it.

I really hope that this is not getting to tiring, but I feel that this is important. Anyway, I await a response. If you still disagree... Oh well, at least I tried.

@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? :smile:

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...

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
.

How about using a kind of algebraic datatype for functions that are known to fail?

There could be a new core type (Result<ErrorType, ReturnType> or maybe Failable<ErrorType, ReturnType>, for GDScript it would be generalized to Result<Variant, Variant>)

template<class E, class R>
struct Result {
    bool is_error;
    union {
        E error;
        R value;
    };

    R get_value();
    E get_error();
    R operator ->(); // use as if it was a pointer to the real result for easier use in C++
};

That way you're forced to acknowledge the error and can handle it properly.

On the other hand, Godots class API is huge, rewriting it using such a result type would be very time consuming, questionable how useful it is anyway.

@karroffel No, godot's design philosophy is that you can have an error without having to handle it. Thus, if an image fails to load, you can still have your scene with a (now invisible) player.

Hmm yeah, with ADTs you don't get an empty image, you don't get no image at all. So error checking needs to be entirely optional.

This makes most error handling systems unsuitable for Godot. What is entirely optional and doesn't require any stack unwinding is a kind of "C errno" system.

some_function()
if Error.present:
    print(Error.error_msg)

With every new function call the last error state could get deleted, so you only ever see the error state of the last called method. (But that would have an performance impact, might not be that big because you'd only need to check one bool in Variant.call() to check, but manual resetting would be an option as well).

It's not pretty but it could work, also it could be implemented using the existing error macros, so existing code wouldn't need to be changed to report errors.

Like I said above, the resetting could be done automatically, but you don't want to catch errors that frequent, so I'd prefer to have a simple Error.clear() method that you call before calling the code that you want to know if it throws an error.

Error.clear()
some_function()
if Error.present:
    print(Error.error_msg)

While a generic way to handle errors would be nice, I do see the point of the Godot devs, so to keep everything nice and clean, I'll close this issue (should have done it earlier, but... well, doing it now).

I know I am a bit late to the party, but I am thinking, wouldn't it be nice if we had something like object.error for anything extended from Node? It wouldn't be a try/catch situation, but having it output OK or not OK would help somewhat with handling unexpected issues. Plus, this wouldn't change the Godot philosophy as Godot would continue to run like it didn't have this feature unless the developer decided to check a function ran OK.

@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).

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 :slightly_smiling_face:

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

I agree with RichardEllicott
Godot is really unstable, it crashes on me daily and to make matters worse the things that cause the most problems don't get fixed. aka ready time and tool scripts

EDIT: here is a literal example of a script that crashes godot

extends EditorImportPlugin


func get_importer_name():
    return "shadowblitz16.jsonresource"

func get_visible_name() -> String:
    return "JSON Importer"

func get_recognized_extensions():
    return ["json"]

func get_save_extension():
    return ".json"

func get_resource_type():
    return "JSONResource"

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var file = File.new()
    var err = file.open(source_file, File.READ)
    if err != OK:
        return err

    var dict = json2dict(file.get_as_text())

    file.close()
    var resource = parse_dict(Resource.new(), dict)
    return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], resource)

func parse_dict(resource:Resource, dict:Dictionary)->Resource:
    for k in resource.property_defaults.keys():
        if dict[k] is Resource:  resource.set(k, parse_dict(resource, dict[k]))
        else:                    resource.set(k, dict[k])

    return resource

func json2dict(text:String)->Dictionary:
    var json = JSON.new()
    var test = json.parse(text)
    if  test.error != OK:
        print("Error: could not parse jsons: "+test.error_line+"/n"+test.error_string)
        return Dictionary()
    elif not test.result is Dictionary:
        print("Error: root must be object")
        return Dictionary()
    return test.result as Dictionary

@Shadowblitz16 Please don't bump old closed issues with unrelated information. Please open a new issue with a minimal reproduction project attached instead.

Was this page helpful?
0 / 5 - 0 ratings