Rant after trying Rust a bit

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Thu Jul 23 23:05:04 PDT 2015


On Fri, Jul 24, 2015 at 05:39:35AM +0000, Tobias Müller via Digitalmars-d wrote:
> Walter Bright <newshound2 at digitalmars.com> wrote:
> > On 7/23/2015 3:12 PM, Dicebot wrote:
> >> On Thursday, 23 July 2015 at 22:10:11 UTC, H. S. Teoh wrote:
> >>> OK, I jumped into the middle of this discussion so probably I'm
> >>> speaking totally out of context...
> >> 
> >> This is exactly one major advantage of Rust traits I have been
> >> trying to explain, thanks for putting it up in much more
> >> understandable way :)
> > 
> > Consider the following:
> > 
> >     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();
> >     }
> > 
> > Now consider a deeply nested chain of function calls like this. At
> > the bottom, one adds a call to 'color', and now every function in
> > the chain has to add 'hasColor' even though it has nothing to do
> > with the logic in that function. This is the pit that Exception
> > Specifications fell into.
> > 
> > I can see these possibilities:
> > 
> > 1. Require adding the constraint annotations all the way up the call
> > tree. I believe that this will not only become highly annoying, it
> > might make generic code impractical to write (consider if bar was
> > passed as an alias).
> > 
> > 2. Do the checking only for 1 level, i.e. don't consider what bar()
> > requires. This winds up just pulling the teeth of the point of the
> > constraint annotations.
> > 
> > 3. Do inference of the constraints. I think that is
> > indistinguishable from not having annotations as being exclusive.

Well, this is where the whole idea of Concepts comes from. Rather than
specify the nitty-gritty of exactly which operations the type must
support, you introduce an abstraction called a Concept, which basically
is a group of related traits that a type must satisfy. You can think of
it as a prototypical "type" that supports all the required operations.

For example, an input range would be a Concept that supports the
operations .front, .empty, and .popFront. A forward range would be a
larger Concept derived from the input range Concept, that adds the
operation .save.

Your range functions don't have to specify explicitly that "type T must
have methods called .empty, .front, .popFront", they simply say "type T
must conform to the InputRange Concept".

	// Hypothetical syntax
	concept InputRange(ElementType) {
		bool empty;
		ElementType front();
		void popFront();
	}

	void myRangeFunc(R : InputRange)(R range)
	{
		// freely use .empty, .front, .popFront here
	}

This also solves your objection in the other post that specifying
constraints will become too onerous because you have to keep listing
every individual operation the function needs, and changing a function
far down the call chain will bubble up and require updating all
functions that call it. With Concepts, you don't have to do this,
because any change can be done in the definition of the Concept itself.

If not, the function that requires more than what the current Concept
provides actually needs a larger Concept that it's asking for, in which
case all its callers *need* to be updated anyway. It's no different from
deciding that a function that used to take struct S1 now needs to take
struct S2 instead -- there's no way to avoid having to update all code
that calls the function so that they pass in the new type.

Concepts can derive from other Concepts too, so a ForwardRange concept
need not repeat the traits specified by the InputRange concept; it can
simply specify that a forward range supports all InputRange traits, plus
the .save method.

If you like, think of Concepts as compile-time interface definitions.


> > Anyone know how Rust traits and C++ concepts deal with this?
> 
> You may aus well ask "How do interfaces in OO programming deal with
> this?".  Frankly, I've never had an issue with that. Or it's a hint
> for design problems.
> 
> Traits (and interfaces) are mostly not that fine grained, i.e. you
> don't have a trait/interface for every method.
> They should ideally define an abstraction/entity with a semantic
> meaning.  If your constraint "hasColor(x)" just means "x has method
> color()", and then implement it for every class that has this method,
> you can just as well omit constraints and use duck typing.
[...]

Yes, the value of Concepts mostly comes from the, um, concepts that
group together a set of traits that characterize a particular category
of types. Like input range, forward range, or output range.  It's of more
limited utility for testing traits individually.  You're not really
thinking in terms of individual traits, at least not directly, when
you're using Concepts; you're thinking in terms of the conceptual
abstraction that the Concept represents. The compiler does the checking
of individual traits for you.


T

-- 
The richest man is not he who has the most, but he who needs the least.


More information about the Digitalmars-d mailing list