Why is `+` used instead of `&&` when specifying multiple trait bounds?

Currently, we use a plus operator (+) to specify multiple trait bounds like:

use std::fmt::{Debug, Display};
fn print<T: Display + Debug>(value: &T) {
    println!("{0}\n{0:?}", value);
}

But this means that print() requires T to implement both of the two traits. It is more appropriate to use the logical and operator (&&) instead of +.

Plus operator usually means addition, and this corresponds to disjunction in logical operation. On the other hand, logical conjunction corresponds to multiplication.

C++, for example, uses && to require multiple concepts. Rust should replace + with && in the context of trait bounds, too.

Has there been any proposal for this yet?

No, it shouldn't, because it would be a wildly breaking change. It's not worth it to change only some superficial syntax that is extensively used in probably millions or billions of lines of code, just for the sake of syntax.


Apart from that, + can be read as "and" colloquially. Programming language design doesn't mean that all terminology must strictly follow the language of mathematics or formal logic. A good piece of syntax is such that it's obvious to a human reader speaking a natural language, who is not necessarily familiar with formal logic.

12 Likes

it would be a wildly breaking change.

You can use the editions to implement this. It is possible to make a breaking change without code breaks.

+ can be read as "and" colloquially.

I didn't know that the same word "and" is used for both addition and logical conjunction in English. I'm Japanese, and just use different words for these two operations. Now I understand why + is used, and it should be so if most people feel it natural. Thank you

5 Likes

To clarify here, just because we can doesn't necessarily mean we should. Even if there is a syntax change that can be done without breaking everyone, and even if most people would agree that the new syntax is better than the old syntax, people can still be against the change based on the amount of disruption it would cause in the ecosystem. As one example of that disruption, materials would need to be updated to use the new syntax. Then as new people learn the language, they learn the new syntax. But when they come into contact with older code, they then also have to learn the old syntax. A lot of code will get updated, but only over time. And some code won't. That's not a great experience. Sometimes it's warranted and worth doing, but it's a large hurdle to overcome.

17 Likes

Following that opinion, wouldn't it be even better to use & instead of &&? && is a special version which has short circuiting semantics but those don't apply to requirements in C++ (I think? Since the requirement evaluation has no side effects), so I don't know why they went with &&.

I have to say though that I never tripped over the fact that + is used in Rust. I do see the argument that & would make more sense but I don't think that it's worth changing it.

3 Likes

No offense but you can stop arguing about this. It will not change, regardless of what you, I, or anyone else argues for.

In addition, editions are not tools to implement breaking changes.

No, neither would be appropriate, since those operators are associated with boolean logic specifically, and specifying trait bounds is not boolean logic.

1 Like

wouldn't it be even better to use & instead of &&

That's true. In C++, & is not a logical and operator but a bitwise and operator (though you can use it for values of boolean type due to implicit type casting). But & works as a logical and operator in Rust, so I should have said & instead of &&.

I never tripped over the fact that + is used in Rust.

I'm now realizing that many people feel natural with +. Maybe I tend to worry about minor things.

editions are not tools to implement breaking changes.

The edition guide says:

This opt in enables editions to contain incompatible changes, like adding a new keyword that might conflict with identifiers in code, or turning warnings into errors.

Doesn't it mean that editions are for implementing breaking changes?

Given what's happened in the past, technically yes. However, that doesn't mean that any old incompatible change will go through. The incompatible changes so far were of 1 of 2 varieties:

  1. Necessary because the alternative was much worse
  2. Small but technically breaking additions such as the aforementioned keyword additions.

Specifically syntactic changes are heavily frowned upon in this community, because they:

  1. Have at best nothing to offer
  2. In fact generally they have negative value because while they don't offer anything that couldn't be done before, they do incur a tremendous cost for the ecosystem.
5 Likes

I understood the demerits of this change... Thank you.

2 Likes

I completely agree there is no point in changing this syntax now, but this argument is not convincing. One could say that we should not use + because it is associated with numerics, and specifying trait bounds is not a numeric operation.

As a matter of fact, Java uses & for this purpose in generics: <T extends I1 & I2>.

3 Likes

I suspect that one of the reasons that this happened is that + is more uncommon in type grammar. You can have things like T: Add<&'static str>, so it can be easier to scan when & is always references and + is joining traits, vs using ampersands for both.

10 Likes

I can see there is room for confusion here.

In colloquial English "and" and "plus" are very similar. For example:

"If you want to buy a house you need 1,000,000 GBP for the house plus 10,000 GBP lawyers fees plus 1000 GBP inspection fees"

vs

"If you want to buy a house you need 1,000,000 GBP for the house and 10,000 GBP lawyers fees and 1000 GBP inspection fees"

(I have no idea what the figures are today in England but you get the idea)

That aside, I would hate to see a simple + replaced with the horribly ugly `&&'. Which by the way does not denote the AND operation in any mathematical logical notation I have ever seen. It's just an ugly syntax of convenience dating back to the time of ASCII terminals.

And finally, I don't think and editions or whatever versioning system in place should be used just to make unnecessary breaking changes to the language at random.

At the end of the day, programming languages are not mathematics (or even colloquial English). They have a notation of their own. Perhaps the closest to maths is Haskell.

4 Likes

I was specifically talking about Rust. I don't think it's a good idea to go syntax mining in other languages, as they have different communities, expectations, experiences, idioms, and even conceptual models.

And no Java doesn't use & for this purpose, since traits aren't interfaces or vice versa. Those features are more like distant cousins than siblings or even the same feature.

In other words: even if there's a language that uses Β¬ for trait bound specification, it's completely irrelevant, because it isn't Rust.

I think it would have been horrible to have & or && in trait bounds given how frequently & already shows up in types.

Avoiding overloading symbols as much as possible is a good thing.

5 Likes

Traits are not condition, they're category of types. I think the plus sign matches more with its corresponding mathematical notation, and we've stabilized and used in all the places since 2015 while the C++ only accepted this concept(pun intended) on 2020. C++ should change it into +. Of course C++ doesn't have the edition mechanism, but should we care the lack of feature of that language?

1 Like

Furthermore, the concept of addition actually does apply here!

You could think of T: Display, T: Debug in a number of ways. The simple one is "T provides Display and T provides Debug. But there's another, more type theoretical way, where addition is the correct way to combine them.

At a type level, a trait is a set of some provided functionality. The Display trait says that the type provides some set of functionality, containing the single function <T as Display>::fmt(&self, &mut fmt::Formatter<'_>) -> fmt::Result. The Debug trait is similar. Display + Debug, then, is the set resulting from a set addition[1] of the two sets, providing both <T as Display>::fmt and <T as Debug>::fmt.

The word and is colloquially used both for a "logical and," which is the boolean case. But it's also used for an "additive and," where it can be substituted for plus, because it indicates the joining of two collections to form an aggregate collection.


[1] wait, isn't that a union? Yes and no: a set addition is strictly speaking a subset operation of a set union, where the elements of both input sets are required to be distinct. Also strictly speaking, adding more trait bounds actually probably should be described as a union operation, because you can specify the same bound twice and it's fulfilled by a singular implementation. But you could also interpret it as adding another copy of the same requirement, rather than deduplicating as a unique set.

6 Likes

So we should be using βˆͺ for this instead. I'm sure that nobody would mind us picking a symbol that doesn't appear on most keyboard layouts but looks very similar to one that does. /s


Looking at it from a different angle, however, it could also be considered an intersection of typeclasses instead of a union of requirements.

3 Likes

Isn't it intersection, not union? Depends on how you think about it, I guess.

T: Display β†’ the set of types T implementing Display
T: Debug β†’ the set of types T implementing Debug

T: Display ∩ T: Debug a.k.a T: Display + Debug is the intersection of the sets - those implementing Display and Debug. In some languages including Rust, & is used for set intersection.

In maths:

∩ is intersection
∧ is logical and

It's not a coincidence that these are similar :slight_smile: Unfortunately we don't have a good Ascii symbol for that - but we have & and + which are used for β€˜and’ in different contexts.

2 Likes

I think both perspectives are valid -- it's an intersection of types that satisfy those constraints, and a union of capabilities you can use as a result.

8 Likes