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:
GDScript
func error_prone_function():
throw "Invalid IP", "<user-supplied IP here>", ERR_INVALID_IP
GDScript
try:
error_prone_function()
catch exception:
print(exception)
GDScript
# This would throw an exception with a description "Error!"
func error_prone_function2():
attempt:
error_prone_function()
GDScript
# This would throw an exception with a description "Error!"
func error_prone_function2():
try:
error_prone_function()
catch exception:
throw exception
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:
ERR
constant, just return
it. Example:gdscript
func something_bad_happens_here():
raise "Reason, cannot be empty"
return ERR_FILE_NOT_FOUND
assert
in GDScript (which does nearly the same)So, about 4...
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:
GDScript
func error_prone_function():
return Error("ERR_SOMETHING", "Some error")
raise
command similar to the following might be usufull, for simplicity:GDScript
func error_prone_function():
raise "ERR_SOMETHING", "Some error"
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...")
error_prone_function()
always raises an ERR_SOMETHING error.
Step1
Some error occurred: Some error
Step2
Continuing on...
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...")
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 first
get_value()call (in
foo()`)
Hopefully I've made my ideas clearer, and I'm looking forward to hearing your opinion.
Also, to respond to some of your points:
get_node()
? Also, what If I want to get the error description in code, to create more readable error messages for the user?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.
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.