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?

Reply via email to