Skip to content
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

Tuple-Based Variadic Generics #1935

Closed
wants to merge 22 commits into from

Conversation

cramertj
Copy link
Member

@aturon aturon added the T-lang Relevant to the language team, which will review and decide on the RFC. label Feb 28, 2017
@withoutboats
Copy link
Contributor

withoutboats commented Feb 28, 2017

How would you actually write variadic functions under this proposal? Am I correct that you'd still have to write something like foo( (arg1, arg2, arg3) ), or would there be some kind of sugar?

I guess the unresolved questions covers this?

@ahicks92
Copy link

It is not a major deal breaker or anything, but it's worth noting that my field reordering stuff does currently affect tuples, and we probably want it to continue doing so where possible, I'd think.

Now if only the world would leave me alone for the like 2 days I need to finish my field reordering stuff, but that's another discussion.

@cramertj
Copy link
Member Author

cramertj commented Feb 28, 2017

@withoutboats You're correct, the current proposal would require something like foo((arg1, arg2, arg3)). However, as you noticed, the "unresolved questions" section mentions a possible extension to allow things like fn foo<Args: Tuple + MyTrait>(args: ...Args), which could be called like foo(arg1, arg2, arg3).

type AsRefs<'a>: Tuple + 'a;
type AsMuts<'a>: Tuple + 'a;
fn elements_as_refs<'a>(&'a self) -> Self::AsRefs<'a>;
fn elements_as_mut<'a>(&'a mut self) -> Self::AsMuts<'a>;
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer to avoid a hard dependency on ATC here, and I'm not sure the functionality needs to be in Tuple itself.
IOW, we can do this more transparently to the user with a recursive impl perhaps? Well, no, "compiler knows best", it could project AsRefs partially (e.g. (A, ...B, C)::AsRefs == (&A, ...B::AsRefs, &C)), but we can probably experiment with a recursive impl in a library, for a trait TupleAsRefs<'a>, to start with.

Copy link
Member

Choose a reason for hiding this comment

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

To be clear, it's likely we'll want to only stabilize the ATC form itself, for convenience's sake, but it's not needed to experiment IMO.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah-- I left a comment about this in the "unresolved questions" section. Separate traits (TupleAsRef<'a> and TupleAsMut<'b>) work and eliminate the dependency on ATCs, but I think it's slightly less clear to the end user.


## The `(Head, ...Tail)` Type Syntax
This syntax would allow for a `Cons`-cell-like representation of tuple types.
For example, `(A, ...(B, C))` would be equivalent to `(A, B, C)`. This allows
Copy link
Member

Choose a reason for hiding this comment

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

This is potentially misleading, as (A, ...(B, C), D) would also be valid. Any mention of "cons" is likely to confuse more than help, compared to, say, "tuple concatenation" (which is valid for both types and values). See #1921 (comment) and #1921 (comment).

Copy link
Member Author

Choose a reason for hiding this comment

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

I had actually purposefully avoided discussing things like (...(A, B,) C) (tuples in non-tail position). I wasn't sure if that was something we wanted to support in the initial implementation, but I think it makes sense to include them prior to stabilizing.

Copy link
Member

Choose a reason for hiding this comment

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

@sgrif thought too (#1921 (comment)) but I disagree, from the implementation side doing it right the first time is not much harder, and it's less work overall than having two incompatible implementations at different points in time.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good to know-- you have a much better sense of the actual implementation then I do. I'll edit the RFC appropriately.

Copy link
Member

@eddyb eddyb Feb 28, 2017

Choose a reason for hiding this comment

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

To be more clear: both tail-only and generalized single-repeat require the same primitive: a single check somewhere in the compiler that the T in ...T is now known to be a tuple (i.e. after substituting in type parameters, resolving associated types, etc.) therefore expand it.
And concatenating 3 lists of types is hardly any more difficult than concatenating 2 lists.
This is done only once, mind you (probably should be done when creating any tuple IMO).

On top of that, everywhere a tuple is introspected now has to take into account a partially unknown tuple, and that has the same cost for both approaches, except if you do it one way then later you have to redo in the other way, and there can even be subtle bugs introduced.

let (head, ...tail) = self;
let mapped_head = f(head);
let mapped_tail = tail.map(f);
(mapped_head, ...mapped_tail)
Copy link
Member

Choose a reason for hiding this comment

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

Might be a bit clearer with the last 3 lines flattened into (f(head), ...tail.map(f)).

```

This example is derived from
[a playground example by @eddyb]()
Copy link
Member

Choose a reason for hiding this comment

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

Missing link 😆.

-Should the `Tuple` trait use separate `TupleRef<'a>` and `TupleMut<'b>` traits
to avoid dependency on ATCs? It seems nicer to have them all together in one
trait, but it might not be worth the resulting feature-stacking mess.

Copy link
Member

Choose a reason for hiding this comment

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

Overlap with range syntax is a problem. I believe x... could always work if we decide it doesn't make sense as a range, over x.. (i.e. is "inclusive at infinity" useful?).

Copy link
Member

Choose a reason for hiding this comment

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

x... is how you expand a parameter pack in C++, so it'd be familiar to some, for better or worse.

lists, the concept should be introduced using "structural recursion": there's
a base case, `()`, and a recursive/inductive case: `(Head, ...Tail)`. Any tuple
can be thought of in this way
(for example, `(A, B, C)` is equivalent to `(A, ...(B, ...(C, ...())))`).
Copy link
Member

@eddyb eddyb Feb 28, 2017

Choose a reason for hiding this comment

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

@eddyb
Copy link
Member

eddyb commented Feb 28, 2017

@camlorn Right, and nothing in the RFC assumes field order matters, i.e. there are no "partial tail borrows" or anything like that, it can all be implemented in a library with safe code for a fixed number of arities.

@eddyb
Copy link
Member

eddyb commented Feb 28, 2017

fn foo<Args: Tuple + MyTrait>(args: ...Args)

MyTrait would presumably imply Tuple, especially if it's a helper specifically for foo.
I was thinking at some point that it makes sense to put ... on the pattern but I was wrong!

  • the signature is fn(...Args) which needs the ... for all external interactions
  • the pattern is matching against a tuple, e.g. these should be equivalent:
fn foo<H, T: Trait>((head, ...tail): ...(H, ...T)) {...}
fn foo<H, T: Trait>(head: H, tail: ...T) {...}
fn foo<H, T: Trait>(args: ...(H, ...T)) { let (head, ...tail) = args; ...}

There is also some unfortunate overlap between the proposed `(head, ...tail)`
syntax and the current inclusive range syntax. However, the similarity
between `start...end` and `...tail` can be disambiguiated by whether or not
there is an expression immediately before the ellipsis.
Copy link
Member

Choose a reason for hiding this comment

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

Sorry but ...x already is a valid inclusive range expression (it creates a std::ops::RangeToInclusive<Tail>). This code works today in nightly:

#![feature(inclusive_range_syntax)]
fn main() {
    println!("{:?}", (1, ...(2,)));
    // prints `(1, ...(2,))`
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, you're correct. @eddyb made the same observation above.

@cramertj
Copy link
Member Author

cramertj commented Jun 6, 2017

@aturon A few of the points I listed above would benefit from some lang team discussion:

  • Discussing the possibility of an RFC for structures with multiple DST elements, e.g. (str, str).
  • Possibly extending the negative reasoning around the Tuple trait. #[fundamental] isn't enough to allow a blanket impl for Tuples and a blanket impl for another trait. Perhaps a #[sealed] annotation would be appropriate to guarantee to the compiler that no Tuple implementations will be added in the future. However, even with #[sealed], the problem isn't solved. For example, consider implementing a trait for T: Tuple and T: Display. This can't ever be coherent, no matter if Tuple is represented by a trait, a concrete type, or a "type list"-- there's nothing to tell the compiler that an impl of Display for some T: Tuple won't be added at a later date, causing conflicts. I suspect that implementing a trait for all tuples and for all T: SomeTrait will require some degree of specialization / lattice impls no matter which design we pursue in this RFC.

I'll also check in @withoutboats again to see if we can come to some consensus WRT their preference for first-class "type lists."

@eddyb
Copy link
Member

eddyb commented Jun 6, 2017

@cramertj I don't recall where the T: Tuple and T: Trait overlap comes from.
Are there any examples of usecases that involve it? I thought you'd have traits implemented for tuples for certain kinds of recursion, and traits implemented for tuples and other types (so no blanket impl would be acceptable anyway - I could be wrong though).

@cramertj
Copy link
Member Author

cramertj commented Jun 6, 2017

@eddyb @sgrif expressed interest in it-- I'm imagining they have some use case in mind.

I'd expect the desire is related to implementing a function that takes either a single argument (of type T: SomeTrait) or a tuple of arguments, however if the future extensions to allow fn my_func(...args: ...Args) are accepted, then this could be represented as a function that takes one arguments or many arguments, and in the meantime single-argument calls to the function could use my_func((arg)).

Edit: I think @sgrif first mentioned this concern here.

@eddyb
Copy link
Member

eddyb commented Jun 6, 2017

It makes more sense IMO to go straight to variadic functions & methods than have multiple arguments manually packaged in tuples.

@cramertj
Copy link
Member Author

cramertj commented Jun 6, 2017

@eddyb Yes, I've been gradually drifting towards preferring an "all at once" approach myself. My initial goal was to keep this RFC as minimal as possible in the hopes that it would be merged sooner. However, in order to know what the right "first steps" are, you have to consider how they move the language towards a comprehensive solution.

@aturon
Copy link
Member

aturon commented Sep 6, 2017

We're clearly not going to get to this before the impl period, so I propose we formally postpone it and start a fresh thread when we're ready to consider it.

@rfcbot fcp postpone

@rfcbot
Copy link
Collaborator

rfcbot commented Sep 6, 2017

Team member @aturon has proposed to postpone this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added the proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. label Sep 6, 2017
@cramertj
Copy link
Member Author

cramertj commented Sep 6, 2017

Alas, what symmetry that my first ever ticked checkbox as a lang team member should be to postpone my first ever RFC. 😄

I'm excited to work more on this next year after we've had a chance to implement the host of wonderful RFCs currently in the queue.

@rfcbot
Copy link
Collaborator

rfcbot commented Sep 19, 2017

🔔 This is now entering its final comment period, as per the review above. 🔔

1 similar comment
@rfcbot
Copy link
Collaborator

rfcbot commented Sep 19, 2017

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Sep 19, 2017
@aturon
Copy link
Member

aturon commented Sep 29, 2017

I'm now closing this RFC as postponed. Thanks, @cramertj and everyone else for pushing further on this issue -- something we'll definitely want to come back to in the future!

@aturon aturon closed this Sep 29, 2017
sstangl added a commit to sstangl/openpowerlifting that referenced this pull request Oct 5, 2017
Unfortunately, we have 28 columns, which requires Diesel's "huge-tables"
support. This dramatically increases compile time of the Diesel
dependency to ~3-4 minutes, up from negligible.

Diesel's team says it's tracked by:
  - diesel-rs/diesel#747
  - rust-lang/rfcs#1921
  - rust-lang/rfcs#1935
@alexreg
Copy link

alexreg commented May 5, 2018

Has there been any interest in reviving this lately?

@scottmcm
Copy link
Member

scottmcm commented May 6, 2018

@alexreg There's still general interest in the feature, but it's not the kind of change that's targeted for the roadmap this year, so there's probably nothing going on with it right now. (Doing it and const generics could be an interesting thing to consider for next year's roadmap, once we're past the stabilize year.)

@alexreg
Copy link

alexreg commented May 6, 2018

@scottmcm Fair enough. w.r.t. Const generics, I thought they were targeted for this year?

@est31
Copy link
Member

est31 commented May 6, 2018

@alexreg const generics has an RFC merged but implementing it is no official priority of the compiler team. All of the statements were of the form "maybe we can have const generics in nightly by end of 2018"...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. postponed RFCs that have been postponed and may be revisited at a later time. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.