As far as I can tell, the only systems that have a real answer to the commutativity problem (and similar) are term-rewriting systems using e-graphs: declare that x + y = y + x, and then try both expansions, picking whichever gives the ultimate result you like better.

Rust could support “intelligent” commutative operations (to the extent of even distinguishing between allowed and disallowed addition/multiplication of matrices) with its recent improvements to const generics if it let you define a commutative trait like the ones in the article (that don’t compile) and then you used const math generic constraints to define the requirements on M and N as it’s purely based off the shape of a matrix that math is allowed.

Are they the same type or different types? If they’re the same type, then the operation will be forwarded to the impl with the lhs/rhs matching how the dev typed them. If they’re different types, you would have two separate impls (x lhs + y rhs, y lhs + x lhs) and the one matching how the dev typed it would be called. Unless I’m missing something.

bytesize which I’ve been using in systemstat for a long time does something slightly interesting regarding various types – number + ByteSize impls are explicit for each numeric type via macros, but ByteSize + number is just one impl<T> Add<T> for ByteSize where T: Into<u64>.

I mention in the article that I was able to do something similar where LHS is a Size, one generic impl<T> Mul<T> for Size where T: IntoIntermediate covers all the primitive integers but you need the separate impls for each primitive type as the RHS.

I don’t use Into<u64> because a) PrettySize supports negative sizes (e.g. the difference between two sizes) so the “base” unit is i64, b) rust doesn’t provide impls for Into<uXX> from signed iXX values, c) I also support floating-point sources (e.g. Size::from_mib(1.1)) - all of which just means I have my own (sealed/private) trait called AsIntermediatethat I implement via a macro for all the primitive signed, unsigned, and float types (except x128) which does a saturating conversion (e.g. u64::MAX becomes i64::MAX).

I guess again unlike bytesize, I also have a second impl even for the LHS of Size case to support ops on a reference - you need for Size and for &Size separately (again because of rust’s orphan rule) since you can’t just do impl ... for Borrow<Size> to cover both Size and &Size (this is discussed briefly in the article).

As far as I can tell, the only systems that have a real answer to the commutativity problem (and similar) are term-rewriting systems using e-graphs: declare that x + y = y + x, and then try both expansions, picking whichever gives the ultimate result you like better.

Rust could support “intelligent” commutative operations (to the extent of even distinguishing between allowed and disallowed addition/multiplication of matrices) with its recent improvements to const generics if it let you define a commutative trait like the ones in the article (that don’t compile) and then you used const math generic constraints to define the requirements on M and N as it’s purely based off the shape of a matrix that math is allowed.

That is not a general solution. What do you do if x+y and y+x are both valid?

Are they the same type or different types? If they’re the same type, then the operation will be forwarded to the impl with the lhs/rhs matching how the dev typed them. If they’re different types, you would have two separate impls (x lhs + y rhs, y lhs + x lhs) and the one matching how the dev typed it would be called. Unless I’m missing something.

`bytesize`

which I’ve been using in`systemstat`

for a long time does something slightly interesting regarding various types –`number + ByteSize`

impls are explicit for each numeric type via macros, but`ByteSize + number`

is just one`impl<T> Add<T> for ByteSize where T: Into<u64>`

.I mention in the article that I was able to do something similar where LHS is a

`Size`

, one generic`impl<T> Mul<T> for Size where T: IntoIntermediate`

covers all the primitive integers but you need the separate impls for each primitive type as the RHS.I don’t use

`Into<u64>`

because a) PrettySize supports negative sizes (e.g. the difference between two sizes) so the “base” unit is`i64`

, b) rust doesn’t provide impls for`Into<uXX>`

from signed`iXX`

values, c) I also support floating-point sources (e.g.`Size::from_mib(1.1)`

) - all of which just means I have my own (sealed/private) trait called`AsIntermediate`

that I implement via a macro for all the primitive signed, unsigned, and float types (except`x128`

) which does a saturating conversion (e.g.`u64::MAX`

becomes`i64::MAX`

).I guess again unlike

`bytesize`

, I also have a second impl even for the LHS of`Size`

case to support ops on a reference - you need`for Size`

and`for &Size`

separately (again because of rust’s orphan rule) since you can’t just do`impl ... for Borrow<Size>`

to cover both`Size`

and`&Size`

(this is discussed briefly in the article).