• Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

 Error codes, not exceptions

GMWolf

aka fel666
So the roadmap lists support for exceptions. But I believe it would be far better to use error codes, and so I wish to discuss it with the community such that YYg can make the right choice.

The problem I have with exceptions is that it completely messes up the code flow.
Take the following example:

Code:
try{
  spriteA = sprite_add(...);
  spriteB = sprite_add(...);
  spriteC = sprite_add(...);
} catch (ioException) {
  //do sokmething
}
If one of the sprite_get statement fail, who knows which one failed? This can be a problem if we later have to free the sprites.
This doesn't apply to sprites only, but pretty much to any function that would throw an exception.

Other issues include the propagation of exceptions.
If one function can throw an exception, then all function around that have to be wrapped in try blocks.

Even when you know an exception won't be thrown (because you already made the checks) , you still have to wrap functions in try blocks.


All these are avoided by using the far more elegant Error code.

Each method would return an error code, with 0 being successful, and other number indicating different errors.
This means we can use normal flow control (if/switch statements) to handle our errors.

"What about functions that return values?" I hear you ask. "How can you both retuurn a value and an error?".
Well the solution is simple: Multiple return values!
Take the go approach!

Let scripts and function return multiple values.
Sonadding a sprite goes from:
Code:
try{
 sprite = sprite_add(...);
  //Some more stuff
} catch(e) {
  //Do somehing
}
To
Code:
sprite, error = sprite_add(...);
if (error) {
  //Do somehing
}
//Something else
This is (imo) much cleaner, and make it much easier to follow the control flow.
 

gnysek

Member
Code:
try{
  spriteC = sprite_add(...);
} catch (ioException) {
}

try{
  spriteB = sprite_add(...);
} catch (ioException) {
}

try{
  spriteC = sprite_add(...);
} catch (ioException) {
}
See no problem here. You can transfer it to array and make a for-loop.
 

Alice

Darts addict
Forum Staff
Moderator
Furthermore, the exception itself can carry information. Say, if no file could be found for sprite_add, there could be FileNotFoundException with "spriteB.png" as a parameter, clearly indicating it's the spriteB that failed.

As for the need to wrap throwing functions with try/catch block; that is not an inherent trait of exception system. From what I recall, Java indeed requires exception-throwing functions to be wrapped, but C# has no such requirement (instead, uncaught exceptions crash the program altogether). I guess it's still worth to ask which exception handling model would be used here.
 

GMWolf

aka fel666
Furthermore, the exception itself can carry information. Say, if no file could be found for sprite_add, there could be FileNotFoundException with "spriteB.png" as a parameter, clearly indicating it's the spriteB that failed.
So could error codes.

@gnysek yay for disgusting syntax!

My main concern is introducing a new layer of complexity for something that should stay very accessible.

By using error codes and MRV, we keep a very simple flow control model, making error handline easy to grasp.

Try catch finally is actually a huge source of bugs in java. Many people forget to free resources using the finally clause, and put it at the end of the try block.
That means that if there is an error somewhere along the try clause, resources don't get freed.


Introducing MRVs to support error codes would be great for general development as well. (No need to wrap everything in an array/struct)
 
M

Multimagyar

Guest
Not a fan of exceptions myself, I would not mind the possibility but I would certainly avoid using it. Error codes like -10001 file is protected or -10002 path does not exists etc. I would see myself using it and would give me an easy case to print it in my console if something goes wrong. And since most of the items that *could* get an error code will be ones that returning some sort of index for something that will be a positive or 0 value anyway so you could just keep the error code negative without any sort of pain. I'm not too much of a fan of side effects either but if we would have a bit more control over the pointers, then I would mind less. That's one thing that grinds my gears since a while too. GM:S2 introduced a ptr() function but you practically cannot use it. Or I did not figure yet but that would be a "safe" side effect in my perspective rather than having something like a constant variable that takes the spot.
Anyway it's not like exceptions would not serve the same purpose, I just don't really like using them... Kind of a relic from my C/C++ times.
 

GMWolf

aka fel666
Not a fan of exceptions myself, I would not mind the possibility but I would certainly avoid using it. Error codes like -10001 file is protected or -10002 path does not exists etc. I would see myself using it and would give me an easy case to print it in my console if something goes wrong. And since most of the items that *could* get an error code will be ones that returning some sort of index for something that will be a positive or 0 value anyway so you could just keep the error code negative without any sort of pain. I'm not too much of a fan of side effects either but if we would have a bit more control over the pointers, then I would mind less. That's one thing that grinds my gears since a while too. GM:S2 introduced a ptr() function but you practically cannot use it. Or I did not figure yet but that would be a "safe" side effect in my perspective rather than having something like a constant variable that takes the spot.
Anyway it's not like exceptions would not serve the same purpose, I just don't really like using them... Kind of a relic from my C/C++ times.
Rather than use negative numbers for errors (let's stay away from bad practices), it would be much better to have multiple return values.
Errors could be of their own type tok
 

Alice

Darts addict
Forum Staff
Moderator
Furthermore, the exception itself can carry information. Say, if no file could be found for sprite_add, there could be FileNotFoundException with "spriteB.png" as a parameter, clearly indicating it's the spriteB that failed.
So could error codes.
Hmmm... how would you write a code with error codes that would clearly indicate where the code has failed? Especially in such a scenario:
> doing some stuff with external JSON files
> at some point, you use strings that are meant to be dictionary keys
> you made a typo, and one of keys is invalid

In that scenario, using exceptions, I'd make sure to throw a KeyNotFoundException with the faulty key being provided, or something like that. Integer error codes provide no such versatility, and 8-byte error real can't possibly handle contain all possible strings (even if we limit to up to 16 characters long or so). I still think the versatility of exceptions outweighs the simplicity of error codes.

Also, I guess it's a matter of aesthetics, but I can't see how this:
Code:
try{
  spriteC = sprite_add(...);
} catch (ioException) {
}

try{
  spriteB = sprite_add(...);
} catch (ioException) {
}

try{
  spriteC = sprite_add(...);
} catch (ioException) {
}
is significantly less elegant or more disgusting than:
Code:
spriteA, error = sprite_add(...);
if (error) {
  //Do somehing
}
else //STAHP!

spriteB, error = sprite_add(...);
if (error) {
  //Do somehing
}
else //STAHP!

spriteC, error = sprite_add(...);
if (error) {
  //Do somehing
}
else //STAHP!
In particular, the STAHP! part is really relevant, because often when you encounter an error condition, you want to stop whatever is going on and handle the damage.
With a program based on individual error codes, rather than try/catch blocks, you must handle each dangerous situation individually (repeating the STAHP! procedure), and handling them all together isn't possible without hacky workarounds.
In contrast, with exception-based code you can limit the scope of individual tries to single expressions, or you can wrap them all in a block if all of them are to be handled identically.

I guess the try/catch might be harder to learn, but the reward is worth it. Error codes might feel more "direct", but I can't see how the code made with mastered error codes ends up more elegant than equivalent code made with mastered exceptions... u_u'
 

GMWolf

aka fel666
Code:
spriteA, error = sprite_add(...);
if (error) {
//Do somehing
}
else //STAHP!

spriteB, error = sprite_add(...);
if (error) {
//Do somehing
}
else //STAHP!

spriteC, error = sprite_add(...);
if (error) {
//Do somehing
}
else //STAHP!
its more like:
Code:
spriteA, error = sprite_add(...);
if (error) {
 //STAHP!
}

spriteB, error = sprite_add(...);
if (error) {
  //STAHP!
}
but yeah, I see your point.
But this is with pretty simple cases, In my experience, try catch gets very messy with more complex programs.
for instance:
Code:
try {
   sprite = foo;
} catch {
  //uh oh
}
What is sprite after this? is it defined? undefined?
Its on in say, java, where it would be out of scope anyways.
but in gm?
 

Alice

Darts addict
Forum Staff
Moderator
What is sprite after this? is it defined? undefined?
Well, judging by general programming logic and specific GM behaviour, I'd expect that:
- if the try block executes without issues, sprite has the value of foo, whether it's a local or instance variable (I recall the funny thing about scoping in GM, with all local variables being declared for the entire piece of code)
- if for whatever reason the assignment fails (maybe it was not "sprite = foo", but rather "sprite = fooImmaThrowinMahException()"), I'd expect the value of sprite is the same as it was before, as if the assignment didn't happen

If I'm not mistaken, "throw" in the exception system basically tells "stop whatever you are doing and goto the applicable catch block or die trying". So, fooImmaThrowinMahException isn't evaluated, thus the assignment containing the call doesn't get executed, either, and value of the sprite variable remains as it was.

I don't really see much room for ambiguity here. I guess it might not be immediately obvious, even for people experienced with programming (let alone those for whom GM is the gateway to coding), and it might lower the accessibility. However, I don't see how exceptions system inevitably leads to ambiguity in code processing. o_O'

Out of curiosity, have you worked much with exceptions in C#? I noticed you mention Java here, and Java has that thing for forcing checked exceptions upon programmers. Working in C#, I used an exception-based code on numerous occasions, and I generally haven't encountered a situation when they were particularly confusing or annoying. Or maybe I just didn't wrote a code where such a problem would arise...?
 

GMWolf

aka fel666
Out of curiosity, have you worked much with exceptions in C#? I noticed you mention Java here, and Java has that thing for forcing checked exceptions upon programmers. Working in C#, I used an exception-based code on numerous occasions, and I generally haven't encountered a situation when they were particularly confusing or annoying. Or maybe I just didn't wrote a code where such a problem would arise...?
I have. Had to code an app using xamrin.
Its true that exception there did not annoy me as much as they do in java.
But i have also dabbled in GO and i LOVED the way they handled errors!
MRTs are also super useful, so would be nice to have the in GM...
 

gnysek

Member
Furthermore, the exception itself can carry information. Say, if no file could be found for sprite_add, there could be FileNotFoundException with "spriteB.png" as a parameter, clearly indicating it's the spriteB that failed.

As for the need to wrap throwing functions with try/catch block; that is not an inherent trait of exception system. From what I recall, Java indeed requires exception-throwing functions to be wrapped, but C# has no such requirement (instead, uncaught exceptions crash the program altogether). I guess it's still worth to ask which exception handling model would be used here.
Yeah, Exception message could be enough if contains line of code or arguments passed to function (but in some cases it might be unsecure...).
 
R

Rusky

Guest
Games tend to have a very limited set of error handling patterns, which make exceptions overkill. On the one hand, there are errors that should never happen at ship (missing files, etc.) which can just abort. On the other, there are very explicit errors like network connection failures that always need to be handled up front.

I don't love error codes, either, though- they're too easy to accidentally ignore. If GML is going to get a new language feature to handle errors, I would prefer it be sum types (also known as tagged unions, algebraic datatypes, enums with values attached, etc.). These are present in Haxe, TypeScript, Swift, Rust, etc. The idea is that you get a value that wraps either a result or an error object (basically what you would put in an exception object). To use the wrapped value, you first have to switch on the wrapper.

Its definition (probably built in, but you could make your own as well) might look like this:
Code:
enum Result {
    okay(value),
    error(error),
}
So instead of this:
Code:
sprite, error = sprite_add(...)
if (error) {
    // error
}
It might look like this:
Code:
var sprite, result = sprite_add(...)
switch (result) {
case okay(s):
    sprite = s
    break
case error(e):
    // error
}
Now, that may look like more work, but hear me out- you don't usually write a whole switch statement every time you use a sum type. Instead, you use helper functions or macros that handle the common cases. For example, the standard library could include an "unwrap" function that just aborts on an error:
Code:
sprite = unwrap(sprite_add(...)) // or sprite_add(...).unwrap() using methods
There might be a quick-return macro that evaluates to the wrapped value on success or returns a wrapped error from the enclosing function on failure (this is like explicit exceptions):
Code:
sprite = try(sprite_add(...))

// Rust even uses the ? symbol to make it even cleaner:
sprite = sprite_add(...)?
Swift has an interesting control structure to help out as well, that works like "if" (simplified to look like GML):
Code:
guard okay(sprite) = sprite_add(...) else {
    // error
}

use(sprite)
Sum types are also useful for far more than error handling. You can use them for state machines, for example:
Code:
enum State {
    running(/* data specific to running */),
    jumping(/* state specific to jumping *),
    ...
}

switch (state) {
case running(...):
    // handle running state
    state = jumping(...)
    break
case jumping(...):
    // handle jumping state
    state = falling(...)
    break
...
}
So, in the end, sum types are 1) cleaner and more flexible than exceptions 2) easier to use correctly than error codes and 3) also useful for other things.
 

GMWolf

aka fel666
It might look like this:
Code:
var sprite, result = sprite_add(...)
switch (result) {
case okay(s):
sprite = s
break
case error(e):
// error
}
Yeah. This is what I propose in my original post, and I think its the best fit for GML.
Error codes can totally be enums. C error codes are just a bad example.
Also, MRT support (returning multiple values from a function), across the board (scripts, functions, etc) would be very useful!
 
R

Rusky

Guest
It's slightly different- your proposal has sprite_add returning two values, which are both immediately accessible. My proposal has it returning only a single value, which only wraps one or the other, and requires your program to acknowledge which one you're working with.

It might be less ambiguous if written this way:
Code:
switch sprite_add(...) {
case okay(sprite):
    // can only use sprite here
    break
case error(error):
    // can only use error here
}
 

GMWolf

aka fel666
It's slightly different- your proposal has sprite_add returning two values, which are both immediately accessible. My proposal has it returning only a single value, which only wraps one or the other, and requires your program to acknowledge which one you're working with.

It might be less ambiguous if written this way:
Code:
switch sprite_add(...) {
case okay(sprite):
    // can only use sprite here
    break
case error(error):
    // can only use error here
}
Ah I see. But why?
I don't think this follows current GM's way of doing things.
Does this not fit GML better?
Code:
var sprite. result = sprite_add(...);
switch(result) {
   case error.ok : {
        //do things with the sprite
    } break;
   
    case error.FileNotFound: {
         //report missing file or something?
    } break;

    case error.SomeOtherError: {
         //do some other thing
    }
}
I think its a lot more to-the-point which is a lot more like GML IMO.

The other option would, of course, be to return things in a structure like you indicated. Derivatives of C (nessC for instance) do that a fair bit, and some C libraries go this route over the Error code global variable.
something like this?
Code:
var result = sprite_add(...);
if (result.error) {
 //look at error code and do something
} else {
    var sprite = result.sprite;   
//do something with sprite
}

HOWEVER, The struct/tuple approach would break all existing projects! that's because you would now have to process the variable returned from the function differently. YYG would never go for that.
MRTs are a better option (imo) because they will not break current projects. And retrofitting projects to use this system would be pretty easy:
Code:
///before change
var sprite = sprite_add(...);

//after change
var sprite, result = sprite_add(...);
if (result) {
   //handle error
}

What is YYG's train of though @Mike ? (sorry to bother you).
 
R

Rusky

Guest
Multiple returns being backwards compatible is a good point! There are two ways you could get backwards compatibility with my proposal:

1. You could add new APIs for cases that can fail (e.g. try_sprite_add), and let existing ones just abort on error as they do now. Either solution requires approximately the same amount of work to actually handle the error (exceptions are actually an interesting case to consider here).

2. Or, you could let the result type automatically unwrap when used as its success type. This would shift the location the error is reported, though, so might have worse error messages. GM could get around that by saving a call stack in the error variant, perhaps only in debug builds. This is closest to your proposal- you don't get an error until you try to use the sprite.

Either way, I suspect you maybe haven't quite grasped what I'm proposing? There would be no "result.error" and "result.sprite", because that has exactly the same problem as multiple returns- you can just access one without checking whether it's the right one. In my proposal, only one variant even exists at a time. That's why it's often described as an enum with data attached- an enum can only have one value at a time. That's where my wacky switch syntax comes from:
Code:
var result = sprite_add(...) // result is an opaque value, you cannot access result.anything yet
switch (result) {            // you *can* switch on it, though, or check if it's equal to something- just like an enum
case okay(sprite):           // only now do you actually have the sprite value- this is the only way to get it
    use(sprite)
    break
case error(error):           // in this branch, you cannot access the sprite, because the name isn't even in scope!
    handle(error)
    break
}
// at this point, sprite has gone back out of scope so unless you stored it somewhere else it's gone again- still can't access result.sprite
// that's why I suggested all those helpers- result.unwrap(), result?, guard okay(sprite) = result else {}, etc
 

GMWolf

aka fel666
Multiple returns being backwards compatible is a good point! There are two ways you could get backwards compatibility with my proposal:

1. You could add new APIs for cases that can fail (e.g. try_sprite_add), and let existing ones just abort on error as they do now. Either solution requires approximately the same amount of work to actually handle the error (exceptions are actually an interesting case to consider here).

2. Or, you could let the result type automatically unwrap when used as its success type. This would shift the location the error is reported, though, so might have worse error messages. GM could get around that by saving a call stack in the error variant, perhaps only in debug builds. This is closest to your proposal- you don't get an error until you try to use the sprite.

Either way, I suspect you maybe haven't quite grasped what I'm proposing? There would be no "result.error" and "result.sprite", because that has exactly the same problem as multiple returns- you can just access one without checking whether it's the right one. In my proposal, only one variant even exists at a time. That's why it's often described as an enum with data attached- an enum can only have one value at a time. That's where my wacky switch syntax comes from:
Code:
var result = sprite_add(...) // result is an opaque value, you cannot access result.anything yet
switch (result) {            // you *can* switch on it, though, or check if it's equal to something- just like an enum
case okay(sprite):           // only now do you actually have the sprite value- this is the only way to get it
    use(sprite)
    break
case error(error):           // in this branch, you cannot access the sprite, because the name isn't even in scope!
    handle(error)
    break
}
// at this point, sprite has gone back out of scope so unless you stored it somewhere else it's gone again- still can't access result.sprite
// that's why I suggested all those helpers- result.unwrap(), result?, guard okay(sprite) = result else {}, etc
No I understood, but I didn't think it would be very GML like (there would need to be a major type rework. I doubt YYG would do that).
So I proposed something closer to how GML works.

I don't think both values being accessible is an issue. At some point we have to stop treating programmers like idots, and just give them the tools they need. Don't overproff your tools.

The advantage of the MRT is that if you know the error is impossible, then you don't even need to check. (For instance, if you already made the checks programmatically, or if you can infer it somehow)
 
Last edited:
R

Rusky

Guest
Ah, alright. Just making sure. (Also for the benefit of Mike or anyone else reading.)

What about it would require a major type rework, though? It doesn't really affect the rest of the type system, such as it is. It can even be piggy-packed off of enums, as it is in several of the languages I listed.

I don't think both values being accessible is an issue. At some point we have to stop treating programmers like idots, and just give them the tools they need. Don't overproff your tools.

The advantage of the MRT is that if you know the error is impossible, then you don't even need to check. (For instance, if you already made the checks programmatically, or if you can infer it somehow)
Both of the options I listed also let you skip the check when you don't need it- either by providing a separate function for cases where you want to check, or by providing automatic conversions as an alternative to whatever error value your proposal would have to store in the "sprite" variable in the error case.

The second one pushes things beyond just "treating programmers like idiots" (though I like when my tools tell me where I broke something, tbh) and into "providing more information for when things go wrong." In your proposal, if you ignore the error but it turns out there was one, you'll get a different error later on- something like "undefined is not a sprite". In my proposal, if you ignore the error, you'll get something much more specific- "error(file 'sprite.png' not found)", perhaps even with a stack trace to the actual call the produced the error.

In that sense, you could even throw out the whole sum type concept. GML is already dynamically typed, so you could just directly return either the sprite or an error object! Then you could check for errors with an "is_error" function instead of a switch. Makes it less useful for other things like state machines, but it's more GML-like and requires even less type system extension.
 

GMWolf

aka fel666
Ah, alright. Just making sure. (Also for the benefit of Mike or anyone else reading.)

What about it would require a major type rework, though? It doesn't really affect the rest of the type system, such as it is. It can even be piggy-packed off of enums, as it is in several of the languages I listed.

Both of the options I listed also let you skip the check when you don't need it- either by providing a separate function for cases where you want to check, or by providing automatic conversions as an alternative to whatever error value your proposal would have to store in the "sprite" variable in the error case.

The second one pushes things beyond just "treating programmers like idiots" (though I like when my tools tell me where I broke something, tbh) and into "providing more information for when things go wrong." In your proposal, if you ignore the error but it turns out there was one, you'll get a different error later on- something like "undefined is not a sprite". In my proposal, if you ignore the error, you'll get something much more specific- "error(file 'sprite.png' not found)", perhaps even with a stack trace to the actual call the produced the error.

In that sense, you could even throw out the whole sum type concept. GML is already dynamically typed, so you could just directly return either the sprite or an error object! Then you could check for errors with an "is_error" function instead of a switch. Makes it less useful for other things like state machines, but it's more GML-like and requires even less type system extension.
what you are saying makes sense. I agree, but I dont like it.
As always language design can be a pretty personal thing. But Im not a fan of your second solution, And the alternative try_* methods will just end up with noone using them until its too late. (will also bloat the already bloated namespace some more).

So i guess this went as far as it can, its down to preference now. (But i sure do hope YYG likes what i do :) )
 
P

pushcx

Guest
Error codes are 70s programming, exceptions are 80s. Let's move up to the 90s with monads, no need to get modern with dependent types and proof assistants. :)
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
Multiple return values seem like a good idea up until the point where you consider that the code must compile to JS without rapidly degrading performance due to overfeeding the garbage collector with leftover return value containers. I've already seen this mistake being made in more than one Lua->JS compiler.

On the topic itself, I think it's not for the existing functions (a change would break backwards compatibility), but for general use - when working with user-provided data, you most often want to quit processing it if anything goes wrong rather than inserting an error check before virtually every operation to avoid having the entire game hard crash.
 

GMWolf

aka fel666
Multiple return values seem like a good idea up until the point where you consider that the code must compile to JS without rapidly degrading performance due to overfeeding the garbage collector with leftover return value containers. I've already seen this mistake being made in more than one Lua->JS compiler.
I think this can easily be fixed with some propper static analysis.
You could only return the 'requested' values, and if only one is requested, return them without it being wrapped in continer.
Code:
///function "foo" returns 3 values, x, y, and error

x, y, error = foo(); //foo returns a container with all three values

x, y = foo(); //foo returns a continer with values x and y

x, , error = foo(); //only x and foo is returned (notice the empty id for y)

x = foo(); //returns x only, not in a comtainer

, y = foo(); // returns y only, not in a container.
Here i use an empty token as a spacer, but any character could be used, like ~ for example.

Another option is to use a global stack. Its how i handled it in my project.

On the topic itself, I think it's not for the existing functions (a change would break backwards compatibility), but for general use - when working with user-provided data, you most often want to quit processing it if anything goes wrong rather than inserting an error check before virtually every operation to avoid having the entire game hard crash.
Im not sure i understnd, is this for or against MUltiple return values?
Because exceptions tend to hard crash programs if try/carch is left out.
Erro codes allow you to quit/retry procesing more easily (and intuitively).

Error codes are 70s programming, exceptions are 80s. Let's move up to the 90s with monads, no need to get modern with dependent types and proof assistants. :)
Monads? I domt think they have nything to do with this... do they?

Error codes are kinda surfacing back up.
Sure, global error cocess are from the 70s, but using multiple values is a much more modern idea.
Proof assistants are great though! Would never work with GML though.


Furthermore, the exception itself can carry information. Say, if no file could be found for sprite_add, there could be FileNotFoundException with "spriteB.png" as a parameter, clearly indicating it's the spriteB that failed.
I realize i never explained that part super well.
Error would not be a boolean value, but would carry information about what the error is.
Code:
sprite, error = sprite_add(...);
if (error == file_not_found) {
  //do something about missing file
  //return?
} else if (error == error_reading_file) {
    //do something else
    //return?
} 
//all seems good here! Go on procesing
 
Last edited:

GMWolf

aka fel666
I would also like to point out how multiple return values can be a much more powerful alternative to exceptions:
Code:
sprite1, e1 = sprite_add(...);
sprite2, e2 = speite_add(...);

if (e1 && e2) {
 sprite1 = spr_notLoaded;
 sprite2 = spr_notLoaded;
 return;
}

if (e1) {
   sprite1 = sprite2;
    return;
}

if(e2) {
    sprite2 = sprite1;
    return
}
Imagine writing that with exceptions. Not as intuitive.

Not to mention MRV would be nice not only for exceptions, but for everything else too!
Being able to return multille detached values like that will save on a lot of array dereferencing!
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
Imagine writing that with exceptions. Not as intuitive.
I'm not sure what the actual use case for that would be, but no one would prevent you from making a wrapper script that would return whatever you want (sample).

I think this can easily be fixed with some propper static analysis.
You could only return the 'requested' values, and if only one is requested, return them without it being wrapped in continer.
If the functions never throw errors and always return error codes, you always have to check error codes, or deal with broken/default return values. Just like in those ANSI C libraries, you know - an if-branch after every single API call. "good times".

If one of the sprite_get statement fail, who knows which one failed?
By adding a marker variable much like you would with anything else done in a sequence. Or making a wrapper script (why would you halt loading the rest of the sprites if one doesn't exist?). Or pushing everything into an array as already suggested. It's not like there are basic unresolved problems with a concept that is in active use since 1960s.

Other issues include the propagation of exceptions.
If one function can throw an exception, then all function around that have to be wrapped in try blocks.
Coincidentally, if you cannot catch errors/exceptions, error-prone segments of program (such as those working with user-defined inputs) can become 90% error handling instead of having a single try-catch block around them:
upload_2017-8-8_11-43-36.png

To conclude: exception handling is intended for situations where you do numerous operations (multiple of which may fail), and would rather not waste time adding input validation/error checking before/after every step. While multiple return values can serve their own purpose (at a price), that would be better discussed separately instead of hijacking your very own topic.
 

rwkay

GameMaker Staff
GameMaker Dev.
My own position on Exceptions is that they should only be used for Exceptional circumstances i.e. Divide by Zero is exceptional, but File Not Found is not...

We will continue to use error codes for most things, but exceptions will be used for things that are Errors at the moment (i.e. variable not found would be thrown as an Exception, and if not handled will result in the Error Dialog you see now) but it can be used to handle exceptional things.

Extension authors will be able to use them as well.

Russell
 

FrostyCat

Redemption Seeker
@rwkay:

Would it be possible to add a top-level exception handler that can catch uncaught exceptions from anywhere else? This would be useful for custom error messages and bug reporters, where retrofitting every existing and future piece of code with the try-catch would be infeasibly error-prone.
 

rwkay

GameMaker Staff
GameMaker Dev.
The plan is to have allow the setting of a global unhandled exception handler so that the default error dialog can be avoided (if that is desired)

Russell
 
R

Rusky

Guest
Would it make more sense to provide *just* an error handler rather than the entire exception handling machinery? It feels like a lot of unnecessary and thus harmful complexity for GML's purposes.
 
Top