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