Rant after trying Rust a bit

Jonathan M Davis via Digitalmars-d digitalmars-d at puremagic.com
Sat Jul 25 17:31:46 PDT 2015


On Friday, 24 July 2015 at 01:09:19 UTC, H. S. Teoh wrote:
> I have trouble thinking of a template function that's actually 
> *correct* when its sig constraints doesn't specify what 
> operations are valid on the incoming type. Can you give an 
> example?
>
> If such code is wrong, I'd say the language *should* reject it.

I see two issues here, both of which relate to maintenance. The 
first one is that if the language were actually able to check 
that you missed a requirement in your template constraint (like 
you're suggesting) and then give you an error, that makes it way 
easier to break valid code. Take code like this, for example

auto foo(T)(T t)
     if(cond1!T && cond2!T)
{
     ...
     auto b = bar(t);
     ...
}

auto bar(T)(T t)
     if(cond2!T)
{
     ...
}

foo calls bar, and it does have all of bar's constraints in its 
own constraints so that you don't end up with a compilation error 
when you pass foo something that doesn't work with bar. Now, 
imagine if bar gets updated, and now its

auto bar(T)(T t)
     if(cond2!T && cond3!T)
{
     ...
}

but foo's constraint isn't updated (e.g. because foo is in a 
different library or program that depends no bar, so the person 
who updates bar isn't necessarily the same person who maintains 
foo). If the compiler then caught the fact that foo didn't check 
all of bar's constraints and gave an error, that would alert 
anyone using foo that foo needed to be updated, but it would also 
mean that foo would no longer compile, when it's quite possible 
that the argument passed to foo does indeed pass bar's template 
constraint and will work just fine with foo. So, working code no 
longer compiles when there's no technical reason why it couldn't 
continue to work. Presumably, once the maintainer of foo finds 
out about this, they'll update foo, and the problem will be 
fixed, but it still means that every time that the template 
constraint for bar is adjusted at all, every template that uses 
it risks breaking if the compiler insists that those templates 
check all of bar's constraints.

So, yes. it does help ensure that users of foo don't end up with 
error messages inside of foo thanks to foo's template constraint 
not listing everything that it actually requires, but it also 
breaks a lot of code when template constraints change when the 
code itself will often work just fine as-is (particularly since 
the change to bar that required a change to its template 
constraint would usually be a change to its implementation and 
not what it did, since if you changed what it did, everyone that 
used it would be broken anyway). Code that's actually broken by 
the change to bar will fail bar's new template constraint even if 
the compiler doesn't complain about foo (or any other function) 
not having updated its constraint, and it'll still get caught. 
The error might not be as nice, since it'll often be in someone 
else's templated code, but it'll still be an error, and it'll 
still tell you what's failing. So, with the current state of 
affairs, only code that's actually broken by a change to bar's 
template constraint would be broken and not everyone, whereas 
what you're suggesting would break all code that used bar that 
didn't happen to also check the same thing that bar was now 
checking for.

The second issue that I see with your suggestion is basically 
what Walter is saying the problem is. Even if we assume that we 
_do_ want to put all of the requirements for foo - direct or 
indirect - in its template constraint, this causes a maintenance 
problem. For instance, if foo were updated to call another 
function

auto foo(T)(T t)
     if(cond1!T && cond2!T && cond3!T && cond4!T)
{
     ...
     auto b = bar(t);
     ...
     auto c = baz(t);
     ...
}

auto bar(T)(T t)
     if(cond2!T && cond3!T)
{
     ...
}

auto baz(T)(T t)
     if(cond1!T && cond4!T)
{
     ...
}

you now have to update foo. Okay. That's not a huge deal, but now 
you have two functions that you're using within foo whose 
template constraints need to be duplicated in foo's template 
constraint. And ever function that _they_ call ends up affecting 
_their_ template constraints and then foo in turn.

auto foo(T)(T t)
     if(cond1!T && cond2!T && cond3!T && cond4!T && cond5!T && 
cond6!T && cond7!T)
{
     ...
     auto b = bar(t);
     ...
     auto c = baz(t);
     ...
}

auto bar(T)(T t)
     if(cond2!T && cond3!T)
{
     ...
     auto l = lark(t);
     ...
}

auto baz(T)(T t)
     if(cond1!T && cond4!T)
{
     ...
     auto s = stork(t);
     ...
}


auto lark(T)(T t)
     if(cond5!T && cond6!T)
{
     ...
}

auto stork(T)(T)
     if(cond2!T && cond3!T && cond7!T)
{
     auto w = wolf(t);
}

auto wolf(T)(T)
     if(cond7!T)
{
     ...
}

So, foo's template constraint potentially keeps getting nastier 
and nastier thanks to indirect calls that it's making. Now, often 
there's going to be a large overlap between these constraints 
(e.g. because they're all range-based functions using 
isInputRange, isForwardRange, hasLength, etc.), so maybe foo's 
constraint doesn't get that nasty. But where you still have a 
maintenance problem even if that's the case is if a function 
that's being called indirectly adds something to its template 
constraint, then everything up the chain has to add it if you 
want to make sure that foo gets no compilation internally due to 
it failing to pass a template constraint of something that it's 
calling. So, if wolf ends up with a slightly more restrictive 
constraint in the next release, then every templated function on 
the planet which used it - directly or indirectly - would need to 
be updated. And much of that code could be maintained by someone 
other than the person who made the change to wolf, and much of it 
could be code that they've don't even know exists. So, if we're 
really trying to put everything that a function requires - 
directly or indirectly - in its template constraint, we 
potentially have a huge maintenance problem here once you start 
having templated functions call other templated functions - 
especially if any of these functions are part of a library that's 
distributed to others. But even if it's just your own code base, 
a slight adjustment to a template constraint could force you to 
change a _lot_ of the other template constraints in your code.

So, while I definitely agree that it's nicer from the user's 
standpoint when the template constraint checks everything that 
the function requires - directly or indirectly - I think that we 
have a major maintenance issue in the making here if that's what 
we insist on. Putting all of the sub-constraints in the top-level 
constraint - especially with multiple levels of templated 
functions - simply doesn't scale well, even if it's desirable. 
Maybe some kind of constraint inference would solve the problem. 
I don't know. But I think that it is a problem, and it's one that 
we haven't really recognized yet.

At this point, even if we're going to try and have top-level 
template constraints explicitly contain all of the constraints of 
the templates that they use - directly or indirectly - I think 
that we really need to make sure that the error messages from 
within templated code are as good as we can make them, because 
there's no way that all template constraints are going to contain 
all of their sub-constraints as code is changed over time, not 
unless the constraints are fairly simple and looking for the same 
stuff.

Fortunately, the error messages are a lot better than they used 
to be, but if we can improve them sufficiently, then it becomes 
less critical to make sure that all sub-constraints be in the 
top-level constraint, and it makes it a lot more palatable when 
sub-constraints are missed.

But as I said in the first part, I really don't think that 
detecting missing constraints and giving errors is a good 
solution. It'll just break more code that way. Rather, what we 
need is to either find a way to infer the sub-constraints into 
the top-level constraint and/or to provide really good error 
messages when errors show up inside templated code, because a 
constraint didn't check enough.

- Jonathan M Davis


More information about the Digitalmars-d mailing list