Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add document about error methods #17

Merged
merged 2 commits into from
May 17, 2019
Merged

Add document about error methods #17

merged 2 commits into from
May 17, 2019

Conversation

p0nce
Copy link
Contributor

@p0nce p0nce commented May 3, 2019

I think is the current state of the consensus. Destroy!

@TurkeyMan
Copy link
Contributor

Thanks for creating this! I wanna discuss this at dconf!

@TurkeyMan
Copy link
Contributor

Incidentally, I don't know if I'm on board... maybe I am... I really don't know.
I don't see why out parameters are so upsetting.

I'd like to expand this document to detail the options considered, and the arguments for/against.

@p0nce
Copy link
Contributor Author

p0nce commented May 4, 2019

yes we can talk about it at Dconf.
Consider the following JPEG decoder internal:

bool decodeImage(out ImageBuffer buffer, out string errorMessage)
{
    // check SOI marker
    if (!expectMarker(0xFFD8))
    {
        errorMessage = "no SOI marker";
        return false; // alternatively: buffer = ImageBuffer.init;
    }
    // bla...
}

There is 3 piece of information to return: the buffer, an error message, and whether the operation succeeded (could use ImageBuffer.init to avoid the bool and have 2 instead).

With Result.T:

Result!ImageBuffer decodeImage()
{
    // check SOI marker
    if (!expectMarker(0xFFD8))
    {
        return Result!ImageBuffererror.fail("no SOI marker");
    }
    // bla...
}

Also if Result can own a string, it would lead the path towards error strings that can be sprintf. Currently ownership of the string is in the territory of "it will be immutable don't worry"

@TurkeyMan
Copy link
Contributor

TurkeyMan commented May 4, 2019

I returned the string in my functions, and null is 'no error':

string decodeImage(out ImageBuffer buffer)
{
    // check SOI marker
    if (!expectMarker(0xFFD8))
        return "no SOI marker";
    // bla...
}

Yeah, I don't really wanna think about string allocation for dynamic messages right now :/

@TurkeyMan
Copy link
Contributor

To be clear, I'm not saying this is great... but that it's functional, it's theoretically faster, and it doesn't require bonus machinery.

@igor84
Copy link
Contributor

igor84 commented May 4, 2019

I agree those are the pros and I will add one:

  1. The code of the function that needs to handle and return error is super simple and intuitive.

I would say the cons are:

  1. When you type the code that uses it you always annoyingly remember too late and have to go back to define variables that you want to use as out params (at least that is how my brain works :))
  2. The usage code becomes less readable because people usually read params as input values in their mind and need a milisecond of additional brain power to relize when it is not the case.
  3. It can lead to unintuitive things like: if (!decodeImage(buffer)) { /* image is successfully decoded */ }.

For library public API, readability might be more important than efficiency, at least to some degree. I guess that is the main point around which we need to agree on.

@p0nce
Copy link
Contributor Author

p0nce commented May 4, 2019

string decodeImage(out ImageBuffer buffer)
{
    // check SOI marker
    if (!expectMarker(0xFFD8))
        return "no SOI marker";
    // bla...
}

This is clever but then the correct way to test for success is to write:

if (!decodeImage(buffer))
{
    // success
}

which is a bit like strcmp... a nest for errors. It is really surprising to read.

@dayllenger
Copy link

dayllenger commented May 5, 2019

One more variant is to make Result only to hold a message and boolean error code (negative message length, for example). Signatures will look like:
Result decodeImage(out ImageBuffer buffer).

Pros:

  • don't need to consider the lifetime of the value, nesting inside Result!T
  • easy to format and own the message, if needed
  • consistent with ref parameter, when the function reuses memory or appends
  • can behave like a boolean error code with opCast(To : bool)

Cons:

  • out parameter: always need to define variables beforehand, does not support chaining like retrieve(args).failed(defValue)
  • does not enforce error checking - Result!T can throw an assert error when trying to unpack the value, which is "not exist"

@rikkimax
Copy link
Collaborator

rikkimax commented May 5, 2019

Whatever method we come up with, I would hope that it is only transitory until -betterC gets exceptions.

My preference would be Result!T instead of an out parameter since it would be closest to if we had exceptions in user code.

@p0nce
Copy link
Contributor Author

p0nce commented May 12, 2019

While I agree, it seems there are no plans that -betterC ever gets exceptions (we asked the question several times).

I've updated the document to account for the discussion at DConf.
I don't think we can have a single error-mechanism, since:

  • implementer want to use what they want for private function, and we don't need to specify this
  • -betterC will never have exceptions, but exceptions are the best in code that doesn't have to be -betterC (and this is TBD we don't know which code will need to be -betterC)
  • Result!T is mostly useful for rich error messages, which are mostly useful for parsers. For smaller functions, returning a bool may be easier to write.

PLEASE VOTE

@ahmetsait
Copy link
Member

I'm fine with either approach but please use error codes instead of just strings. I can't imagine having to check for string equality to determine why the function failed and branch my own code accordingly. Same with bool out params, if the function can fail for a variety of different reasons then it should return an int and it should be documented properly what the error codes mean.
Additionally, we can kind of emulate Error/Exception behaviour by returning a negative value for programmer errors (and positive ones for exception-like situations) so it's easier to check.

@igor84
Copy link
Contributor

igor84 commented May 12, 2019

We probably should add int error codes, but note that in png reader at least there are basically 2 types of errors: out of memory and png file is invalid. The message why is it invalid could be useful to the human to read but there is nothing you can really code based on that reason.

@p0nce
Copy link
Contributor Author

p0nce commented May 12, 2019

@ahmetsait OK but then we declare a special return value WG_NO_ERROR == 0 for no error? (like other libraries do?). Good idea about Error vs Exception which is the dark side of error codes.

@igor84 yeah we already get an irrecoverable error and a recoverable one so better distinguish them like @ahmetsait suggested, which is neat. <0 is Error, > 0 is Exception, == 0 is no error... (EDIT: alternative way is to crash on Error, which is to me the right thing to do)

Note that Result!T is supposed to have errorer() and succeeded() methods to avoid having to ever check for string equality.

Should Result!T always embed an error code? If the string already signals what it is?

@p0nce
Copy link
Contributor Author

p0nce commented May 13, 2019

Same with bool out params, if the function can fail for a variety of different reasons then it should return an int and it should be documented properly what the error codes mean.

Well in this case you really wanted a Result!T with a string in it then, instead of bool out

@0xEAB
Copy link

0xEAB commented May 13, 2019

> Should Result!T always embed an error code?

In theory error codes are nice because you can handle each error individually, and you don't have to be afraid of anything breaking because of changing of the error msg string (or potential i18n).

The (from my perception) common solution for exception handling in D is to not create specific exception subtypes, but just go for one (or none) type and just differentiate the specific exceptional cases by a meaningful message.

@igor84 igor84 merged commit c59013d into DlangGraphicsWG:master May 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants