www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Register based error-handling?

reply "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
It has been argued that exceptions are too slow in D to propagate 
regular errors and should just be used for the truly exceptional 
case. Then there should be a faster, but equally convenient 
alternative for regular errors.

In Go you "solve" the problem by returning tuples:
(returnvalue,errorcode) = function_call();

That is annoying. I'd rather see an implicit error code by having 
"return error(value)" be a builtin.


The basic idea is have a register based errno with language 
support:

1. On amd64 you designate one "caller save" SIMD register to 
error propagation.

2. Function signatures can specify if a return value:
- never provide errors
- provide errors that must be handled
- provide errors that can be ignored

3. The language checks if all mandatory errors are handled.

4. Functions called are only allowed to write non-zero values to 
the error register on return.

The language then provide constructs for dealing with errors:

// ignore error values, the compiler does not check
// errors until after func2() is executed (like opengl)

tryall {
     value = func1();
     value += func2();
}
catcherror{ … }

value = func(3);
if error { print_error(error); }
// error is cleared here by the compiler

value = func_optional_error();

// error is cleared here before calling a function
// that returns an error

value = func4();
Nov 06 2014
next sibling parent "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Thursday, 6 November 2014 at 08:30:22 UTC, Ola Fosheim Grøstad 
wrote:
 1. On amd64 you designate one "caller save" SIMD register to 
 error propagation.
I meant "callee save". It is saved by the called function if it does not return an error.
Nov 06 2014
prev sibling next sibling parent reply "Jonathan Marler" <johnnymarler gmail.com> writes:
On Thursday, 6 November 2014 at 08:30:22 UTC, Ola Fosheim Grøstad 
wrote:
 It has been argued that exceptions are too slow in D to 
 propagate regular errors and should just be used for the truly 
 exceptional case. Then there should be a faster, but equally 
 convenient alternative for regular errors.

 In Go you "solve" the problem by returning tuples:
 (returnvalue,errorcode) = function_call();

 That is annoying. I'd rather see an implicit error code by 
 having "return error(value)" be a builtin.


 The basic idea is have a register based errno with language 
 support:

 1. On amd64 you designate one "caller save" SIMD register to 
 error propagation.

 2. Function signatures can specify if a return value:
 - never provide errors
 - provide errors that must be handled
 - provide errors that can be ignored

 3. The language checks if all mandatory errors are handled.

 4. Functions called are only allowed to write non-zero values 
 to the error register on return.

 The language then provide constructs for dealing with errors:

 // ignore error values, the compiler does not check
 // errors until after func2() is executed (like opengl)

 tryall {
     value = func1();
     value += func2();
 }
 catcherror{ … }

 value = func(3);
 if error { print_error(error); }
 // error is cleared here by the compiler

 value = func_optional_error();

 // error is cleared here before calling a function
 // that returns an error

 value = func4();
I've been in your shoes before, wondering why there aren't better mechanisms for error handling. I've changed my view over time and let me explain why. First, D has the capability to generate code that uses error codes via registers or some global variable (note: I wouldn't specify that a compiler should use a certain register for error codes, the compiler is smart and will decide the best way to do that). So I believe what you're looking for is a standard mechanism for functions to return errors and for the language to support that standard with a nice syntax. I don't think this is a bad idea, but this isn't really necessary since you can already do these things, maybe in a more verbose syntax, but you can do them without modifying the current language. One thing I like about your suggestion is you can specify if a function's error code should be verified to be handled at compile time. It would be nice as a library writer if you could specify that the error MUST be handled by the caller. That way you have some protection that users will use your functions correctly. Now back to Exceptions. I wanted to address your concern (and everyone else's) about exceptions being "slow". There are a lot of different opinions on this but over time I've "evolved" my view on them to some ideas that have been working for me for a while. The first idea is when to throw an exception...easy A function should throw an exception if it CAN NOT perform the action indicated by it's name. If a function named "write" fails to write, it should throw an exception. So what about functions that want to return error codes instead. What I do is prefix the function with "try". You don't have to do that, but it helps me personally keep track of what functions require explicit error checking. A function named "tryWrite" lets the user know the function may not write and they need to check somehow whether or not it failed. Typically in the "normal operation" of a program, especially one you want designed to run fast, you will NEVER THROW AN EXCEPTION. An exception being thrown isn't typically used as normal "control flow". An exception is typically used to indicate an "environment error". That's why it's OK that they are so slow and expensive. You don't want to be throwing and catching exceptions all over the place, but when you do have an exception you want to capture as much info as possible even it if incurs a huge runtime cost. A function that returns error in "normal program operation" should use the "try" mechanism and a function that always succeeds unless some type of environmental error occurs can just throw an exception.
Nov 06 2014
next sibling parent reply "Nemanja Boric" <4burgos gmail.com> writes:
 If a function named "write" fails to write, it should throw an 
 exception.  So what about functions that want to return error 
 codes instead.  What I do is prefix the function with "try".  
 You don't have to do that, but it helps me personally keep 
 track of what functions require explicit error checking.  A 
 function named "tryWrite" lets the user know the function may 
 not write and they need to check somehow whether or not it 
 failed.
Maybe you could consider something like returning `Result<Ok, Err>` (like in Rust) which will make you not forget to check for return code. http://doc.rust-lang.org/std/result/
Nov 06 2014
parent "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Thursday, 6 November 2014 at 09:36:38 UTC, Nemanja Boric wrote:
 Maybe you could consider something like returning `Result<Ok, 
 Err>` (like in Rust) which will make you not forget to check 
 for return code.
Yes, but that is too syntax heavy. I want a language builtin that is non-intrusive. Not having the logic of the program polluted by error handing is a significant advantage of regular exceptions (and what they were intented for).
Nov 06 2014
prev sibling next sibling parent "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Thursday, 6 November 2014 at 09:17:55 UTC, Jonathan Marler 
wrote:
 First, D has the capability to generate code that uses error 
 codes via registers or some global variable (note: I wouldn't 
 specify that a compiler should use a certain register for error 
 codes, the compiler is smart and will decide the best way to do 
 that).
Well, if it is that smart then it would be an optimization, but that makes separate compilation harder.
 So I believe what you're looking for is a standard mechanism 
 for functions to return errors and for the language to support 
 that standard with a nice syntax.  I don't think this is a bad 
 idea, but this isn't really necessary since you can already do 
 these things, maybe in a more verbose syntax, but you can do 
 them without modifying the current language.
Well, but it requires wrapping. Google are not using exceptions in C++, according to a presentation on cppcon, they are using a "status-object" that requires errors to be handled or explicitly ignored. BUT: if you have noisy syntax for it, people will avoid using it.
 One thing I like about your suggestion is you can specify if a 
 function's error code should be verified to be handled at 
 compile time.  It would be nice as a library writer if you 
 could specify that the error MUST be handled by the caller.  
 That way you have some protection that users will use your 
 functions correctly.
Yes, I like the concept. You could have four different policies for error handling: 1. no error 2. must be handled at once 3. must be handled sometime (collective handling) 4. optional handling
   A function should throw an exception if it CAN NOT perform 
 the action indicated by it's name.
Walter has argued against web servers using exceptions to return errors in REST applications because of the performance penalty… So I think you need a third fast-unwind-exception feature… :-) Which I have suggested before: you have a manger object and a landing pad. When you throw "fast unwind" you jump to the landing pad, let the manager object free all allocated resources and reset the stack pointer. Voila, fast unwinding! (Basically a generalized version of region allocation with language support.)
 codes instead.  What I do is prefix the function with "try".  
 You don't have to do that, but it helps me personally keep 
 track of what functions require explicit error checking.  A 
 function named "tryWrite" lets the user know the function may 
 not write and they need to check somehow whether or not it 
 failed.
Yeah, such naming conventions can be useful. At some point I used to append "_ok" to functions that returned boolean error values: "if (!write_ok(…))…"
 Typically in the "normal operation" of a program, especially 
 one you want designed to run fast, you will NEVER THROW AN 
 EXCEPTION.
And you want to not compile in exception support and omit explicit stack frames. :-)
  An exception being thrown isn't typically used as normal 
 "control flow".  An exception is typically used to indicate an 
 "environment error".
I assume you used quotes because you agree that "environment error" is underspecified and perhaps even indeterminate at the failure point. Beside, I don't really agree. I think it is ok to throw whenever a web server cannot serve a response to a request. No need to special-case the difference between 500 and 404, the external entity can trigger both (and the difference can be subtle). You basically want uniformity and no special casing based on subtle differences.
 and expensive.  You don't want to be throwing and catching 
 exceptions all over the place, but when you do have an 
 exception you want to capture as much info as possible even it 
 if incurs a huge runtime cost.
But I may want the same info on input related failed requests. If someone is getting a 404 it might be a problem in the server too.
 A function that returns error in "normal program operation" 
 should use the "try" mechanism and a function that always 
 succeeds unless some type of environmental error occurs can 
 just throw an exception.
The language should support clean programming. If you need to unwind all the way to the root handler then why go through the hassle of adding a spaghetti of conditionals? Without fast and simple unwinding for aborting a request D will not be suitable for webserver programming IMO.
Nov 06 2014
prev sibling next sibling parent reply Jeremy Powers via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Thu, Nov 6, 2014 at 1:17 AM, Jonathan Marler via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 One thing I like about your suggestion is you can specify if a function's
 error code should be verified to be handled at compile time.  It would be
 nice as a library writer if you could specify that the error MUST be
 handled by the caller.  That way you have some protection that users will
 use your functions correctly.
This is why checked exceptions were invented. Personally I prefer exceptions over error codes, as any sufficiently advanced use of error codes will start to resemble exceptions anyway... But whichever, there is still the need to enforce that an error case/exception is actually handled (or explicitly not). I'd really like a language-enforced way to do this. Don't have any better suggestion than checked exceptions though...
Nov 06 2014
next sibling parent "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Thursday, 6 November 2014 at 19:18:14 UTC, Jeremy Powers via 
Digitalmars-d wrote:
 This is why checked exceptions were invented.
Yeah, I agree. Main reasons for classical exception handling: 1. Get error handling directly to a level where it can be handled to prevent propagating clutter in the logic. 2. Make sure that the program does not continue by mistake. 3. Recovery at the failure point, by having the main call site fix the error then go back to where the failure occurred, but this was too complicated in practice. It is possible for floating point exceptions though (replace the failed computation with a value).
 Personally I prefer exceptions over error codes, as any 
 sufficiently
 advanced use of error codes will start to resemble exceptions 
 anyway...
Yes, it is primarily a performance vs convenience issue. You can stuff a lot of information into 256 bits, so it is not as restrictive as errno.
 I'd really like a language-enforced way to do this.  Don't have 
 any better suggestion than checked exceptions though...
I have no direct experience with checked exceptions, but it sounds interesting. Still, the main issue with exceptions is performance.
Nov 06 2014
prev sibling parent "eles" <eles215 gzk.dot> writes:
On Thursday, 6 November 2014 at 19:18:14 UTC, Jeremy Powers via 
Digitalmars-d wrote:
 On Thu, Nov 6, 2014 at 1:17 AM, Jonathan Marler via
 suggestion than checked exceptions though...
I think the worst thing about exceptions is the syntax deformation introduced by all the try/catch blocks. Something like a "flat try/catch" could help a bit: foo(); //implicit try if declared with checked exceptions check(exception1, exception2) { writeln("1 or 2")}; check(exception3) writeln("this is 3"); //compilation error since exception4 was not checked
Nov 06 2014
prev sibling parent "eles" <eles215 gzk.dot> writes:
On Thursday, 6 November 2014 at 09:17:55 UTC, Jonathan Marler 
wrote:
 On Thursday, 6 November 2014 at 08:30:22 UTC, Ola Fosheim 
 Grøstad wrote:
 I've been in your shoes before, wondering why there aren't 
 better mechanisms for error handling.
Me too, I wondered a bit about this. For C-like languages, this is the most structured mechanism that I did find: https://developer.gnome.org/glib/stable/glib-Error-Reporting.html But, in the end, we went with standard return codes.
Nov 06 2014
prev sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
I don't think we should have a language construct for that. It is
possible to pack the returned value in a struct that enforce
error checking. Exception is fine for procedure if they can't do
their job. tryDo as explained before in this thread is also a
common convention to indicate that you'll try do to something and
fail silently.
Nov 06 2014
parent reply "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Thursday, 6 November 2014 at 20:06:55 UTC, deadalnix wrote:
 I don't think we should have a language construct for that. It 
 is
 possible to pack the returned value in a struct that enforce
 error checking.
How? Please note that inconvenient is not an option since people then will end up with the classical "use an illegal value to convey error": As a consequence error is sometimes -1 and sometimes 0 and sometimes NaN, so you have to rely on docs.
Exception is fine for procedure if they can't do their job.
The problem is that they don't perform well. They don't really belong in system level programming and there are no inconvenient non-noisy alternatives.
Nov 06 2014
next sibling parent reply "eles" <eles215 gzk.dot> writes:
On Thursday, 6 November 2014 at 20:58:02 UTC, Ola Fosheim Grøstad 
wrote:
 On Thursday, 6 November 2014 at 20:06:55 UTC, deadalnix wrote:
 I don't think we should have a language construct for that. It 
 is
 possible to pack the returned value in a struct that enforce
 error checking.
How?
The most similar thing that I can think about is Boost Optional.
Nov 06 2014
parent reply "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Thursday, 6 November 2014 at 21:13:53 UTC, eles wrote:
 The most similar thing that I can think about is Boost Optional.
Yes, that is kind of like Rust's std::result or Haskell's Maybe, but it does not support delaying the handling like you do in OpenGL. What is desirable is that you are allowed to continue as long as you eventually handle the error. That way you can minimize the error checking… And the mechanisms should also not introduce any cache misses…
Nov 06 2014
parent reply "ZombineDev" <valid_email he.re> writes:
Something a little better than Boost's Optional:
Expected<T>
http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
Nov 06 2014
parent "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Thursday, 6 November 2014 at 21:55:42 UTC, ZombineDev wrote:
 Something a little better than Boost's Optional:
 Expected<T>
 http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
If you have exceptions maybe, but not good enough. And none of the Maybe-like solutions work when you don't have a return value, so they don't enforce error-handling.
Nov 06 2014
prev sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Thursday, 6 November 2014 at 20:58:02 UTC, Ola Fosheim Grøstad
wrote:
 On Thursday, 6 November 2014 at 20:06:55 UTC, deadalnix wrote:
 I don't think we should have a language construct for that. It 
 is
 possible to pack the returned value in a struct that enforce
 error checking.
How?
struct ErrorCodeResult(T) { // Option 1. inout(T) get() inout { if (error_code) throw new Exception("Check error code you morron !"); return result; } // Optin 2. auto get(alias success, alias failure)() { if (error_code) return failure(error_code); return success(result); } immutable uint error_code; private: T result; } The option 3 is to use opDispatch to do the check on everything and return a ErrorCodeResult of whatever should have been returned. Options are numerous for various safety levels, and all as fast as the manual check.
Nov 06 2014