Search code examples
functionrusttypes

Function to accept a set of types


Suppose I have the following structs:

struct Oranges { i: i32 }
struct Apples { i: i32 }
struct Chairs { i: i32 }

I can define a set of Apples and Oranges and count them up as a bunch of fruits.

let o = Oranges{i: 4};
let a = Apples{i: 3};
let fruits = o.i + a.i;
println!("I have {} fruits", fruits)  // I have 7 fruits.

If I write a function I can do this dynamically, but this is restrictive becuase argument 1 must be Apples and argument 2 must be Oranges.

fn count(a: &Apples, o: &Oranges) -> i32 {
    a.i + o.i
}
println!("I have {} fruits", count(&a, &o));  // I have 7 fruits.

How do I write count function that accepts either Apples or Oranges as types?


I realise that this must seem quite trivial to one experienced in Rust. I have introduced Chairs as a comparator that is not allowed.

I have tried to implement traits with compiler errors:

trait Fruit {}
impl Fruit for Apples {}
impl Fruit for Oranges {}

fn count<T, S>(a: &T, o: &S) -> i32
where T: Fruit, S: Fruit
{
    a.i + o.i
}           ^ unknown field error[E0609]

I have also tried to use enums but that just seems to create such a combinatorial mess that I assume there is a much better way.

enum Fruit{
    O(Oranges),
    A(Apples)
}

fn count(a: &Fruit, b: &Fruit) -> i32
{
    match (a, b) {
        (Fruit::O(x), Fruit::O(y)) => x.i + y.i,
        (Fruit::O(x), Fruit::A(y)) => x.i + y.i,
        (Fruit::A(x), Fruit::O(y)) => x.i + y.i,
        (Fruit::A(x), Fruit::A(y)) => x.i + y.i,
    }
}
println!("I have {} fruits", count(&Fruit::A(a), &Fruit::O(o)));  // I have 7 fruits.

Solution

  • Two possibilities:

    Trait with a count method

    Traits can't declare or access fields, so you need to have a count method that you implement for each type of fruit that returns the correct value:

    trait Fruit {
        fn count (&self) -> i32;
    }
    
    impl Fruit for Apples {
        fn count (&self) -> i32 { self.i }
    }
    
    impl Fruit for Oranges {
        fn count (&self) -> i32 { self.i }
    }
    
    fn count<T, S>(a: &T, o: &S) -> i32
    where T: Fruit, S: Fruit
    {
        a.count() + o.count()
    }  
    

    Playground

    enum with a count method to avoid the combinatorial mess

    enum Fruit{
        O(Oranges),
        A(Apples)
    }
    
    impl Fruit {
        fn count (&self) -> i32 {
            match self {
                Fruit::O (o) => o.i,
                Fruit::A (a) => a.i,
            }
        }
    }
    
    fn count(a: &Fruit, b: &Fruit) -> i32
    {
        a.count() + b.count()
    }
    

    Playground

    Avoiding repetition

    If you have a lot of fruit types, you can avoid repeated code for either solution with macros, e.g.:

    trait Fruit {
        fn count (&self) -> i32;
    }
    
    macro_rules! impl_fruit {
        ($($f:ident),*) => {
            $(
                impl Fruit for $f {
                    fn count (&self) -> i32 { self.i }
                }
            )*
        }
    }
    
    impl_fruit!(Apples, Oranges);
    
    fn count<T, S>(a: &T, o: &S) -> i32
    where T: Fruit, S: Fruit
    {
        a.count() + o.count()
    }  
    

    Playground