Pre-RFC: variadic tuple

None of those are current syntax. The current syntax is

let [a, b @ .., c] = [1, 2, 3, 4];

And as far as I know this kind of syntax has only ever been usable on slices because of an issue with the layout of subtuples.

Link to the relevant RFC, specifically to a comment I think is particularly relevant to this discussion.


For bundling multiple tuples of the same length I've played with using @@ to invert the top two layers

( ((1,2), (3,4), (5,6)) @@..) == ((1,3,5), (2,4,6))

But it's not something I've fleshed out.

1 Like

Is .. a prefix of the type variable (like variable types in Perl), or a rest/spread operator?

To me it'd make sense as an operator, i.e.:

fn my_func<T>((..v): (..T)) -> T

T is a variable tuple (contains (T1, T2, T3, etc.)), and .. takes T and spreads it into its components.

If .. was used in type position, I'd expect it to capture multiple types:

HashMap<..T>
T == (K, V)

Thanks for the link, this is indeed relevant and useful for this RFC!

I guess I should use the syntax ident @ .. to be consistent.


This depends on the context actually, hence is confusing. I'll use the @ syntax as the link above to describe the difference.

For types, it is a rest/spread operator:

impl<(..(K, V))> MegaMap<(..(K, V))>
//   ^^^^^^^^^^ rest     ^^^^^^^^^^ spread
where ..(K: Hash), {
//   ^^^^^^^^^^^^ spread
    fn get(&self, k: (..K)) -> (..Option<V>) {
//                   ^^^^^ and ^^^^^^^^^^^^^ spread
    }
}

for destructuring, it is more appropriate to have bindings:

{
  let source: (Head, ..Tail) = _;
  // `head` is a variable of type `Head`
  // `tail` is a tuple variable of type `(..&Tail)`
  let (ref head, ref tail @ ..) = source;
}
{
  let mut source: (..L, ..R) = _;
  // `l` is a tuple variable of type `(..&mut L)`
  // `r` is a tuple variable of type `(..&mut R)`
  let (ref mut l @ .., ref mut r @ ..)) = source;
}

Also, this points out that there is an issue in the 2nd pattern: .. is used twice which is not possible. Also, it is not explicit how the compiler should "split" the destructuration of both tuples.

In for loops, it is none. It only indicates you are not iterating with a standard for loop. (Which is currently a pretty bad syntax and need to be changed).

Then I think it should not be an operator, because:

// This function accept all generic types
// and `(..T)` tries to rest the members of T, but we don't know anything about T
fn my_func<T>((..v): (..T)) -> T

// This function only accept tuple types as generic arguments
fn my_func<(..T)>((..v): (..T)) -> (..T)
1 Like

I have updated the RFC with the @ pattern for destructuring. Also added an entry in the "Unresolved questions" section concerning the usage of multiple .. in a single pattern.

Can you elaborate? I did not find precise documentation about this. (except this pre-RFC)

I don't know how it works, but variadic tuples will likely be implemented in the same way as generics, by monomorphozing on each distinct tuple, so handling it in the same way as generics (whatever that is), should be fine.

1 Like

(..T) tries to rest the members of T, but we don't know anything about T

Good point. How about:

fn my_func<T: Variadic>((..v): (..T)) -> T

There would a "magic" trait std::yadayada::Variadic implemented by all tuples, so then we know .. operator can be applied to a tuple (and maybe fixed-size arrays could implement it too, and be spreadable as well?)

1 Like

In that case fn my_func<T: Variadic>((..v): (..T)) -> T and fn my_func<(..T)>((..v): (..T)) -> T are equivalent. I my opinion using (..T) is easier to use.

I feel this is a rabbit hole, If I have to mix tuples and arrays I prefer to have a quite explicit syntax. (So not relying on a trait that both arrays and tuple implements)

To sum up briefly the state of the discussion:

  • The destructure syntax was changed to the syntax used for slice patterns and subslice. (so <ident> @ ..).

  • The for loop syntax to iterate over the members of a variadic tuple is ambiguous with the runtime for loop:

// here, k and maps are variadic tuples
(for (ref k, map) in (k, maps) {
    maps.get(k)
})

See these suggestions as alternatives

I also want to add a new point on the discussion about the .. token in patterns: Its semantic is ambiguous, sometimes it means "catch everything you can" and sometimes it means "catch this explicit variadic tuple". Example:

// Existing destructure pattern
// a = 1, .. = (2, 3), b = 4
// It has the meaning "catch everything you can at this position"
let (a, .., b) = (1, 2, 3, 4);


let variadic_tuple: (Head, ..Tail) = ...;
// Close to existing destructure pattern
// h: Head, and tail: (..Tail)
// The meaning is both:
//   - Catch everything you can at this position
//   - Catch the variadic tuple (..Tail)
let (h, tail @ ..) = variadic_tuple;

// And here comes trouble: 
// If we want to "split" a tuple:
let source: (..L, ..R) = ...;
// A lot of bindings are possible
let (l @ .., r @ ..) = source;

// So maybe we need to explicitly state that we want to match a variadic tuple
// Type ascription may help here:
let (l: (..L) @ .., r: (..R) @ ..) = source;

What's wrong with body == ()?

You are right, I remove the first issue which is not an issue ><

I'm not sure about this. There's a lot of ..s in the code, and they have multiple meanings. Rust already has that kind of asymmetry for & in patterns, where it dereferences instead of referencing, and it turned out to be difficult to learn, and eventually got de-facto removed with match ergonomics.

T: AutoTrait is already an existing pattern, and it's googlable.

I think there are several points here:

Yes, multiple meaning is not good from a UX standpoint. First version of the RFC used .. as prefix to rest and as postfix to expand. Maybe this is something we can redo to solve this ambiguity.

I'd like to have only a single syntax to declare the variadic tuple, and the issue about using a trait bound is that you can't group the declaration of variadic tuples to enforce that they have the same arity. (Like in (..(L, R))). Otherwise, it could be a viable option. For this reason, I still prefer the (..T) syntax.

Considering the for loop syntax, I would be in favor of one of these syntaxes:

// Option 1 - Enclosed in brackets

// Rationale: <> Is like a generic parameter, so a parameter that 
//    is evaluated at compile time

// Iterate over tuple and type
(for (ref k, map) type (Key, Value) in <k, maps> type <K, V> {
    HashMap::<Key, Value>::get(&map, k)
})

(for ref key type Key in <k> type <K> {
    Key::do_something(key)
})

// Iterate over tuple
(for (ref k, map) in <k, maps> {
    map.get(k)
})

(for ref key in <k> {
   key.do_something()
})

// Iterate over type
(for type (Key, Value) in type <K, V> {
    (Key::zero(), Value::one())
})

(for type Key in type <K> {
   Key::one()
})
// Option 2 - Prefixed by @

// Rationale: @ binds to a set of value, 
// the type of the value is determined at compile time

// Iterate over tuple and type
(for (ref k, map) type (Key, Value) in @(k, maps) type @(K, V) {
    HashMap::<Key, Value>::get(&map, k)
})

(for ref key type Key in @k type @K {
    Key::do_something(key)
})

// Iterate over tuple
(for (ref k, map) in @(k, maps) {
    map.get(k)
})

(for ref key in @k {
   key.do_something()
})

// Iterate over type
(for type (Key, Value) in type @(K, V) {
    (Key::zero(), Value::one())
})

(for type Key in type @K {
   Key::one()
})
// Option 2 - Replace `in` by `@` and use `<>` for types

// Rationale: @ binds to a set of value and `<>` designates types variables

// Iterate over tuple and type
(for (ref k, map) <Key, Value> @ (k, maps) <K, V> {
    HashMap::<Key, Value>::get(&map, k)
})

(for ref key <Key> @ k <K> {
    Key::do_something(key)
})

// Iterate over tuple
(for (ref k, map) @ (k, maps) {
    map.get(k)
})

(for ref key @ k {
   key.do_something()
})

// Iterate over type
(for <Key, Value> @ <K, V> {
    (Key::zero(), Value::one())
})

(for <Key> @ <K> {
   Key::one()
})
1 Like

I find it confusing to remove the in keyword, what about:

// Rationale: @ binds to a set of value and `<>` designates types variables

// Iterate over tuple and type
for (ref k, map) <Key, Value> @in (k, maps) <K, V> {
    HashMap::<Key, Value>::get(&map, k)
}

for ref key <Key> @in k <K> {
    Key::do_something(key)
}

// Iterate over tuple
for (ref k, map) @in (k, maps) {
    map.get(k)
}

for ref key @in k {
   key.do_something()
}

// Iterate over type
for <Key, Value> @in <K, V> {
    (Key::zero(), Value::one())
}

for <Key> @in <K> {
   Key::one()
}

For the examples with @, the outer parentheses are unnecessary. Because @ isn't used anywhere instead of pattern matching, I don't think that it is ambiguous.

Do you mean in

(for (ref k, map) @in (k, maps) {
    map.get(k)
})

having something like

(for (ref k, map) @in k, maps {
    map.get(k)
})

I think it is ok as long as you don't have type arguments. (like:

(for (ref k, map) <Key, Value> @in (k, maps) <K, V> {
    HashMap::<Key, Value>::get(&map, k)
})

)

So it could be a valid simplified syntax in that case.

So I think the last syntax is nice as it is clear about what you are iterating on and not being to ambiguous or different from existing syntax.

I re-post the syntax here:

// Rationale: @ binds to a set of value and `<>` designates types variables

// Iterate over tuple and type
for (ref k, map) <Key, Value> @in (k, maps) <K, V> {
    HashMap::<Key, Value>::get(&map, k)
}

for ref key <Key> @in k <K> {
    Key::do_something(key)
}

// Iterate over tuple
for (ref k, map) @in (k, maps) {
    map.get(k)
}

for ref key @in k {
   key.do_something()
}

// Iterate over type
for <Key, Value> @in <K, V> {
    (Key::zero(), Value::one())
}

for <Key> @in <K> {
   Key::one()
}

I'll add in future possibilities the case where we can omit the parentheses

I meant instead of

(for (ref k, map) @in (k, maps) {
    map.get(k)
})
// do this
for (ref k, map) @in (k, maps) {
    map.get(k)
}

You are right, the parenthesis are unnecessary when having a single for loop. I update the RFC and the posts

A PR is open now https://github.com/rust-lang/rfcs/pull/2775