I think Rust made two design mistakes in this regard.
Mistake #1: Mix “operator syntax” with “operator semantics”
For example, the operator traits are called after the mathematical operation that they represent, the trait for + is called Add. However, say I want to implement string concatenation for my string type using +. Now I have to implement the Add trait, which conveys “Addition”, but addition is commutative, while string concatenation is not. Calling them OpPlus or even Op+ would have been “cleaner”. Anyhow, we can live with this.
Mistake #2: Implement operators for types where the meaning is overloaded
Consider the example above, would you implement + for your own string type? Rust std does not, because its meaning is overloaded, as explained above. However, it implements ^ to mean xor, when ^ can also mean exponentiation… This is not that bad, since lots of languages use ^ to mean xor and then they go on to use e.g. ** to mean exponentiation instead.
However, the bitwise operators << and >> can mean logical shift left/right, or arithmetic shift left/right. 90% of the time you don’t care since they produce the same result, but when they do not, then you do care and can’t tell what’s what. This sucks.
We can still live with this. For example, I have 4 functions in the bitwise crate: shift_arithmetic/logical_right/left that convert the integer to a signed/unsigned type of the same size, perform the shift, and then convert it back, because particularly in generic code, it is really hard to tell what << and >> are actually doing on integers at all.
So… in a nutshell, I actually care more about what these operators convey to the humans reading the program, rather than how hard they do make the rust compiler to implement. When there are any ambiguities, everything is fine. When there might be some ambiguities, I prefer to eliminate them with a more explicit approach. Explicit approaches that convey the wrong information (Add conveying addition instead of OpPlus) are misleading, but that’s how things are.