Concept proposal: Safely catching error

Olivier FAURE via Digitalmars-d digitalmars-d at puremagic.com
Mon Jun 5 02:50:15 PDT 2017


I recently skimmed the "Bad array indexing is considered deadly" 
thread, which discusses the "array OOB throws Error, which throws 
the whole program away" problem.

The gist of the debate is:

- Array OOB is a programming problem; it means an invariant is 
broken, which means the code surrounding it probably makes 
invalid assumptions and shouldn't be trusted.

- Also, it can be caused by memory corruption.

- But then again, anything can be cause by memory corruption, so 
it's kind of an odd thing to worry about. We should worry about 
not causing it, not making memory corrupted programs safe, since 
it's extremely rare and there's not much we can do about it 
anyway.

- But memory corruption is super bad, if a proved error *might* 
be caused by memory corruption then we must absolutely throw the 
potentially corrupted data away without using it.

- Besides, even without memory corruption, the same argument 
applies to broken invariants; if we have data that breaks 
invariants, we need to throw it away, and use it as little as 
possible.

- But sometimes we have very big applications with lots of data 
and lots of code. If my server deals with dozens of clients or 
more, I don't want to brutally disconnect them all because I need 
to throw away one user's data.

- This could be achieved with processes. Then again, using 
processes often isn't practical for performance or architecture 
reasons.

My proposal for solving these problems would be to explicitly 
allow to catch Errors in @safe code IF the try block from which 
the Error is caught is perfectly pure.

In other words, @safe functions would be allowed to catch Error 
after try blocks if the block only mutates data declared inside 
of it; the code would look like:

     import vibe.d;

     // ...

     string handleRequestOrError(in HTTPServerRequest req) @safe {
         ServerData myData = createData();

         try {
             // both doSomethingWithData and mutateMyData are @pure

             doSomethingWithData(req, myData);
             mutateMyData(myData);

             return myData.toString;
         }
         catch (Error) {
             throw new SomeException("Oh no, a system error 
occured");
         }
     }

     void handleRequest(HTTPServerRequest req,
                        HTTPServerResponse res) @safe
     {
         try {
             res.writeBody(handleRequestOrError(req), 
"text/plain");
         }
         catch (SomeException) {
             // Handle exception
         }
     }

The point is, this is safe even when doSomethingWithData breaks 
an invariant or mutateMyData corrupts myData, because the 
compiler guarantees that the only data affected WILL be thrown 
away or otherwise unaccessible by the time catch(Error) is 
reached.

This would allow to design applications that can fail gracefully 
when dealing with multiple independent clients or tasks, even 
when one of the tasks has to thrown away because of a programmer 
error.

What do you think? Does the idea have merit? Should I make it 
into a DIP?


More information about the Digitalmars-d mailing list