Skip to content

Add draft of variadic notes #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 16, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions src/design_notes/variadic_generics_design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@

# Variadic Generics

A draft for Variadic Generics has existed since ~2013, and there have been multiple postponed RFCs surrounding
the topic. Despite this, the difficulty of designing such a system as well as lack of singular best choice
has led to no resolution. This document has been written as an attempt to 'sum up' proposals and discussion in
this space.

# Proposals so far

Following will be an overview of each of the proposals so far, followed by their individual pros and cons

## EddyB's Draft

This is a simple proposal, and the oldest one. It proposes the basic idea that all variadic types can
be seen as just a tuple with N elements. The syntax is thus built around allowing expansions of tuple
types into their components, and taking multiple types that will be condensed into a tuple.

For using these types, it is proposed that one destructure the tuple, generally into a single head, and the remaining
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eddyb also has previously expressed a preference for symmetric VG-- that is, VG, which would allow pulling out the first or last element, rather than fixing the choice of left->right traversal or right->left traversal. I don't know how important I personally feel this is, but it's worth noting given that this was the subject of quite a bit of discussion in previous proposals.

tail. This matches the recursive style used in C++. There is also some desire to
be able to iterate in both directions (from left or right) rather than fixing
the choice to a single direction.

### Pros

- Simple. Doesn't add much syntax while allowing the driving use case, the `Fn` traits and similar designs.

- Allows intuitive function call ergonomics

### Cons

- Mentioned by eddyb, a subtuple type could have different padding than its parent.
EG: `&(A, B, C, D) != &A, &(B, C, D)`

- The `..` syntax is already used in ranges, so some other syntax would be needed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have ...T syntax (three dots) available now, since inclusive ranges now use ..=.

- Note that `...` syntax may be available, as `..=` is now the inclusive
range syntax.

### Syntax

```rust
type Tuple<..T> = T;

(..(true, false), ..(1, "foo")) == (true, false, 1, "foo")

fn bar(..x: (A, B, C)) {}
```

## Cramertj's Proposal

Proposes both a syntax similar to C++ as well as a Tuple trait that will be implemented by all tuples.
The trait would contain helpful types and methods for working with variadic tuples:
- AsRefs type, `(A, B, C) -> (&A, &B, &C)`
- AsMuts type, `(A, B, C) -> (&mut A, &mut B, &mut C)`
- elements_as_refs fn, with signature `(&'a self) -> Self::AsRefs<'a>`
- elements_as_mut fn, with signature `(&'a mut self) -> Self::AsMuts<'a>`

- Not provided, but proposed as future extensions:
- Allowing unpacking tuple types in an argument position, preventing the need to call variadic functions like
`foo((1, 2.0, "3"))`
- Allowing the `...` syntax in generic type position, preventing the need to write traits such as `Fn` like
`Fn<(T1, T2, T3)>`

### Pros

- Proposes simple syntax, similar to eddyb proposal

- Includes Tuple trait, which is helpful for working with variadic arguments

### Cons

- None yet.

### Syntax

```rust
trait MyTrait: Tuple {}

impl MyTrait for () {}

impl<Head, Tail> MyTrait for (Head, ...Tail) where Tail: Tuple {}

fn foo<T: MyTrait>(args: T) {}
```

## Fredpointzero's Proposal

Takes a similar path to previous proposals in terms of syntax, but adds on a lot of ergonomics syntax.
This additional syntax is proposed to allow easier usage of the variadic types, allowing the user to generate
more imperative loops over the variadic type. A lot of the debate on this proposal surrounded the proposed for
loop syntax, as many found it hard to read/comprehend at a glance compared to the rest of the proposed syntax.

### Pros

- Ergonomics, this proposal allows more linear usage of variadic types, which the other proposals lack.

- Power, this proposal allows almost full control over the bounds on variadics, and provides many example
implementations.

### Cons

- Complexity, much more new syntax is needed than in the other proposals

- No way to terminate recursive functions currently, they just won't compile

### Syntax

```
struct Foo<(..T)>
where
..(T: Debug)
{
items: (..T)
}

fn append<(..L), (..R)>(l: (..L), r: (..R)) -> (..L, ..R) {
todo!()
}

fn foo<(..T)>(args: (..T)) {
let (head, tail @ ..) = args;

(for arg <ARG> @in args <T> {
Vec::<Arg>::new();
})
}
```

## Common notes

All the proposals start with a similar syntax, using two or three dots to represent packing/unpacking types.
They all act on tuples, extending language syntax to allow tuples of varrying instead of constant arity, and building
all new functionality on top of that idea.

The overlap with range is a common topic of discussion, but all full proposals seem to still use that syntax.
Overall, the basic syntax seems to be agreed on, but usability and 'extra' ergonomic functionality still requires
a lot of work.

## Tuple layout

Another important consideration for these proposals, particularly those resting
on tuples, is the layout of the tuple. We do not currently guarantee any
particular ordering of the fields in a tuple, which can limit our ability to
"subset" the tuple under a reference. See [this
comment](https://github.com/rust-lang/lang-team/pull/76#issuecomment-857206830)
for some discussion.