Search code examples
rusttraitstypeclass

Multiple implementations for the same trait of the same type in Rust


With Rust traits, I can express a Monoid type class (forgive me for the naming of the methods):

trait Monoid {
  fn append(self, other: Self) -> Self;
  fn neutral() -> Self;
}

Then, I can also implement the trait for strings or integers:

impl Monoid for i32 {
  fn append(self, other: i32) -> i32 {
    self + other
  }
  fn neutral() -> Self { 0 }
}

However, how could I now add another implementation on i32 for the multiplication case?

impl Monoid for i32 {
  fn append(self, other: i32) -> i32 {
    self * other
  }
  fn neutral() { 1 }
}

I tried something like what is done in functional but that solution seems to rely on having an additional type parameter on the trait instead of using Self for the elements, which gives me a warning.

The preferred solution would be using marker traits for the operations - something I also tried but didn't succeed in.


Solution

  • The answer, as pointed out by @rodrigo, is to use marker structs.

    The following example shows a working snippet: playground

    trait Op {}
    struct Add;
    struct Mul;
    impl Op for Add {}
    impl Op for Mul {}
    
    trait Monoid<T: Op>: Copy {
        fn append(self, other: Self) -> Self;
        fn neutral() -> Self;
    }
    
    impl Monoid<Add> for i32 {
        fn append(self, other: i32) -> i32 {
            self + other
        }
        fn neutral() -> Self {
            0
        }
    }
    
    impl Monoid<Mul> for i32 {
        fn append(self, other: i32) -> i32 {
            self * other
        }
        fn neutral() -> Self {
            1
        }
    }
    
    pub enum List<T> {
        Nil,
        Cons(T, Box<List<T>>),
    }
    
    fn combine<O: Op, T: Monoid<O>>(l: &List<T>) -> T {
        match l {
            List::Nil => <T as Monoid<O>>::neutral(),
            List::Cons(h, t) => h.append(combine(&*t)),
        }
    }
    
    fn main() {
        let list = List::Cons(
            5,
            Box::new(List::Cons(
                2,
                Box::new(List::Cons(
                    4,
                    Box::new(List::Cons(
                        5,
                        Box::new(List::Cons(-1, Box::new(List::Cons(8, Box::new(List::Nil))))),
                    )),
                )),
            )),
        );
        
        println!("{}", combine::<Add, _>(&list));
        println!("{}", combine::<Mul, _>(&list))
    }