[Analysis / Pre-RFC] Variadic generics in Rust

So far I have seen so many parenthesis but why do we need them?

Why fn f<...T>(t: (...T)) rather than fn f<...T>(t: ...T)?

I wonder what would happen if the different types have different lifetimes? How do we do 'a, 'b: 'a, 'c: 'b...?

1 Like

In @PoignardAzur's write-up they mean different things:

fn f1<...T>(t: ...T) {...} // N arguments
fn f2<...T>(t: (...T)) {...} // 1 argument which is a tuple

I personally prefer to spell it

fn f1<...T>(...t : ...T) {...} // N arguments
fn f2<...T>(t : (...T)) {...} // 1 argument which is a tuple

It seems lifetime specifications for variardic fn-s needs to be less flexible.. However could you please give an example of a function with complex lifetime parameters which you would like to be able to write variardically?

// btw under the suggestions made so far it is fully expected
fn f<...T>(...) {...}
// can be invoked as
f::<Vec<&'a u8>, Vec<&'b u8>>(...);
1 Like

Seemed confusing to me at first for the 1 argument which is a tuple. So if we have that means we could have it for array as well right?

fn f2<...T, N>(t : [...T; N]) {...} // 1 argument which is an array

It would be good if the RFC could put the comment like this to make it clearer.

Like the example I have given, the lifetime is getting smaller and smaller, with the first argument being the largest lifetime but the last argument being the smallest lifetime.

That doesn't make sense, because arrays are homogenous: They can only contain a single type.

4 Likes

I'm actually not aware of anybody working on an RFC right now.

If really needed could be expressed via some built-in where condition I suppose:

fn f_Ref<'a, T> -> & 'a T;
fn f<(...'a,) (...T)> f(...t : ...zip<...'a, ...T, f_Ref>) -> ...
    where ...progressively_smaller<...'a> {...}
// invoked as
f::<('a, 'b), (i32, str)>(...)

To be completely clear, I didn't actually write a RFC.

What I wrote was more of a toolbox, a list of examples and use-cases and design principles that an actual RFC could build upon.

I would be really surprised if there was a legitimate real-world use case where that was the only practical solution.

In general, the idea with variadic types is that they are parallel; as in, the types don't depend on each other.

This is because most variadic use-cases are for heterogenous collections, collections where every item will be treated the same way, but whose type still needs to be statically known.

3 Likes

On syntax:

wouldn't it be interesting to always treat ...t/...T
as applications of splattering operator
to a tuple/tuple type?
which can also occur in pattern position

fn f1<...T>(t : T) {...}
// when invoking f1 as
f1::<u8, i8, &str>((1, 2, "a"));
// T becomes (u8, i8, &str) tuple type
// these two are equivalent
fn f2<(...T)>(...t : ...T) // this says T is a tuple type rather explicitly
fn f2<T>(...t : ...T) // this adds T : tuple constraint more implicitly
// in either case the way to use f2 is
f2::<(&str, &str)>("abc", "def");
// Update: these two are equivalent as well
fn f21<...T>(t : T) // 1 arg which is a tuple
fn f21<...T>(t : (...T)) // same tuple, we splutter and wrap in a tuple again
fn f3<...T>(...t : ...T) {
    some_fn_that_takes_a_variardic_tuple(t); // ok
}
fn f4<...T>(t : T) {
    some_fn_that_takes_a_variardic_sequence_of_values(...t); // ok
}
fn f4<...T>(a : i32, b : &str, ...t : ...T) {..} // ok

This would means ...zip() is no longer a good name for a built-in fn operating on both tuple types and tuple values. It'd need some other name.. "masquerade" it as a macro?

fn t_Pair<A, B> -> (A, B);
fn f_Pair<A, B>(a : A, b : B) {
    (a, b)
}
fn f5<(...A), (...B)>(a : A, b : B) -> zip!<A, B, t_Pair> {
    zip!(a, b, f_Pair)
}
This built-in macro would be special in that the compiler would know what it does and the errors would be "pre-application" so to say. It'd be treated more like a real function for type-checking and error reporting but it would be spelled as a macro.

Or else some other naming style would be needed that would clearly differentiate it from regular functions..

Why does half your post have a smaller font size? That's kind of odd.

smaller is quieter, almost like whispering

5 Likes

What about Fn traits? Will I be able to write something like this?

fn blah<...Fs: Future>(impl Fn (futures: ...Fs)) {
 // ...
}

In that exact form, probably not, because functions with both existential arguments and explicit template parameters are forbidden (though I'm not sure that'll always be the case).

In general, yeah, that would probably be a desirable use case, though probably not for a MVP.

Nope, you can declare such a function, you just can't specify any of its generic parameters at the callsite.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.