0

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?

2 Answers2

0
  • 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.

  • 1
    This is not a wrong way to do that, but it's also not the common way - because it involves a bunch of heap allocations and virtual calls. – Chayim Friedman Feb 18 '23 at 17:16
0

Use a macro to implement Foo for tuples:

trait Foo {
    fn do_it(&self);
}

struct A {}
impl Foo for A {
    fn do_it(&self) {
        print!("A");
    }
}

struct B {}
impl Foo for B {
    fn do_it(&self) {
        print!("B");
    }
}

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

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

macro_rules! impl_Foo_for_tuple {
    ( $first:ident $($rest:ident)* ) => {
        impl< $first: Foo, $( $rest : Foo, )* > Foo for ( $first, $($rest,)* ) {
            fn do_it(&self) {
                #[allow(non_snake_case)]
                let ( $first, $($rest,)* ) = self;
                $first.do_it();
                $( $rest.do_it(); )*
            }
        }
        impl_Foo_for_tuple!( $($rest)* );
    };
    () => {};
}
impl_Foo_for_tuple!(A B C D E F G H I J K L M);
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • Interesting and a better way to do it for sure if we look at heap allocations, but how can I get only the `A` values for example ? And the `Foo` implementation for tuple is limited to the number identifiers we give in `impl_Foo_for_tuple!(...);`, isn't it? What if we want a list of 15 000 objects? – antoninhrlt Feb 18 '23 at 21:48