Variadic tuples have definitely been discussed in the past, and even went through RFC, but the RFC was closed for lack of resources and deferred to be revisited at a later time. If we had variadic tuples as per that RFC, it seems like you could have your combine
function:
fn combine<(.. T)>(sources: (.. Source<T>)) -> Source<(.. T)> { todo!() }
But the usual way of working around the lack of variadic tuples is to be generic over tuples up to a certain reasonable length, using a macro, like itertools does (albeit it only deals with homogeneous tuples, but it should work similarly):
trait Combine {
type Output;
fn combine(self) -> Source<Self::Output>;
}
macro_rules! impl_combine {
($first:ident) => {};
($first:ident, $($T:ident),+) => (
impl_combine!($($T),+);
impl<$($T: Send + Sync + Clone + 'static),+> Combine for ($(Source<$T>),+,) {
type Output = ($($T),+,);
fn combine(self) -> Source<Self::Output> {
Callbag(Box::new(move |_message| {
let ($($T),+,) = &self;
todo!("combine")
}))
}
}
)
}
impl_combine!(dummy, T, U, V, W, X, Y, Z);
fn combine<T: Combine>(sources: T) -> Source<<T as Combine>::Output> {
sources.combine()
}
fn main() {
let source_a: Source<String> = Callbag(Box::new(|_| todo!()));
let source_b: Source<i64> = Callbag(Box::new(|_| todo!()));
let source_c: Source<Vec<u64>> = Callbag(Box::new(|_| todo!()));
let combined_sources: Source<(String, i64, Vec<u64>)> =
combine((source_a, source_b, source_c));
}
playground link
The exercise for you is obviously to replace the todo!("combine")
which may or may not be possible..
This definition of combine
obviously only works with a statically-known tuple input, but then again, that's the only way to have a statically-known output. If you want a heterogeneous tuple as input whose types and arity are only known at runtime, then it seems like any language isn't gonna be able to do better than Vec<Box<dyn Any>>
..