DIP33: A standard exception hierarchy

H. S. Teoh hsteoh at quickfur.ath.cx
Wed Apr 3 16:11:46 PDT 2013


On Wed, Apr 03, 2013 at 03:28:10PM -0700, Ali Çehreli wrote:
> On 04/03/2013 10:01 AM, H. S. Teoh wrote:
> > On Wed, Apr 03, 2013 at 09:19:24AM -0700, Ali Çehreli wrote:
> 
> >> Because the above is not the case today, if I write a function, I
> >> cannot put the function pre-conditions in 'in' blocks because I
> >> don't know whether my function is being called as an implementation
> >> of my module or as an API function. The API function foo() may also
> >> be used as part of the implementation of the same module. (Maybe
> >> the same pre-condition checks should be repeated in the 'in' block
> >> and in the body; as asserts and corresponding enforces.)
> >
> > This is very bad. It makes greatly diminishes the value of DbC in D.
> > What are the obstacles preventing us from fixing DMD so that
> > contracts are compiled with user code instead of library code?
> 
> The following thread is relevant but I don't remember whether it
> touches issues with dmd:
> 
>   http://forum.dlang.org/thread/kf19eh$14tv$1@digitalmars.com
[...]

Alright. Apparently Jonathan touched on some of the issues in the above
thread (quoted below):


[...]
> Unfortunately, while that's how it really _should_ work, AFAIK,
> there's no way with D's linking model to make things work that way.
> You can link against functions without any access to their bodies.
>
> Function pointers make it trivial to use a function without the
> compiler knowing what function your using (meaning that it couldn't
> insert the contracts at the call point).  Etc.  Etc. The contracts
> would have to be passed around with the functions in a manner which
> made it so that the caller could always insert them if it's being
> compiled with assertions enabled, and that just won't work.
[...]

This is not impossible to overcome.

One approach is to define contracts as separate (sub)functions that wrap
around the real function according to some well-known scheme; say the
contract is mangled as mangle(funcname)~"__contract" or something like
that. The contract wrapper has exactly the same arguments/return value
as the real function, and simply forwards them to the real function, and
passes the real function's return value back.  Then when compiling in
non-release mode, DMD will link all calls/references to the function to
the contract wrapper instead, and when compiling for release, these
calls/references go directly to the "real" function.

In essence, this code:

	RetType myFunc(Args...)(Args args)
	in { assert(inContract(args)); }
	out(RetType ret) { assert(outContract(ret)); }
	body {
		return dotDotDotMagic(args);
	}

	void main() {
		auto x = myFunc(1,2,3);
		auto fp = &myFunc;
	}

gets lowered to:

	RetType myFunc__contract(Args...)(Args args)
	{
		assert(inContract(args));
		auto retVal = myFunc(args);
		assert(outContract(retVal));
		return retVal;
	}

	RetType myFunc(Args...)(Args args)
	{
		return dotDotDotMagic(args);
	}

	void main() {
		version(release)
		{
			auto x = myFunc(1,2,3);
			auto fp = &myFunc;
		}
		else
		{
			auto x = myFunc__contract(1,2,3);
			auto fp = &myFunc__contract;
		}
	}

The *__in_contract wrappers are always shipped with the library, so
library users can always choose to link to the contracted version or
not. (Even if the library is compiled in release mode, the contract
wrappers are still there, they are just bypassed by internal library
calls. They can still be enforced when compiling user code in
non-release mode, since the compiler will then route all calls through
the wrappers.)


T

-- 
Real Programmers use "cat > a.out".


More information about the Digitalmars-d mailing list