What if tuple types were isomorphic?

What would Rust look like, if

  • (x,) was interchangable with x?
  • (x, (y,)) was the same as ((x,), y) and (x, y)?
  • There was no distinction between functions with multiple arguments and functions with tuple arguments?

Sometimes, I just wonder.


At this point, what difference does it make? There's no way this could change given Rust's back-compatibility guarantees.

Edit: Reading this again, I see that this may come off as rude. That is not my intent — it's a legitimate question as to the purpose of asking.

Hi there, why would you want something like that? There are some discussions of variardic generics and it doesn't seem totally impossible - at least to me - for some transformations like this to emerge one day. But why? What is the use case?

It becomes immediately confusing when you hide types behind generics. Suddenly, you can't treat ((A, B), (A, B)) and (T, T) identically, even if T = (A, B).

(T, T).1 needs to be T for the type system to work. This is incompatible with the "simple type checked template" interpretation of generics, as the behavior changes if you substitute (A, B) for T.

Even if you make this work, this necessarily mandates that tuples are always laid out linearly in memory, so that you can take a reference to the prefix or suffix of the tuple, because you can bundle them up into a type.

Basically, making tuple nesting isomorphic makes them no longer behave like normal types do.


I do not think auto-flattening tuples in type level is a good idea. Besides back compatibility issue, the structures of tuples also provide useful infomation. For example, you can encode trees in tuples: let piled_tree = trees::Tree::from(( 0, (1,2,3), (4,5,6) ));, which represents the following tree

.     0     .
.   /   \   .
.  1     4  .
. / \   / \ .
.2   3 5   6.

Sometimes we do need list operations on tuples, e.g. push(), pop(), merge(), flatten(), unflatten()..etc, and traits can be helpful. It will be perfect if the language provides variadic generics.


Swift used to have this-or-a-very-similar feature, and it was removed because it was considered confusing and not especially useful. I haven't missed it since it was removed. (I'm a daily Swift programmer.)

Swift-Evolution proposals:


AFAIK Microsoft's Q# language has something like this, and I have wondered for a while whether it'd make sense for Rust as well.

Not auto-flattening, mind you (this would indeed run into the aforementioned issues), but auto-conversion between values, 1-tuples and 1-arrays (only!).

For one, this would make the fugly 1-tuple syntax unnecessary in a lot of cases. For example, when way back then there was talk about yield expressions with return values:

let (a,) = yield b;   // technically yield returns a tuple, requiring 1-tuple syntax
let c = (yield d).1;  // I guess this would work, too

let x = yield y;      // with auto-conversion this would look much nicer

Or, for hypothetical generic array or tuple joining const functionality could obviate the necessity to have 2 different functions for append and join:

let joined1 = join([1u8, 2, 3], [4, 5, 6]);  // -> [1, 2, 3, 4, 5, 6]
let joined2 = join([1u8, 2, 3], 9);          // 9 auto-lifted to [9] -> [1, 2, 3, 9]
let joined3 = append[1u8, 2, 3], 9);         // would require a 2nd fn otherwise

FWIW, layout-wise this should be a no-op, but TBF, I'm still not a 100% sure myself if it'd be worth it.

It would look like Perl, which has this feature.

Very ambiguous and backward incompatible.

Interesting but I think backward incompatible, too

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.