This is a proposal for Variadic type parameters, that is sufficient to make a clean API for the Fn* traits that we could stabilize. It does not create a complete system for general variadic arguments, although I believe it would be forwards compatible with such a system.
To give a concrete example first, the definition of the FnOnce
trait would look like this:
pub trait FnOnce<Args: ...> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
and an implementation of FnOnce
might look like:
impl FnOnce<i32, String, bool> for MyCallable {
type Output = Result<(), Error>;
extern "rust-call" fn call_once(self, a: i32, s: String, b: bool) -> Self::Output {
...
}
}
Details
The special token “…” will be allowed as a type bound on generic type parameter, such a type parameter will be referred to as a variadic type parameter. The “…” bound cannot be combined with other bounds with “+”. A generic item must have at most one variadic type parameter, and if it does have one, it must be the last type parameter.
If a type parameter T is “…”, then that type parameter is only allowed in the following positions:
- The type of the last argument in a function, method, or closure definition
- As the last type in a tuple type (ex:
(i32, u32, T)
) - As the last type in the definition of a tuple like struct (ex:
struct MyStruct<T: ...>(i32, u32, T)
) - As the last type parameter to another generic item which accepts a variadic type parameter
In particular, note that you cannot use a variadic type parameter as the type for a field or associated const, as an associated type, or as a return type, although you could use a tuple composed of the types in T in these places by using (T)
.
When a generic item with a variadic type parameter T is used, rather than having to use exactly one type to substitute into T, T can be replaced with zero or more type parameters separated by commas. Any place where the variadic type parameter was used then expands to those types. If implementing a generic trait, and the variadic type parameter is used as the last argument to a method, the implementation of that method must replace that argument with n arguments, where n is the number of types in T and the types of those arguments match the types that T is replaced with (in order).
If an argument whose type is a variadic type parameter is passed to a function (or function-like object) it is equivalent to passing a list of arguments of the types determined by the variadic type parameter.
Examples
struct TaggedTuple<T: ...>(u32, T);
fn test() {
let x = TaggedTuple(0, "hello", 23, 5.4);
assert!(x.0 == 0);
assert!(x.1 == "hello");
assert!(x.2 == 23);
assert!(x.3 == 5.4);
}
trait Len {
fn len() -> usize;
}
impl<Head, Tail: ...> Len for (Head, Tail) {
fn len() -> usize {
1 + (Tail)::len()
}
}
impl Len for () {
fn len() -> usize {
0
}
}
fn partial_apply<T, F, Args: ...>(f: F, first: T) -> impl FnOnce<Args> where F: FnOnce<T, Args> {
|args: Args| {
f(first, args)
}
}
Alternatives
- The first pass could leave out allowing variadic type parameters in tuple and tuple-like-struct types. I think that allowing that adds quite a bit of value for not very much cost though.
- Different syntax for specifying the variadic type parameter
- wait for a more complete variadics solution
- stabilize Fn* traits as is
- https://github.com/rust-lang/rfcs/issues/376
Extensions
An important extension would be to allow combining the “…” contstraint with other type constraints (ex: allow something like ... + Clone
to specify a variadic type argument where all types passed must implement Clone
).
Another extension would be a way to deconstruct a variadic argument to a function. I’m not entirely sure what this would look like, but would probably involve some kind of compile time pattern matching, or maybe specialization.
Finally, eventually it may be desirable to have some kind of splat operation to pass a tuple as a variadic argument that is expanded.