nothrow function callbacks in extern(C) code - solution
H. S. Teoh via Digitalmars-d
digitalmars-d at puremagic.com
Thu Jun 19 13:23:21 PDT 2014
On Thu, Jun 19, 2014 at 12:59:00PM -0700, Walter Bright via Digitalmars-d wrote:
> With nothrow and @nogc annotations, we've been motivated to add these
> annotations to C system API functions, because obviously such
> functions aren't going to throw D exceptions or call the D garbage
> collector.
>
> But this exposed a problem - functions like C's qsort() take a pointer
> to a callback function. The callback function, being supplied by the D
> programmer, may throw and may call the garbage collector. By requiring
> the callback function to be also nothrow @nogc, this is an
> unreasonable requirement besides breaking most existing D code that
> uses qsort().
>
> This problem applies as well to the Windows APIs and the Posix APIs
> with callbacks.
>
> The solution is to use overloading so that if your callback is
> nothrow, it will call the nothrow version of qsort, if it is
> throwable, it calls the throwable version of qsort.
>
> Never mind that those two versions of qsort are actually the same
> function (!), even though D's type system regards them as different.
> Although this looks like an usafe hack, it actually is quite safe,
> presuming that the rest of the qsort code itself does not throw. This
> technique relies on the fact that extern(C) functions do not get their
> types mangled into the names.
>
> Some example code:
>
> extern (C) { alias int function() fp_t; }
> extern (C) nothrow { alias int function() fpnothrow_t; }
>
> extern (C) int foo(int a, fp_t fp);
> extern (C) nothrow int foo(int a, fpnothrow_t fp);
>
> extern (C) int bar();
> extern (C) nothrow int barnothrow();
>
> void test() {
> foo(1, &bar); // calls the 'throwing' foo()
> foo(1, &barnothrow); // calls the 'nothrow' foo()
> }
This is a clever hack to work around the type system, but it introduces
boilerplate, and doesn't ultimately fix the underlying problem. I agree
that it's "good enough" for now -- to get those system APIs working
without massive breakage of existing code -- but I think for the long
run, we should face the root issue: functions that call callbacks have
attributes that *depend* on an input argument.
So we really should have the equivalent of "inout" for other attributes
than const. For example, a function can be "dependently nothrow",
meaning that the body of the function is nothrow, except for that call
to user-supplied delegate:
// This is hypothetical syntax.
int dgCaller(scope int delegate(int) dg inout(nothrow))
inout(nothrow)
{
//throw new Exception(...); // illegal: body of function must not throw
int result = dg(1); // OK: we inherit nothrow-ness from dg().
return result;
}
void f1() nothrow {
// OK: the delegate is nothrow, so dgCaller is nothrow,
// so it's permitted to call it from a nothrow function.
auto x = dgCaller((x) => x+1);
}
void f2() {
// OK: the delegate is throwing, which makes dgCaller
// nothrowing w.r.t. this call. So this function becomes
// throwing.
auto x = dgCaller((int) { throw new Exception(...); });
}
void f3() nothrow {
// ILLEGAL: the delegate throws, so dgCaller may throw,
// so we can't call it from a nothrow function.
auto x = dgCaller((int) { throw new Exception(...); });
}
This capability will solve a lot of attribute-related issues especially
in generic code. For example, a container that implements opApply()
currently cannot be marked pure, because it would impose purity on the
delegate passed to it, which greatly limits its applicability. But
accepting a non-pure delegate makes it uncallable from pure code, even
if opApply() itself doesn't do anything impure. So we end up needing to
write two identical versions of opApply: one pure, and one non-pure. And
this has to be multiplied for each attribute we wish to support (pure,
nothrow, @safe, @nogc -- that's 2^4 = 16 copies of the same function),
which is clearly untenable.
With inout(nothrow), inout(pure), etc., we can collapse all of these
variants into a single function, and at the same time have the compiler
statically verify that the result does not violate any attribute.
T
--
"I'm not childish; I'm just in touch with the child within!" - RL
More information about the Digitalmars-d
mailing list