Variadic generics - pre-RFC


#1

I’d love get some feedback on my pre-RFC for variadic generics. :slight_smile:

This RFC is highly inspired by the draft made by @eddyb (https://github.com/rust-lang/rfcs/issues/376).


#2

I had to read it a couple of times to ensure I was getting all the details straight, but, overall it seems like a solid way to handle variadics. I think the syntax is reasonable, but, the “…” syntax might conflict with existing use of “…” as range either actually or at least cognitively.


#3

For the purposes of facilitating initial review, I would suggest creating a PR against your on repository; I’ll leave some line comments when you do that. :slight_smile:


#4

I really like this approach.

I think you could use some more examples of the fn foo<*T> type, for example:

Would you be able to do fn foo<*T: Debug>(foo: T) { ... } to specify that tuple T is Debug (and by extension, all elements of the tuple are Debug)? So you could, say, iterate over the tuple and debug-print out each one separately?

What I’d really like to use this for is type parameterizing with variable sized tuples. So, with the above syntax, fn my_struct_of_tuples<*T: MyTrait>(foo: T) -> MyStruct<T> { ... } which (I think you’re saying) would be called like my_struct_of_tuples((1, 2, 3)); and give me a MyStruct<(i32, i32, i32)>?


#5

If I understand the proposal correctly, the only way to “iterate” over each element of a variadic tuple is recursion. This seems like a potential downside to me (although having tuples large enough for the recursion limit to become an issue seems somewhat unlikely in normal code).


#6

What’s the idea behind using the two different syntaxes .. and *? because really variadicness is packing, so the operator would pack or unpack depending on the context.

e.g. in python you have a, *b = c and def a(*b): in both locations. Though i could see * being confusing or ambiguous, so would .. work in such locations that you’re using * currently?


#7

The problem when reusing ..-syntax is, as follows, ..arg talks about the elements of the tuple arg, and considering a function fn foo(..arg: (i32;)), the part ..arg: (i32;) suggests that the elements of arg are of type (i32;). Which is wrong, as the elements are of type i32.

Yet I get the point, it’d be a nice symmetry if both things would use the same syntax.


#8

(T;T: Clone)

I don’t think we need syntax for handling bounds as part of the type. I think a more robust and way of handling bounds would be something like

impl<*T> T
where *T: Clone {
}

Where *T: Clone means all elements of the tuple type implement Clone.

This would also mean that bounds on T itself mean bounds on the tuple, which is simpler and more consistent.


I think that this is worth looking into, I don’t think we can use * because that could conflict with pointer dereference, and .. or ... conflicts with ranges. We could use more dots, ...., but that seems too long. Maybe we could bring back ~ for this?


#9

Look into the examples, I did exactly that with Display.

yes.


#10

Also, from the previous discussion of this, it was noted that tuples don’t necessarily contain contiguous parts of memory that would represent a sub-tuple. For example, (i32, u32, f32) does not have to have a sub-tuple (u32, f32).

Because of this, how would this be handled.

let tuple: (i32, u32, f32) = (-1, 0, 2.0);
let (x, ..xs) = &tuple;

Specifically, what is the type of xs?


#11

See the Destructuring part. xs would be of type (&u32, &f32).


#12

Ok, I asked because that’s slightly different because you are passing the tuple by value, not a reference to the tuple. Thanks for the reply.


#13

If I understand correctly you think abstract-tuple-types are not required, because you could also add syntax, which talks about all elements of a tuple. Correct me, if I understood wrong.

Something like this was my initial idea, but how would you eg. define associated types, which have to be tuples? Currently this works with type A: (T;T); This is required as otherwise you could never use .. onto an associated type, as you would not know whether the associated type is a tuple-type.


#14

We don’t have generic associated types yet, and if and when we do get them we will have where bounds on associated types. Therefore, bounds on tuple types could be defined earlier in the impl using where bounds.

impl<*T> Trait for Foo<T>
where T: Clone {
   type Assoc = T;
}

#15

Do you mean I should create a PR to my own repository?


#16

Hm… I don’t quite understand. How would you define the following without this syntax?

trait Foo {
    type A: (T;T);
}

#17

(T; T) isn’t a trait, right? So you can’t use it as a bound.


#18

... does not have any conflict with ranges.


#19

... is valid in match patterns. Proof.


#20

I recognize I could have been more explicit about this, but yes (T; T) is a trait or shall at least behave like a trait.