Rant after trying Rust a bit

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Thu Jul 23 15:06:49 PDT 2015


On Thu, Jul 23, 2015 at 01:40:17PM -0700, Walter Bright via Digitalmars-d wrote:
> On 7/23/2015 12:50 PM, H. S. Teoh via Digitalmars-d wrote:
> >That assumes the template author is diligent (foolhardy?) enough to
> >write unittests that cover all possible instantiations...
> 
> No, only each branch of the template code must be instantiated, not
> every possible instantiation. And we have a tool to help with that:
> -cov
> 
> Does anyone believe it is a good practice to ship template code that
> has never been instantiated?

OK, I jumped into the middle of this discussion so probably I'm speaking
totally out of context... but anyway, with regards to template code, I
agree that it ought to be thoroughly tested by at least instantiating
the most typical use cases (as well as some not-so-typical use cases).

An uninstantiated template path is worse than a branch that's never
taken, because the compiler can't help you find obvious problems before
you ship it to the customer.

A lot of Phobos bugs lurk in rarely-used template branches that are not
covered by the unittests.

Instantiating all branches is only part of the solution, though. A lot
of Phobos bugs also arise from undetected dependencies of the template
code on the specifics of the concrete types used to test it in the
unittests.  The template passes the unittest but when you instantiate it
with a type not used in the unittests, it breaks. For instance, a lot of
range-based templates are tested with arrays in the unittests. Some of
these templates wrongly depend on array behaviour (as opposed to being
confined only to range API operations) while their signature constraints
indicate only the generic range API. As a result, when non-array ranges
are used, it breaks. Sometimes bugs like this can lurk undetected for a
long time before somebody one day happens to instantiate it with a range
type that violates the hidden assumption in the template code.

If we had a Concepts-like construct in D, where template code is
statically constrained to only use, e.g., range API when manipulating an
incoming type, a lot of these bugs would've been caught.

In fact, I'd argue that this should be done for *all* templates -- for
example, a function like this ought to be statically rejected:

	auto myFunc(T)(T t) { return t + 1; }

because it assumes the validity of the + operation on T, but T is not
constrained in any way, so it can be *any* type, most of which,
arguably, do not support the + operation.

Instead, templates ought to be required to explicitly declare up-front
all operations that it will perform on incoming types, so that (1) its
assumptions are obvious, and (2) the compiler will reject attempts to
instantiate it with an incompatible type.

	auto myFunc(T)(T t)
		if (is(typeof(T.init + 1)))
	{
		return t + 1;
	}

The current syntax is ugly, of course, but that's easily remedied. The
more fundamental problem is that the compiler does not restrict
operations on T in any way, even when the sig constraint specifies how T
ought to be used. Someone could easily introduce a bug:

	auto myFunc(T)(T t)
		if (is(typeof(T.init + 1)))
	{
		/* Oops, we checked that +1 is a valid operation on T,
		 * but here we're doing -1 instead, which may or may not
		 * be valid: */
		return t - 1;
	}

The compiler still accepts this code as long as the unittests use types
that support both + and -. So this dependency on the incidental
characteristics of T remains as a latent bug.

If the compiler outright rejected any operation on T that hasn't been
explicitly tested for, *then* we will have eliminated a whole class of
template bugs. Wrong code like the last example above would be caught as
soon as the compiler compiles the body of myFunc.


T

-- 
Elegant or ugly code as well as fine or rude sentences have something in common: they don't depend on the language. -- Luca De Vitis


More information about the Digitalmars-d mailing list