- Feature Name: (fill me in with a unique ident, my_awesome_feature)
- Start Date: (fill me in with today's date, YYYY-MM-DD)
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Summary
This RFC adds variadic generics using tuples.
Motivation
- fn types need a reform, and being able to define a trait with a variable number of type parameters would help
- working with functions which have a variable number of arguments is impossible right now (e.g. a generic
bindmethod,f.bind(a, b)(c) == f(a, b, c)) and defining such functions may only be done (in a limited fashion) with macros - tuples also have similar restrictions right now, there is no way to define a function which takes any tuple and returns the first element, for example
Detailed design
Syntax
Note: The following syntax is not formally part of this RFC, but will be used to define the syntax!
---patterns represent continuing patterns, eg.(T1, T2, ---, T5)means(T1, T2, T3, T4, T5).- strings in double quotes (
"type") represent placeholders. - strings in single quotes (
'opt') represent optional elements.
Tuple Types
This RFC adds abstract-tuple-types;
These types are traits or at least behave like them, as you can use them as trait bounds and they are unsized.
abstract-tuple-types are types of tuples, which impose conditions to all tuple elements.
A tuple-type is a either an abstract-tuple-type or any type matching (T1, ---, Tn).
It is forbidden to implement an abstract-tuple-type on any type manually.
Syntax of an abstract-tuple-type: ("type-expression";T1': "type_1"', ---, Tn': "type_n"' 'where "condition_1", ---, "condition_m"')
type-expression, condition_is, and type_is may contain the types T1, ---, Tn.
Semantics
A non-abstract tuple-type (S1, ---, Sm) is subtype of the abstract-tuple-type ("type-expression";T1': "type_1"', ---, Tn': "type_n"' 'where "condition_1", ---, "condition_m"')
iff for every tuple member-type Si, there exist types T1, to Tn,
which satify all conditions (condition_1 to condition_m),
so that Si matches the type-expression, where T1 to Tn are inserted into type-expression accordingly.
Examples
- tuples which only contain
u32:(u32;) - tuples where all members are
Clone:(T;T: Clone) - any tuples:
(T;T) - any tuples, which only contain elements, which are pairs of addable types:
((T,U); T: std::ops::Add<U>, U)
The unfold syntax
This RFC adds the ...-syntax for destructuring tuples.
Intuitively the ...(a, b, ---, z) syntax removes the parens of the tuple,
and therefore returns a comma-separated list a, b, ---, z.
This is just allowed in very specific contexts as defined below.
The unfold syntax for tuple values
in Function Calls
fn addition(x: u32, y: u32) { x + y }
let t = (1, 2);
let result = addition(...t);
assert_eq!(result, 3);in Arrays
let a = [1, ...(2, 3, 4)];
assert_eq!(a, [1, 2, 3, 4]);in Tuples
let a = (1, ...(2, 3, 4));
assert_eq!(a, (1, 2, 3, 4));Destructuring
let x = (1u32, 2u32, 3u32);
let (a, ...b) = x;
let (ref c, ref ...d) = x;
assert_eq!(a, 1u32);
assert_eq!(b, (2u32, 3u32));
assert_eq!(c, &1u32);
assert_eq!(d, (&2u32, &3u32));The unfold syntax for tuple types
Analogous to the unfold syntax for tuple values, there is also such a syntax for tuple types.
The ... syntax is only applicable for those types, which are Tuple Types by definition above.
This is also just allowed in very specific contexts as defined below.
in Tuple Types
type Family32 = (u32, ...(f32, i32));in Type Parameters
foo<...(u32, u32)>();
type A = HashMap<...(String, bool)>;Variable Number of Arguments
The ...-Syntax used as prefix of a (function / generic) parameter creates
- functions with a variable amount of parameters, and
- generics with a variable amount of generic parameters
by folding this variable amount of parameters into one tuple paramter of variable size, which in turn may be processed using....
in Type Parameters in definitions
fn foo<...T>() { /* code */ }In this context, if foo<A, B, C, D>() is called, the type T would be equal to the tuple type (A, B, C, D).
foo can also be called with one (named U) or zero type arguments, this would cause T to be a (U,) or () respectively.
Every type parameter of the form ...T implicitly gets the trait bound (X;X), and therefore you can call ...T onto it.
The ...T syntax is also allowed in combination with other generic parameters:
fn bar<T, ...U>() { /* code */ }But it is important, that every ...T type parameter is the last type parameter.
Calling bar<A, B, C, D>() would mean T = A and U = (B, C, D).
Asterisk type parameters can not only be used for functions but anywhere, where normal type parameters are allowed.
on Function Parameters
In addition to this, you can use the ...-syntax on function parameters.
Consider a function with an argument ...arg: (A, B, C, D): this causes the function to accept 4 distinct arguments of type A, B, C and D.
These are internally put together into the quadruple arg, when the function is called.
fn addition(...arg: (u32, u32)) -> u32 {
arg.0 + arg.1
}or using ...-syntax again:
fn addition(...arg: (u32, u32)) -> u32 {
[...arg].iter().sum()
}An argument prefixed by ... has to be the last function argument.
These addition functions are equivalent to the definition of addition above.
The ... syntax can also be used in lambda-expressions, and can also be combined with mut: |mut ...arg| { /* code */ }.
Examples
use std::fmt::Display;
// addition
fn u32_addition<T: (u32;)>(...arg: T) -> u32 {
[...arg].iter().sum()
}
fn any_addition<T: std::ops::Add<T, Output=T>, U: (T;)>(...arg: U) -> T {
[...arg].iter().sum()
}
// tuple
fn tuple<T>(...arg: T) -> T { arg }
fn tuplezip<T: (X;X), U: (X;X)>(t: T, u: U) -> (...T, ...U) {
(...t, ...u)
}
// display tuple
trait DisplayTuple {
fn printall(&self);
}
impl DisplayTuple for () {
fn printall(&self) {}
}
impl<T, U> DisplayTuple for (T, ...U)
where T: Display, U: DisplayTuple {
fn printall(&self) {
let (a, ...b) = self;
println!("{}", a);
b.printall();
}
}
// bind
fn bind<T, F: Fn(T, ...U), ...U>(f: F, t: T) -> impl Fn(...U) {
|...u: U| f(t, ...u)
}
fn main() {
assert_eq!((true, false), tuple(true, false));
(2i32, 3u32).printall();
}Drawbacks
This RFC adds the ... syntax on tuples and therefore adds complexity to the language.
Alternatives
- don't add this or anything similar
Prior art
C++11 has powerful variadic templates, yet these have some drawbacks:
- Tedious syntax:
template <typename ...Ts> void foo(Ts ...args) { ... } - You can't easily impose conditions onto types like
T: Clone