pub trait Props {
// ...
}
// One, Two and Three implement Props
pub type One = LengthyServiceDefWithGenerics<...>;
pub type Two = AnotherServiceDefWithGenerics<...>;
pub type Three = YetAnother<...>;
impl Builder {
pub fn build_it<P: Props>(&self) -> Elem<P> {
Elem::new(self);
}
}
// somewhere else in the codebase
// there might be more instances to be built than just a, b, c
let builder = Builder.new();
let a = builder.build_it::<One>();
let b = builder.build_it::<Two>();
let c = builder.build_it::<Three>();
This is existing code, quite simplified, in a large code base, and it runs.
As a rust rookie, I first naively thought I could just abstract this away by doing something like
impl Outer {
pub fn new(props: Vec<Props>) {
let builder = Builder::new();
for p in props.iter() {
builder.build_it::<p>();
}
}
}
but I guess that has several issues:
- As far as I know there is no way to make a vector of just types in rust. Types don’t take space in memory.
- Even if they did, the iteration would pick an instance of that vector which probably can’t just be passed to the function like this
build_it::<p>
Is there a way to achieve this? I guess it needs to be done in a very different way.
I invested quite some time in simplifying quite a large codebase involved for this question, but there might things missing in this simplification, or the simplification stopped to make sense. I sincerely apoologise if this is the case. I also searched quite some time around to see if there are already existing questions, but “vector of types” yields results with quite different contexts.
I still hope it became clear what I want to do.
7
IIUC, a macro like the following is equivalent to your Outer::new
:
macro_rules! build_all {
($($t:ty),*) => {
let builder = Builder::new();
$(builder.build_it::<$t>();)*
}
}
fn main() {
build_all!(i32, f32, String);
}
Playground
Note however that this can only be a compile-time construct. There is no way in Rust to build and use a list of types dynamically at run-time.
2
Macro
struct Builder;
struct Elem<P: Props> {
props: P,
}
trait Props {}
impl Props for i32 {}
impl Props for i64 {}
impl Props for f32 {}
impl Props for f64 {}
impl Builder {
fn new() -> Self {
todo!()
}
fn build<P: Props>(&self) -> Elem<P> {
todo!()
}
}
macro_rules! build_all {
($builder:expr,$($t: ty), +) =>{
($($builder.build::<$t>()), + )
}
}
fn main() {
let builder = Builder::new();
let build_all: Elem<i32> = build_all!(builder, i32);
let build_all: (Elem<i32>, Elem<i64>) = build_all!(builder, i32, i64);
let build_all: (Elem<i32>, Elem<i64>, Elem<f32>) = build_all!(builder, i32, i64, f32);
let build_all: (Elem<i32>, Elem<i64>, Elem<f32>, Elem<f64>) = build_all!(builder, i32, i64, f32, f64);
}
I suggest doing it with macro. If you don’t want to change Elem<T>
, here’s an example:
trait Props {}
struct A {}
impl Props for A {}
struct B {}
impl Props for B {}
struct C {}
impl Props for C {}
struct Elem<T: Props> {
// placeholder marker to make it compile
maker: PhantomData<T>,
}
struct Builder {
// stuff
}
impl Builder {
fn build_it<T: Props>(&self) -> Elem<T> {
Elem::new()
}
}
impl<T: Props> Elem<T> {
fn new() -> Self {
Elem { maker: PhantomData }
}
}
macro_rules! elems {
($builder: expr; $($t:ty),*) => {
(
$($builder.build_it::<$t>(),)*
)
};
}
fn foo() -> (Elem<A>, Elem<B>, Elem<C>) {
elems!(Builder {}; A, B, C)
}
The result of the macro is a tuple, so you can’t do Vec
things with it.
The reason is because Elem<A>
and Elem<B>
and Elem<C>
are not the same type, so rust can’t put them in the same Vec<T>
.
If you really want to create a Vec<Elem>
, you need to change Elem<T>
to use dynamic dispatch like so
trait Props {}
#[derive(Default)]
struct A {}
impl Props for A {}
#[derive(Default)]
struct B {}
impl Props for B {}
#[derive(Default)]
struct C {}
impl Props for C {}
struct Elem {
// put stuff on Heap to make Elem fix sized at compile time
inner: Box<dyn Props>,
}
struct Builder {
// stuff
}
impl Builder {
fn build_it<T: Props + Default + 'static>(&self) -> Elem {
Elem::new(Box::new(T::default()))
}
}
impl Elem {
fn new(item: Box<dyn Props>) -> Self {
Elem { inner: item }
}
}
macro_rules! elems {
($builder: expr; $($t:ty),*) => {
vec![
$($builder.build_it::<$t>(),)*
]
};
}
fn foo() -> Vec<Elem> {
elems!(Builder {}; A, B, C)
}
If you want to know more about writing and understanding macros, I suggest reading the Macros chapter of the Rust Book.