Skip to content

Binary operator traits with specializable generic implementations #2578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

mzabaluev
Copy link
Contributor

@mzabaluev mzabaluev commented Oct 25, 2018

Provide new fallback traits for overloading binary operators, such as DefaultPartialEq, DefaultPartialOrd, DefaultAdd, DefaultAddAssign, etc., with default blanket implementations abstracted over Borrow for built-in and standard library types. This will drastically reduce the number of explicit impl items necessary to cover operator-compatible type pairs. When resolving the implementation for an operator, the compiler will consider the new traits as the second choice after the plain old non-specializable overload traits.

Edited: previous revisions proposed next-gen operator overload traits to replace the Rust 1.0 traits, with a complicated overload resolution rule for backward compatibility.

Rendered view

Added more explanation, details, and motivational impetus for the
default blanket implementation rule.
@Centril Centril added T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC. labels Oct 25, 2018
@withoutboats withoutboats removed the T-lang Relevant to the language team, which will review and decide on the RFC. label Oct 26, 2018
@mzabaluev
Copy link
Contributor Author

@withoutboats The compiler will need to be changed to consider the new traits, with a kink in the algorithm. Are you sure T-lang does not apply?

@Centril Centril added A-traits-libstd Standard library trait related proposals & ideas A-resolve Proposals relating to name resolution. A-operator Operators related proposals. labels Nov 22, 2018
mzabaluev added 3 commits May 4, 2019 00:47
Edited text to improve readablity.
Added a `?Sized` bound in an example.
In binary-ops-specialization, the example does not need to
provide a special case for string slices on the right hand side.
The default body of method ne2 called an eq method that's not
present in this trait. The second-gen method is named eq2.
@mzabaluev
Copy link
Contributor Author

An alternative idea occurred to me: add the new style op traits not as a next-gen replacement for the Rust 1.0 traits, but as fallback generic implementations: the generic impl of OpDefault (not necessarily specializable) would apply when no Op implementation matches the operator.

WDYT?

@RustyYato
Copy link

I don't see how this proposal solves the problem with specialization because this looks just like specialization. But only for the specified operator traits and with some new added complexity. Please elaborate on how this improves this situation. If the only difference is that it isn't a breaking change to add a PartialEq2 impl when a PartialEq impl exists, then that is a poor reason to add these traits. What if we wan't to use AsRef in the future, or provide some other way of doing PartialEq, do we need to add PartialEqN (slippery slope argument). This sets a bad precedent.

Yes it is unfortunate that we can't extend String (or other types) in this way, I don't see this as sufficient motivation for such complexity.

@mzabaluev
Copy link
Contributor Author

@KrishnaSannasi For a real-world example that perhaps illustrates the problem better than the dry motivational prose of my RFC, please take a look at this long list of PartialEq/PartialOrd impls for Bytes and BytesMut. Please suggest a way to resolve this tedium with generic default implementations that: 1) do not conflict with the impls that are already out there; 2) automatically cover any other crate that comes up with a clever way to package a slice of bytes, without adding a dependency to that crate from bytes or vice versa.

As suggested in my previous comment, I now think the new traits should augment the existing fully specialized operator traits rather than soft-deprecating them. The impl resolution rule does not need to be that complex, either. I will update the RFC with this idea.

@mzabaluev mzabaluev changed the title Second-generation binary operator traits with specialization Binary operator traits with specializable generic implementations Jun 18, 2019
Reinvented the specializable operator overload traits as fallbacks
augmenting the extant operator traits rather than replacing them.
all crates in the dependency graph to be compatible with the feature.
The proposed [rule][default-implementation-rule] of defining generic
operator impls slashes the quadratic explosion of mostly tedious
non-generic operator trait implementations that takes place today.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of this tedium could be resolved through disciplined use of macros.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm doing this, and it's still tedious 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The macros also don't help when there is another clever container crate whose types compare to the same basic types as yours. You'd have to add a dependency on the crate, just for the impls. The authors of that crate might want to do the same for your crate, and now you have to mutually agree over what depends on what and under which feature flags.

The generic impl does it automagically and with no extra dependencies, as long as there is a fitting Borrow.

considered together with its operand type family for the following rule.
Types in each such family usually implement `Borrow` to the base operand type,
and their binary operator trait impls, as currently provided, tend to cover
any possible pairs with the other types in the family.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just impl PartialEq and such for just the base varaints? Then force people to convert the owned versions to do the comparison? Is that too much of an ergonomic hit?

Copy link
Contributor Author

@mzabaluev mzabaluev Jun 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, using .as_str() and .as_bytes() everywhere for a workalike type with a different ownership model is incredibly annoying and inelegant. Especially considering that we have other generic APIs using Borrow where all lender types for the same target work interchangeably.

for non-`Copy` types: these should not work between two borrowed
values to avoid allocations or other side effects hidden in operator notation,
while moving both owned operands into an operator expression
may be non-ergonomic.
A precedent is set in the `Add` implementation for `String` to only let

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More than a few are unhappy with this precedent, and would like to see it changed.

Copy link
Contributor Author

@mzabaluev mzabaluev Jun 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no really good alternatives that could have been chosen with the non-specializable Add for Rust 1.0. There are probably other considerations for some owned types without open-ended Borrow/Deref kinship. What do linear algebra people do?

@KodrAus KodrAus added the Libs-Tracked Libs issues that are tracked on the team's project board. label Jul 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-operator Operators related proposals. A-resolve Proposals relating to name resolution. A-traits-libstd Standard library trait related proposals & ideas Libs-Tracked Libs issues that are tracked on the team's project board. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants