I have heard that there is a general sentiment that rust doesn't need or shouldn't have variadic functions because the builder pattern is so good. While I must admit that the builder pattern is very good, I still think that there are some use cases for variadic functions.
A variadic function can be called with 0 or more arguments. The type is some type that implements Iterator<item = T> for a declaration ...T.
A combination of individual and slice arguments are allowed. They will be iterated over in left to right order for each item. Effectively like iter::once().concat(iter::once()).concat(slice.into_iter()).
Traits:
Such functions would be allowed in traits but would not have to be specified as a type parameter.
Alternatives:
Do nothing, we can currently specify something very close to this in another way: ie the builder pattern.
Do not allow the joining of both individual items and other types of iterators. This is a smaller feature but I think it doesn't have enough weight behind it to be worth the effort.
Variadic functions in a single type is not the big issue. We can model that with slices, or if we need owned values, we can use iterators. If const generics lands, we could also use arrays.
Its generic variadics that needs solving. Where each element in the variadic can be a different type.
I'm not sure what you're getting at with this question, but I guess the answer is "both"? AFAIK all useful variadic generic code would eventually need a trait bound to show up somewhere, but for the feature to scale to non-trivial examples there's also plenty of intermediate steps that would need to work on literally any types, like turning T1, T2, ... into an N-tuple and then later taking the Xth element of that tuple.
I would think the "printf" type of problem is best/easily solved by using a FormatMe trait and passing an array/slice/etc dyn FormatMe items. Something like:
Or perhaps it would need to be Box<FormatMe>[] or some-such. Is there something besides this sort of thing where something else would be needed/appropriate?
EDIT: Changed hypothetical trait name to ensure it is understood as not something that already exists necessarily.
EDIT 2: The reason I believe something like this is sufficient, is that, it is highly run-time dependent and so, I would think, that allocation overhead is probably not high on the list due to the inherent inefficiency.
I guess if, you wanted compile-time expansion and dispatch based on the compile-time types, then you'd be looking at something like the Generic/Variadic Tuples proposal RFC
How would the compiler be able to check the format string at compile time if it was a regular function argument (as opposed to the current situation, which is that it's passed to a compiler-defined macro)?
What does “using only concrete types” mean in this context? Any use of generics (variadic or otherwise) must provide concrete types somehow.
Note that we’ve always had variadic macros like vec! and print!, so it’s hard to see what subset of variadic generics could provide any new functionality. In fact, AFAIK the only significant benefit of “proper” variadic generics over macros is additional type safety, so there’s not even much design space to look for subsets in.
Also, the moment you do any kind of runtime indirection like boxing, you’re erasing types and are back to things we already have today like arrays/slices of trait objects.
I mean that the type in the function signature would only be able to be a concrete type.
What I meant by boxing was that even with the above restriction, it would still be possible to use type objects using Box<dyn T>.
I guess there thus another possible alternative: a macro variadic!. Which concerts its arguments into a chain of iterators with the ... meaning use "iter" instead of "once" .
The "uniform variadic" case is handled almost entirely by for<N: usize> [T; N]. (Sure, const generics are a ways off, but so would a variadics implementation.) For a literal list of arguments, this is pretty much how the variadic would be implemented.
The part that isn't covered by taking an array is the spread operator. And those are served by taking a impl [Into]Iterator<Item = T> with a chain! or list! macro to sugar the chaining.
(I assume the presence of [T; N]: IntoIterator<Item=T>.)
In all cases where a variadic function goes into what is basically an iterator there is no need to make this part of the function call, just a syntactic sugar for constructing another iterator
iter![1,2,3,rest...]
// where rest is IntoIterator perhaps.
// or even
iter![1,2,3,it1...,12,13,it2...,somevar]
I realize creating a macro that works over the second one would have a fair few challenges.
Also, there's a lot of things you can do in eg D that don't work with a simple trait solver. Eg writing react-style DOM components for a web framework, with the components represented as structs:
I'm not saying the pseudocode above is good or desirable.
But it's expressive, in a way that can't be done with typeless macros and trait arithmetic alone.
EDIT: Actually, I'm realizing the Yew framework does exactly that, so this is a bad example. Not sure what a better example of first-class types used for actual production work would look like.
While de sugaring to Iterators makes sense in principle, using iterators often enough involves heap allocation, and it would be a real waste to have to allocate just to use variadic arguments.
I'm not sure this idea would ultimately work, but since the number of variadic items and their types are known at compile time, then in principle they can be allocated much like any other argument to regular fns.
One place where that gets complicated is that there are multiple calling conventions, and any fn variadics feature that gets implemented has to work with all of them.
What do you mean by "iterators often enough uses heap allocation". I don't see any Box<T> in any of std::iter::Once, std::iter::Empty, std::iter::Chain, or the IntoIterator implementations for []. Since, I think that these would be the most common building steps I don't see where you claim comes from.
It is sort of generic, but the benefit of using iterators over a slice/vec solution is that there is no need to copy the elements to the vec just for the function (which it probably worse than a heap allocation anyway).