Suggestion for removing boilerplate code

Hi,
So I did a nice project for symbolic maths in rust a while ago - https://github.com/Metadiff/symbolic_polynomials
However, getting back to it I've just added two features to the crate, but was wondering if anyone knows of any simple way of reducing the boilerplate code for implementing operators. For instance, I have a lot of places where the class implements an operator for both T and &T. Also, I need to explicitly implement and the reverse operator traits for the primitive variables with the polynomial, e.g. Add<Polynomial> for i64. Is there any general way of avoiding it or at least writing less boilerplate.

Also, any suggestions or comments on the code are most welcome.

For repeated implementations macros work well.

https://github.com/pornel/rust-rgb/blob/master/src/internal/convert.rs

I think another way of abstracting over something which may be either a borrow or owned (T or &T) is to use the Borrow and AsRef traits.

The Borrow trait is used when you’re writing a data structure, and you want to use either an owned or borrowed type as synonymous for some purpose.

Someone with more experience may correct me, but that could halve the amount of boilerplate you need to write. Otherwise you can use a macro to generate the impls if the code is reasonably similar.

1 Like

Yeah I think I gave it some attempt, but the issue was that you can not impl the traits for a trait object, that is you can remove Add<T> for C and Add<T> for &C, potentially you can eliminate only if the <T> in the trait can be both a ref and an object, which might indeed cut the the boilerplate a half... I will have to give it a try.

I think you might run into "generic parameter X is unconstrained by the impl, self, or predicates" error if you try to impl the ops for a type that also implements Borrow. I noticed there's a Polynomial<I, C, P> struct in the crate. So one might be tempted to write something like:

impl<I, C, P, T> Mul<C> for T
where I: Id,
         C: Coefficient,
         P: Power,
         T: Borrow<Polynomial<I, C, P> {
     type Output = Polynomial<I, C, P>;
     fn mul(self, rhs: C) -> Self::Output {
       // use self.borrow() to work with owned and reference Polynomial<...> types
     }
}

And the above will cause the compiler to indicate that type params I and P are unconstrained.

Maybe there's a way to formulate around this, or do it entirely differently and yet achieve generalization over owned and reference types.

1 Like

Yes, I was looking exactly for something like that, I think I've tried it with AsRef and as you pointed out did not work. Especially in my case where I'm returning a newly created value anyway, it would have been very nice if there is some way around this issue.