Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
211 lines (172 sloc) 7.54 KB
  • 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 bind method, 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

Unresolved questions

You can’t perform that action at this time.