Rant after trying Rust a bit

Tofu Ninja via Digitalmars-d digitalmars-d at puremagic.com
Sat Jul 25 18:56:27 PDT 2015


On Sunday, 26 July 2015 at 00:31:48 UTC, Jonathan M Davis wrote:
> 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.


I think one of the key points, is that it would be opt-in. 
Current constraints would continue to work as they do now. Only 
templates that chose to use the new tighter constraints would 
have this "problem". They would be separate from current 
constraints (with a separate syntax). Code using current 
constraints would not stop working if a template that used the 
new constraints.

It makes sense when you consider how current constraints work 
now, they basically say "If you can't do X then fail" but they 
say nothing about if it can do extra things in addition to X.

So for example in the following code I will use the 
specialization syntax to signify one of the NEW constraints and 
the regular "if" syntax for the current constraints.


void foo(T)(T x) if(cond1!T) // OLD constraint
{
      bar(x);
}

void bar(T : cond2)(T x) // NEW constraint
{
      ...
}

Would still work, and would only fail when foo gets instantiated 
with a type that does not pass both cond1 and cond2. Which makes 
perfect sense in the context of how the current constraints work. 
They only test if you can do something, they don't care if you 
can do extra things. There would be no need to update the 
constraints on foo.

> 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.

With what I said about opt-in I think every thing above is null.

The key advantage to the new constraints would be that it 
constrains the type to only do what the constraints say. As 
opposed to being able to do anything in addition to what the 
constraints say.

> 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.

Again, with it being opt-in, this problem is not as bad as what 
you say. Only templates that choose to use the new constraints 
would need to do the sort of maintenance that you say.

Also this problem is the same as normal type systems experience! 
If some low level function needs something new out of type T then 
the same problem will arise. Its not a problem there so it should 
not be a problem here.

> 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.

Again opt-in.

> 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.

Better errors are of course better.

> 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

Key point is opt-in.



More information about the Digitalmars-d mailing list