On Friday, 19 May 2017 at 18:16:33 UTC, Jack Stouffer wrote:
On Friday, 19 May 2017 at 15:45:28 UTC, Mike Parker wrote:
...

Secondly, I'm not a fan of special casing syntax, especially when I don't think the given benefits outweigh the above listed costs...

You're raising an extremely important point. Special-casing exceptions in such a way, while may help *some* code, is still a crutch, and a very ugly one at that. The bigger issue of @nogc is the implicit allocations. It would be much better to solve the fundamental problem, that is, give user code optional, but complete control over memory allocation.

All three main areas need to be addressed in concert: classes, arrays and delegates. Working on just one area, and even just a subset of it (exceptions) in the long run will further the problem, not help solve it.

As an example, operator new could accept additional syntax (outer [] mean "optional"):

new [(TAllocator)] Type [[count]] [(init)]

Where TAllocator is a class providing at least minimal allocator interface (allocate/deallocate). @nogc can be inferred from the allocator. This lets user code decide on their class and array allocations. OTOH, this will incur storage penalty, since all dynamically-allocated objects (including arrays) will have to carry the allocator reference with them. It doesn't, however, solve the @nogc inference in general, since allocator is not part of the type. Attempting to concatenate two arrays at a distance from their allocation site would not be considered @nogc. That being said, arrays with complete inference support could be made library types, so perhaps that is not *that* big of an issue.

Delegates, as they are now, require a lot more attention. Firstly, there is the dubious design decision that any outer scope access is always by reference, which imposes the GC allocation of closures in the first place. @nogc cannot be solved for delegates without addressing control over captures. Then we have the allocation itself, in cases when it is desired. And that will completely destroy existing delegate syntax.

There is, however, another side to this problem, and that is interaction of exceptions with the runtime. Allowing users to employ any desired allocation scheme precludes reliable exception handling, since the runtime effectively loses control over exception lifetime. Catching garbage in a catch(...) block would never be a pleasant surprise. That being said, it's not that it is in control right now either:

void innocent() {
    throw new Exception("I am innocent");
}

void malicious() {
    try {
        innocent();
    } catch (Exception e) {
        // innocent, eh? well I am not...
        e.destroy;
        throw e;
    }
}

void oblivious() {
    try {
        malicious();
    } catch (Exception e) {
        writeln("Caught: ", e.msg);
    }
}

void main() {
    oblivious();
}

Hello, segmentation fault. If users want to be difficult, they'd find a way :)

So perhaps, instead of tackling exceptions on their own, we really should focus on allocation in general. Everything else should (hopefully) come automagically.

Reply via email to