Search code examples
templatesgenericsrusttuplesdowncast

Rust generics: list where each element is from the same trait?


I found this question on the Rust users forum : Generics: Can I say "tuple where each element is FromSql". Basically, the questions was to know how do something like that :

trait Foo {}

struct A {}
impl Foo for A {}

struct B {}
impl Foo for B {}

fn main() {
    let x = (A{}, A{}, B{}, A{});
    bar(x);
}

fn bar<T: Foo>(tuple: (T...)) {

}

This code does not work, it's an idea of how it could look like.

So, how can we do that?


Solution

    • The first step is to create a ToAny trait that will be implemented for all our structures.
    use std::any::Any;
    
    pub trait ToAny {
        fn as_any(&self) -> &dyn Any;
    }
    
    • Let's create our trait
    trait Foo: ToAny {}
    

    It requires implementing the ToAny trait to force each structure implementing Foo to implement ToAny too.

    • Let's create our structures:
    struct A {
        id: i32,
    }
    
    impl ToAny for A {
        fn as_any(&self) -> &dyn Any {
            self
        }
    }
    
    impl Foo for A {}
    
    struct B {
        id: i32,
    }
    
    impl ToAny for B {
        fn as_any(&self) -> &dyn Any {
            self
        }
    }
    
    impl Foo for B {}
    

    The implementation of ToAny is always the same, we could create a macro implementing it easily.

    • And then, we can create a Vec instead of a tuple to store our values:
    let boxeds: Vec<Box<dyn A>> = vec![
        Box::new(A {id: 1}),
        Box::new(B {id: 2}),
        Box::new(A {id: 3}),
        Box::new(B {id: 4}),
    ];
    
    // Stores the values being `B`.
    let mut bees: Vec<&B> = vec![];
    
    for boxed in &boxeds {
        // `Some(x)` if `boxed` contains a `B` value.
        let found = match boxed.as_any().downcast_ref::<B>() {
            Some(b) => b,
            None => {}, // it is a `A` value.
        };
    
        bees.push(found);
    }
    
    assert_eq!(bees, vec![
        &B {id: 2},
        &B {id: 4}
    ]);
    

    If we refer to the question, the following code:

    fn bar<T: Foo>(tuple: (T...)) {
    
    }
    

    can be this valid code:

    fn bar(values: Vec<Box<dyn Foo>>) {
    
    }
    

    I've written a gist file being tests for that. Be careful, I've changed the names in this post, Foo is A and there is only the B structure.