Lowest common multiple trait

I am trying to set up a type structure on concrete types that enables me to encode a sort of 'lowest common multiple' between two types. More specifically, I want to be able to write down all pairs of types and their 'lowest common multiple' type such that if I want the two to interact, I can upgrade them both to that upgraded type and operate on now two identical types.

I have a MWE that only works in one direction:

struct A {}
struct B {}
trait Upgradable {}

impl Upgradable for A {}
impl Upgradable for B {}

trait SmallestUpgrade<T: Upgradable> {
    type Output: Upgradable;
}

// impl SmallestUpgrade<A> for B {
//     type Output = B;
// }

impl SmallestUpgrade<A> for A {
    type Output = A;
}

impl SmallestUpgrade<B> for B {
    type Output = B;
}

impl SmallestUpgrade<B> for A {
    type Output = B;
}
struct Foo<T> {
    ts: Vec<T>,
}

pub trait Mult<T: Upgradable> {
    fn mult<U: SmallestUpgrade<T>>(&self, other: &Foo<U>) -> Option<Foo<U::Output>>;
}

impl Mult<A> for Foo<A> {
    fn mult<U:SmallestUpgrade<A>>(
        &self,
        other: &Foo<U>,
    ) -> Option<Foo<U::Output>> {
        None
    }
}

impl Mult<A> for Foo<B> {
    fn mult<U:SmallestUpgrade<A>>(
        &self,
        other: &Foo<U>,
    ) -> Option<Foo<U::Output>> {
        None
    }
}

impl Mult<B> for Foo<B> {
    fn mult<U:SmallestUpgrade<B>>(
        &self,
        other: &Foo<U>,
    ) -> Option<Foo<U::Output>> {
        None
    }
}

impl Mult<B> for Foo<A> {
    fn mult<U:SmallestUpgrade<B>>(
        &self,
        other: &Foo<U>,
    ) -> Option<Foo<U::Output>> {
        None
    }
}

fn main() {
    let a = Foo { ts: vec![A{}] };
    let b = Foo { ts: vec![B{}] };
  

    let c =a.mult(&b); //works

    let c =b.mult(&a); //fails saying: type annotations needed cannot satisfy <A as SmallestUpgrade<_>>::Output == B

}

Is there a better type model for this? Is this a fundamental limitation of rust or just a shortcoming of the 'current' compiler?

Did you mean something like this?

Actually I figured out a similar version that works: Rust Playground

Thanks!

However the main point was to be able to then multiply. However rust complains about the foreign trait:

use num::Complex;
pub trait Upgradable {}



impl Upgradable for f64 {}
impl Upgradable for Complex<f64> {}
pub trait SmallestUpgrade<T> {
    type LCM;
    fn upgrade(self) -> Self::LCM;
}

// impl SmallestUpgrade<A> for B {
//     type LCM = B;
// }

impl<T> SmallestUpgrade<T> for T {
    type LCM = T;
    fn upgrade(self) -> Self::LCM {
        self
    }
}

impl SmallestUpgrade<f64> for Complex<f64> {
    type LCM = Complex<f64>;
    fn upgrade(self) -> Self::LCM {
        self
    }
}

impl SmallestUpgrade<Complex<f64>> for f64 {
    type LCM = Complex<f64>;
    fn upgrade(self) -> Self::LCM {
        Complex::new(self, 0.0)
    }
}

impl<T: Upgradable + SmallestUpgrade<U>, U: Upgradable + SmallestUpgrade<T>> std::ops::Mul<T>
    for U
{
    type Output = U::LCM;
    fn mul(self, rhs: U) -> Self::Output {
        self.upgrade() * rhs.upgrade()
    }
}


fn main() {

}

(Playground)

Is there any smart way around this?

You need your own (non-operator) types or newtypes if you want to do this generically. Even if the orphan rules weren't a thing, you're trying to redefine Mul<f32> for f32 for example.[1]

You also need some sort of bound so that the multiplication makes sense, maybe

T::LCM: Mul<U::LCM, Output = U::LCM>

(but given the problems above I didn't take the time to verify the particulars).


  1. The rules exist to avoid similar breakage, but in the future and between cousin crates. ↩︎

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.