-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Commutative multiplication and addition #2608
Comments
Your second block defines an operation on a trait object, but then attempts to consume that DST by value, and uses a syntax slated for deprecation: https://github.com/rust-lang/rfcs/blob/master/text/2113-dyn-trait-syntax.md You want:
|
I kind of got lost drafting the post, I think the point that I was trying to make is that no matter which way you approach it, it's not going to work due to language/design constraints. Your example would work great if I guess my real question is if there is (as a thought experiment) an alternative way that select operators could be implemented that wouldn't end up running into the restrictions on trait implementations. |
I believe #2451 explains why everything works fine. |
Two words: Matrix multiplication. |
Eight more words: string concatenation in the standard library, no less. |
But more in the spirit of the thread, I think the closest one can get to making something like this work is by using a proc-macro to implement a DSL; You can replace all Alternative two: A newtype can be used: pub struct Scale<T>(pub T);
impl Mul<MyType<U>> for Scale<T>
where MyType<U>: Mul<T> {
...
} This second alternative scales just a bit better for cross-crate usage, but unfortunately I'm not certain that the ergonomical cost of using either of these workarounds offsets the benefits to the user. |
I do not think #2451 fixes this, as the Specifically it permits impls where:
My suggestion for Scale(2.0) * matrix
// as opposed to the unconventional form
matrix * 2.0 |
@ExpHP Exactly, and I think it's clear to everyone that the ergonomics of your distinct |
Why can't there be a |
Commutative multiplication would look like this: use someone_elses_lib::OtherStruct;
struct MyStruct ...;
impl Mul<OtherStruct> for MyStruct {
type Output = Self;
fn mul(self, rhs: OtherStruct) -> Self::Output {
...
}
}
impl RevMul<OtherStruct> for MyStruct {
type Output = Self;
fn rev_mul(self, lhs: OtherStruct) -> Self::Output {
self.mul(lhs)
}
}
(Edited for clarity.) |
How would you desugar |
I'm pretty new to Rust so I'll ask another question rather than directly answer yours: Is it not doable for the compiler to desugar differently based on context? It seems like something as fundamental as operator overloading should be worthy of some special treatment from the language. |
It is possible to desugar things based on context, but I don't think anything besides closures and the newly minted In this case the danger is something like this: // crate A:
struct Foo;
// crate B:
struct Bar;
impl RevMul<crate_a::Foo> for Bar { ... }
// crate C
fn main() {
let _ = crate_a::Foo * crate_b::Bar;
// desugars to
let _ = ::core::ops::RevMul::rev_mul(crate_a::Foo, crate_b::Bar);
} Now, let's say that we desugar by precedence: first, we try Now, // crate A
// snip ...
impl<T> Mul<T> for Foo { ... }
// crate C
fn main() {
let _ = crate_a::Foo * crate_b::Bar;
// desugars to
let _ = ::core::ops::Mul::mul(crate_a::Foo, crate_b::Bar);
} Note that the desugaring changed! This is a breaking change, especially if So if not precedence how should you decide which of Keeping in mind:
|
If a |
If that were true, yes it wouldn't be a breaking change, however you don't need to
More egregiously, it would mean adding a |
I wonder if we can have a phantom marker that indicates a type opts out of a default implementation , eg struct Foo {
x: i32,
_revmult: core::phantom::PhantomRevMul,
} This would forbid an impl of This would prevent breaking backwards compatibility since changes to a type, including adding a phantom member, are breaking. It would mean an extra compiler-internal special trait (like Unsize) but it’s really more of an implementation detail than anything else. It does introduce a new paradigm for opting out of (or into) a behavior via composition rather than impl but again, it is influencing language-level rather than lib-level behavior (sugaring) so that doesn’t seem too egregious to me. (Whether this particular behavior is a default opt-in for a future edition or not is a different story.) Personally, I think the problem is with making Output an associated type. I think the cleaner solution would have been a |
Associated In practice, rust code should bless a specific primitive type around which it optimizes performance, not be polymorphic over primitive types. As a rule, one should prefer distinguished scalar types like the All this seems moot now but.. Rust could've placed the LHS and RHS on eaual footing by defining
Rust could've even made
|
I was surprised to learn that the implementation of
core::ops::{Add, Mul}
(keywords: addition, multiplication) is not automatically (and exclusively) commutative. This is often not a problem for cases where it is possible to bothimpl Mul<X> for Y
andimpl Mul<Y> for X
, but this is not an option when generics are involved.For example, this implementation of
Mul
allows multiplyingSize
by any basic number type (fromnum_traits
), as inSize(12) * 2
orSize(42) * 1.0
:but it is not possible to define an ergonomic and efficient inverse to support the commutative variant of the same operations (
2 * Size(12)
or3.0 * Size(7)
), because the following is illegal:as the
self
parameter to themul
function results in an attempt to define a non-generic parameter with unknown size.I would like to use this issue to sound out ideas to support commutative operations, at the very least for operations that are typically (read: mathematically) commutative, but would appreciate any insight or commentary that sheds light on anything I'm missing here first.
The text was updated successfully, but these errors were encountered: