All types are equal ... but some are more equal than others


#1

Yesterday in IRC we noted a slight problem. The = operator can mean one of three things when used in relation to types:

  • “Default” types. Eg struct Foo<T=u8>(T). This means that if unspecified, T is a u8
  • Equality for associated types. For example, T: Iterator<Item=u8> can be used to specify that type T is an iterator over u8
  • The mythical equality bound in where. There is partial internal support for things like where T == Foo or whatever (probably can be much more complex given the nature of where clauses). I don’t know what the plan is for these, but these will probably add more complexity to type bounds.

Having three usages for = in type bounds is confusing. The compiler can tell them apart via context, but it can be confusing for a human to read.

Perhaps we should change the operators being used here somehow?


#2

We have already established = to assign defaults in type parameter lists for structs and for functions and methods, so we don’t have much choice but to use a different symbol for type equality.


#3

T: Iterator<Item=u8> is (obviously?) the ugly one here, it’s the only place where = is used for equality. Personally, I never liked this syntax for its inconsistency, but everyone seemed okay with it so I never voiced the discontent.


#4

#2 is just a shorthand for #3 (that the more general longhand form isn’t implemented yet is just an artifact of the release schedule). So using a different symbol for #3 than for #2 would be strange. The actual inconsistency is only between #1 and #2+#3. But given that #1 and #2 both exist already, we are kind of stuck with this. It’s potentially a bit unfortunate (especially since #1 looks so similar to #2), but it’s not actually that different from the “multiple meanings” of = we already have at the value level (let foo = bar for initialization vs. foo = bar for mutation). I’m not really bothered by it personally.


#5

The question is the way forward. I think adding = as equality bound in where clauses would be insanity, due to semantic conflict with = in regular type parameter lists.


#6

Well, permit T: Iterator<Item==u8> and deprecate T: Iterator<Item=u8>.


#7

Or, deprecate T: Iterator<Item=u8> and forget that it ever existed because that syntax doesn’t make any sense; Item is not a type parameter to Iterator.

Then, permit:

T: Iterator,
T::Item == u8

Disallow:

T::Item == u8, // Error: no `Item` in scope
T: Iterator

And to disambiguate between multiple associated items with the same name:

T: Iterator + SomeOtherTraitWithAssociatedTypeNamedItem,
T::Item == u8 // Error: multiple `Item`s in scope
T: Iterator + SomeOtherTraitWithAssociatedTypeNamedItem,
<T as Iterator>::Item == u8 // OK

Also, allow:

T: Iterator,
T::Item: ToString

#8

I don’t think it is right to say = is used in three places - == is treated as a different operator for expressions, so we should do so for types too. I.e., it makes perfect sense to use == in where clauses.

I think it is clearly the right choice for defaults too.

For associated types it is more interesting. I think the current choice is correct (although I see how reasonable people could differ on this). I don’t think of it as an equality constraint, but something akin to initialisation. Iterator<Item=u8> defines the type given by starting with Iterator and setting Item to u8. In particular it is not the set of types I which are Iterators and satisfy the constraint I::Item == u8. The difference to me is what can appear on the rhs. The = syntax implies (correctly) that we can only set the associated type to a concrete type. The == syntax implies (wrongly) that we could constrain two associated types to be the same. E.g., for a trait T with two assoc types A and B, we could have a constraint in a where clause that T::A == T::B, but we could not have T<A=B>.

tl;dr, I don’t think there is a problem here


#9

I think default type parameters ~ default initialization is a less strained analogy than associated types sugar ~ initialization, for what it’s worth.

…I thought it was. What’s the difference? (Is there a counterexample?)


#10

Yeah, default type params are a closer analogy.

I’m not sure there is a counter-example, since this is somewhat hand wavey. I think at some level the two interpretations are equivalent (which is why I said it was reasonable to take either position). What makes me prefer = to == is the level of generality. An == where clause allows the programmer to state that any two types are equal, whereas the = assoc type param constraint admits only associated types of the parametrised trait on the lhs, and only concrete types on the rhs.


#11

Can we just use Iterator<Item: u8>? (This is starting to sound like the epic struct syntax flamewar)


#12

I really like this. It looks more pleasing (which is entirely subjective), but it also is more consistent with the way where clauses work. It would be a large last-minute change for 1.0 though (as would the other proposals), so I doubt anything is going to change at this point.


#13

Late to the discussion (and probably too late since 1.0 is out), but I don’t see Iterator<Item=u8> as a use of equality. Iterator<Item==u8> implies to me that Item==u8 can be replaced with a bool value, which is obviously not true.

I rather see it similar to a keyword argument in a function call in other languages. Changing it to Iterator<Item: u8> seems fine, but unnecessary to me: I don’t see the current syntax as ugly to begin with.


#14

I think this hits the nail on the head with respect to what really bugs me about the == syntax. This is an equality requirement, not an equality test. Thanks!

I think it would be more coherent to use this syntax as sugar for bounds on associated types, as proposed by @P1start.