Rant after trying Rust a bit

Jonathan M Davis via Digitalmars-d digitalmars-d at puremagic.com
Sun Jul 26 18:59:47 PDT 2015


On Sunday, 26 July 2015 at 22:59:09 UTC, deadalnix wrote:
> On Sunday, 26 July 2015 at 03:42:22 UTC, Walter Bright wrote:
>> On 7/25/2015 3:28 PM, deadalnix wrote:
>>> Also, argument from ignorance is hard to maintain when the 
>>> thread is an actual
>>> feedback from experience.
>>
>> You say that interfaces do the same thing. So please show how 
>> it's done with the example I gave:
>>
>>     int foo(T: hasPrefix)(T t) {
>>        t.prefix();    // ok
>>        bar(t);        // error, hasColor was not specified for 
>> T
>>     }
>>
>>     void bar(T: hasColor)(T t) {
>>        t.color();
>>     }
>
> I'm not sure what is the problem here.

The problem here is that if you're dealing with traits or 
concepts or whatever that are essentially interfaces, and foo 
uses the Prefix interface/trait/concept and then wants to use bar 
which has the Color interface/trait/concept, it has to then 
require that what it's given implements both the Prefix and Color 
interfaces/traits/concepts.

In the case of actual interfaces, this really doesn't work well, 
because you're forced to basically have an interface that's 
derived from both - e.g. PrefixAndColor. Otherwise, you'd be 
forced to do nonsense like have foo take a Prefix and then cast 
it to Color to pass to bar and throw an exception if the type it 
was given didn't actually implement both interfaces. And it's 
downright ugly. In reality, the code would likely simply end up 
not being that generic and would require some specific type that 
you were using in your code which implemented both Prefix and 
Color, and foo just wouldn't work with anything else, making it 
far less reusable. So, with actual interfaces, it becomes very 
difficult to write generic code.

With traits or concepts, presumably, you could say that foo 
required a type that implemented both Prefix and Color, which 
fixes the problem of how you're able to accept something that 
takes both generically without coming up with something like 
PrefixAndColor (though if you can only list one trait/concept as 
required, you're just as screwed as you are with interfaces). But 
even if you can list multiple traits/concepts as required by a 
function, you quickly end up with a proliferation of 
traits/concepts that need to be covered by a higher level 
function like foo, because not only would foo need to list all of 
the traits/concepts that the functions it uses directly require, 
but it has to list all of the traits/concepts that it even 
indirectly requires (potentially from far down the call stack). 
So, any change to a function that even gets called indirectly 
could break foo, because it didn't have the right traits/concepts 
listed in its requirements. And all of the functions in the call 
chain would have to have their list of required traits/concepts 
updated any time there was any tweak to any of the underlying 
functions, even if those functions would have actually worked 
fine with most of the code that was calling them, because the 
types being passed in had the new requirements already (it's just 
that the functions higher up the stack didn't list the updated 
requirements yet).

By having foo list all of the traits/concepts that would be 
required anywhere in its call stack, you're doing something very 
similar to what happens with checked exceptions where the 
exceptions have to be listed clear up the chain. It's not quite 
the same, and there's no equivalent to "throws Exception" (since 
that would be equivalent to somehow having a trait/concept that 
said that you didn't care what the type given to foo 
implemented). Rather, you're basically being forced to list each 
trait/concept individually up the chain - but it's still a 
similar problem to checked exceptions. It doesn't scale well. And 
if a function is required to list all of the traits/concepts that 
are required - even indirectly - then changing the requirements 
of a function - even slightly - results in code breakage similar 
to that of checked exceptions when you change which exceptions a 
function throws, and "throws Exception" wasn't being used. And 
even if you're not worried about breaking other people's code, 
it's a maintenance problem to maintain that list clear up the 
chain.

Unfortunately, we _do_ have a similar problem with template 
constraints if we insist on putting all of the function's 
requirements in its template constraint rather than just its 
immediate requirements. But at least with template constraints, 
if the top-level constraint is missing something that a function 
somewhere deeper in the stack requires, then you get a 
compilation error only when the type being passed in doesn't pass 
the constraint on the function deeper in the stack. So, if you 
adjust a template constraint, it will only break code that 
doesn't work with the new constraint - even code that uses that 
function indirectly (possibly even quite deeply in a call stack, 
far from their own code) won't break due to the change, unless 
the type being used doesn't pass the new constraint. And when it 
does fail, the errors may not be pretty, but they do tell you 
exactly what's required to figure out what's wrong when you look 
at the source code. Whereas the traits/concepts solution would 
break _all_ code that used the function that was adjusted (even 
indirectly), not just the code that wouldn't work with the new 
requirements.

I discussed this quite a bit more elsewhere in this thread: 
http://forum.dlang.org/post/lsxidsyweczhojoucnsw@forum.dlang.org

- Jonathan M Davis


More information about the Digitalmars-d mailing list