# variadic tuples attempt #80973022 - Feature Name: (`min_variadic_tuples`) - Start Date: (fill me in with today's date, YYYY-MM-DD) - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary [summary]: #summary Implement a solution for minimal-surface-area variadic generics by extending the responsibility of tuples to arbitrary arities: ```rust #![feature(tuple_trait, min_variadic_tuples)] trait FrobTuple: Tuple { fn frobnicate() -> usize; } impl FrobTuple for () { fn frobnicate() -> usize { 0 } } impl<T, R: Tuple> FrobTuple for (T, ..R) { fn frobnicate() -> usize { 1 + R::frobnicate() } } type Frobbable = (u8, u16, u32, u64, u128); assert_eq!(Frobbable::frobnicate(), 5); ``` # Motivation [motivation]: #motivation Across the Rust ecosystem, [there](https://docs.rs/bevy_ecs/latest/bevy_ecs/query/trait.WorldQuery.html#impl-WorldQuery-for-(F0,)) [are](https://docs.rs/diesel/latest/diesel/prelude/trait.Queryable.html#impl-Queryable%3CRecord%3C(ST0,)%3E,+Pg%3E-for-(T0,)) [many](https://docs.rs/leptos/latest/leptos/ev/trait.EventHandler.html#impl-EventHandler-for-(A,)) [examples](https://doc.rust-lang.org/stable/std/hash/trait.Hash.html#impl-Hash-for-(T,)) of traits implemented for tuples of varying (but never *arbitrary*) arity. Macros ease the pain of these implementations somewhat, but, [as Alice Cecile explained for Olivier Faure](https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/#do-we-really-need-variadics), this situation is far from ergonomically robust: > * It produces terrible error messages. > * The compile times cost is high. > * Code using this pattern is hard to read and review. > * Its documentation is terrible. Additionally, there are many APIs which could very elegantly be expressed as variadic functions, but, due to the lack of variadic support are either macros (like [`futures::join`](https://docs.rs/futures/latest/futures/macro.join.html)) or functions taking two parameters (like, well, the other [`futures::join`](https://docs.rs/futures/latest/futures/future/fn.join.html)). Note that the case for `futures::join` [used to be](https://github.com/rust-lang/futures-rs/issues/2215) so problematic (even disregarding the poor ergonomics) that the project had separate `join{3..=5}` functions. This kind of API fragmentation is difficult to teach, difficult to maintain, and difficult to use. Other (potential) targets: * [`core::iter::zip`](https://doc.rust-lang.org/stable/core/iter/fn.zip.html) * [`futures::select`](https://docs.rs/futures/latest/futures/macro.select.html), and (of course) [`futures::select`](https://docs.rs/futures/latest/futures/future/fn.select.html) * # Guide-level explanation [guide-level-explanation]: #guide-level-explanation Variadic tuples extend the behavior and utility of tuples to an arbitrary number of elements. ## Interpolation This includes support for interpolating tuples at the type level. The elements of one tuple can be "unpacked" to be stored inline within another tuple with the new `..T` (unpacking) type operator. The definition of tuples is extended such that their elements are either "inline" or "unpacked": ```rust #![feature(min_variadic_tuples)] // `T`, then all the elements from `A` type Prepend<A, T> = (T, ..A); // all the elements from `A`, then `T` type Append<A, T> = (..A, T); // all the elements from `A`, then all the elements from `B` type Concat<A, B> = (..A, ..B); // `Identity1<A> == A` type Identity1<A> = (..A,); // This is identical to the above; notice the lack of a trailing comma. type Identity2<A> = (..A); // ERROR: the unpacking operator cannot be used outside of a tuple! type Identity3<A> = ..A; ``` Note that the unpacking operation (`..T`) does not recursively flatten tuples, so `(..(A, (), B))` can be simplified to `(A, (), B)`, not `(A, B)`. For unpacking to be well formed, operands must only implement the (currently unstable) `Tuple` trait: ```rust #![feature(min_variadic_tuples)] // ERROR: `u8` cannot be unpacked because it is not a tuple! type Unpacked = (..u8); ``` A key difference of this RFC compared to those prior, is that it does not propose any new syntax for composition and destructuring of tuple values. ```rust /// # Safety /// The field in `tup` with type `T` is copied. /// Care must be taken to ensure UB is not triggered by reusing the value. unsafe fn read_in_place<A: Tuple, T, B: Tuple>( tup: &ManuallyDrop<(..A, T, ..B)>, ) -> T { let field = ptr::from_ref(tup).wrapping_byte_offset(ptr::offset_of!((..A, T, ..B), 0)); // SAFETY: `field` is not used again // and is valid-for-reads from caller contract. unsafe { field.read() } } fn write_in_place<A: Tuple, T, B: Tuple>( tup: &mut MaybeUninit<(..A, T, ..B)>, t: T, ) -> T { let field = ptr::from_mut(tup).wrapping_byte_offset(ptr::offset_of!((..A, T, ..B), 0)); // SAFETY: `field` is valid for writes for all `MaybeUninit`s. unsafe { field.write(t) } } pub fn concat<A: Tuple, B: Tuple>(a: A, b: B) -> (..A, ..B) { struct Marker; // Writes to a partially initialized tuple, from another tuple. // // Let us call `T` the "infix", `(..A, ..AA)` the "prefix", and `B` the "suffix". // This gives us `(..A, ..AA, ..T, ..B)`, (..prefix, ..infix, ..suffix), as the "full tuple". // // This trait allows initializing part of a full tuple (the infix) in place. unsafe trait Unpack<A: Tuple, B: Tuple, AA: Tuple, T: Tuple> { // Initialises the the infix of `dst` from the `src` tuple. // // # Safety // * The infix of `src` is copied by reference, // care must be taken to ensure UB is not caused by reusing those values. unsafe fn write(dst: &mut MaybeUninit<(..A, ..AA, ..T, ..B)>, src: &ManuallyDrop<(..AA, ..T)>); } // SAFETY: This is the trivial implementation for a unit infix. unsafe impl<A: Tuple, B: Tuple, AA: Tuple, T: Tuple> Unpack<A, B, AA, ()> for Marker { unsafe fn write(dst: &mut MaybeUninit<(..A, ..AA, ..B)>, src: &ManuallyDrop<AA>) { // Nothing more to do. The infix is the unit tuple. } } // SAFETY: // * This initializes the infix by inductive recursion. // * See (1) and (2) in the implementation of `write`. unsafe impl<A: Tuple, B: Tuple, AA: Tuple, T, BB: Tuple> Unpack<A, B, AA, (T, ..BB)> for Marker { #[inline] unsafe fn write(dst: &mut MaybeUninit<(..A, ..AA, T, ..BB, ..B)>, src: &ManuallyDrop<(..AA, T, ..BB)>) { // SAFETY: // * The infix is `(T, ..BB)`. // * The caller guarantees the infix of `src` is initialized, // so the `T` below (the first element of the infix) must be initialized. let t = unsafe { read_in_place::<AA, T, BB>(src) }; // NB (1): The first element of the infix is now initialized. write_in_place::<(..A, ..AA), T, (..BB, B)>(dst, t); // NB (2): // * This recursive call initializes all other elements of the infix. // * This is because we "shift" the first element of the infix into the prefix type, now `(..A, ..AA, T)`. // * Notice how the types of the `src` and `dst` arguments does not change, // but the infix type does, so this points to a different method. <Marker as Unpack<A, B, (..AA, T), BB>>::write(dst, t) } } // A safe wrapper for `Unpack::write` which uses the whole `src` tuple as the infix. fn unpack<A: Tuple, B: Tuple, T: Tuple>( dst: &mut MaybeUninit<(..A, ..T, ..B)>, src: T, ) { let src = &ManuallyDrop::new(src); // SAFETY: `src` is never used again. unsafe { <Marker as Unpack<A, B, (), T>>::write(dst, src) } } let mut dst = MaybeUninit::<(..A, ..B)>::uninit(); unpack::<(), B, A>(dst, a); unpack::<A, (), B>(dst, b); // SAFETY: // * `dst` is `MaybeUninit<(..A, ..B)>`. // * The first `unpack` call above writes `A` to the start of `dst`. // * The second `unpack` call above writes `B` after `A` to `dst`. // * Therefore, the whole of `dst` is initialized correctly. unsafe { dst.assume_init() } } pub fn prepend<R: Tuple, T>(rest: R, first: T) -> (T, ..R) { concat((first,), rest) } pub fn append<R: Tuple, T>(rest: R, last: T) -> (..R, T) { concat(rest, (last,)) } ``` [range-to-search]: https://github.com/search?q=lang%3Arust+%2F%2C+%5C.%5C.%5Ba-zA-Z0-9%5D%2B%5C%29%5C%29%2F&type=code ## Inference Binding to an arbitrary number of generics, then is a simple as imposing bound on `Tuple`. The compiler can perform the inference required to fit variadic types against eachother: ```rust #![feature(tuple_trait, min_variadic_tuples)] fn split_first<T, R: Tuple>(_tuple: (T, ..R)) -> (T, R) { todo!() } // ERROR: Tuple arity mismatch. The tuple `()` must have at least 1 element. let (t, r) = split_first(()); // The `R` type parameter is inferred to be `()`. let (t, r) = split_first((A,)); ``` ## Induction For more complex usages, traits can be implemented [inductively][induction] for tuples of varying lengths. We can define some number of finite _base cases_ and a recursive _inductive step_ which extends the applicability of the implementations to an arbitrary arity: ```rust #![feature(tuple_trait, min_variadic_tuples)] trait AllTuples {} // (1) --- a base case for length 0 (unit) tuples impl AllTuples for () {} // (2) --- the inductive step for tuples with length len(R) + 1 impl<T, R: Tuple> AllTuples for (T, ..R) {} fn all_tuples<T: AllTuples>() {} fn any_tuple<T: Tuple>() { // Even though `T: Tuple` does not trivially imply `T: AllTuples`, // The compiler is nevertheless able to reason (very loosely): // // * impl (2) holds for any tuple with 1.. elements, // * impl (1) holds for the unit tuple. // // So it can deduce that these impls, when inductively unified, // cover every possible type for `T`, and so `T` must implement `AllTuples`. // Therefore, the call compiles. all_tuples::<T>() } ``` [induction]: https://en.wikipedia.org/wiki/Mathematical_induction # Reference-level explanation [reference-level-explanation]: #reference-level-explanation ## Syntax The tuple type syntax is extended to the following: > _TupleElem_ :\ > &nbsp;&nbsp; &nbsp;&nbsp; [_Type_]\ > &nbsp;&nbsp; | `..` [_Type_] > > _TupleType_ :\ > &nbsp;&nbsp; &nbsp;&nbsp; `(` `)`\ > &nbsp;&nbsp; | `(` `..` [_Type_] `)`\ > &nbsp;&nbsp; | `(` ( _TupleElem_ `,` )<sup>+</sup> _TupleElem_<sup>?</sup> `)` [_Type_]: https://doc.rust-lang.org/reference/types.html#type-expressions ## Elements and Fields A tuple can be conceptualised as a heterogenously typed sequence of *elements*, where each element takes one of the following forms: - Inline, like the `A` and the `B` in `(A, B)`. - Unpacked, like the `A` in `(..A, B)`. This sequence of elements can be decomposed into a heterogenously typed list of *fields* using the following rules: - Inline elements correspond directly to fields at their position. - Unpacked elements "flatten" their elements directly into their encompassing tuple. Consider the following decomposition (1) of a tuple type into its elements and fields: ``` elements: 0 1 2 3 | | ┌──────┴──────┐ | (A, B, ..(C, D, ..()), E) | | | | | fields: 0 1 2 3 4 ``` Additional precision is required for the case where a tuple used in an unpacked element does not have a known length. Consider the following decomposition (2), where `Opaque` does not have a known length (like the generic parameter in `fn variadic<T: Tuple>() {}`). ``` elements: 0 1 2 3 4 5 | ┌───┴───┐ ┌───┴───┐ | ┌───┴───┐ | (A, ..Opaque, ..(B, C), D, ..Opaque, E) | | | | | fields: 0 1 2 3 4 ``` Note that fields are defined in this way, so that field projection on tuples (`tuple.0`, etc.) behaves in a reasonable manner: * Allowing field projection onto opaque, unpacked tuple elements (like `..Opaque` above) is not possible behind references and is a performance footgun for owned tuples, due to alignment and padding rules rearranging otherwise simple tuples. Since tuple layout is not guaranteed, this could be changed in the future to allow matching unpacked opaque elements with patterns. * Field projection behaves the same whether or not a tuple is "eagerly unpacked", so the first field of `(..(u8,))` and `(u8,)` must be the same. * These rules aren't perfect. See [the unresolved questions](#unresolved-questions). ## Trait solving > [!WARNING] > **TODO:** I have many notes here, but have not yet written them up. # Drawbacks [drawbacks]: #drawbacks * This feature adds significant complexity (and subtlety) to a type system which some users are already overwhelmed by. * It is hard to say without benchmarking which would have the worse effect on compile times, between the macro-dependent "fake variadics" currently in the wild (increasing code complexity, mostly only when they are explicitly used), or the implementation of this language feature (increasing compiler complexity, perhaps even when are not used). # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives ## Alternatives for the elements of this RFC * Instead of providing the `builtin # unpack` syntax, we could make `core::tuple::concat` its own compiler-defined intrinsic. It offers the same expressibility as `builtin # unpack`, without much of the ergonomics. `builtin # unpack` is likely to correspond more directly to compiler behavior. ## Analysis of variadic generics in other languages ### Varargs (various) Baseline support for variadic parameters is present in many languages (often called "varargs"). These are nearly always composed as a single list of arguments written inline at the end of the argument list. Implementations of this feature are largely split into two categories: * Untyped ([C-style](https://en.cppreference.com/w/c/language/variadic)) - Rust [already has support](https://doc.rust-lang.org/reference/items/external-blocks.html#variadic-functions) for this type of parameter in `extern` blocks. - These are problematic for use in Rust because they are [not type-checked during compilation][bad-c-variadics] (i.e. they are inherently unsafe). * Typed ([C#-style](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#params-modifier)) - While typed variadic parameters are safe, they provide very little benefit over simply taking a collection of values as an argument, as they are required to be homogenously typed. - They are better suited languages with polymorphic typing by default since inheritence can mock heterogenously typed collections. Furthermore, varargs allow for a form of function overloading which is separate from (and likely more controversial than) the features proposed in this RFC. [bad-c-variadics]: https://c.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:___c,selection:(endColumn:1,endLineNumber:14,positionColumn:1,positionLineNumber:14,selectionStartColumn:1,selectionStartLineNumber:14,startColumn:1,startLineNumber:14),source:'%23include+%3Cstdio.h%3E%0A%23include+%3Cstdarg.h%3E%0A%0Aint+problematic_variadic(int+count,+...)+%7B%0A++++va_list+args%3B%0A++++va_start(args,+count)%3B%0A%0A++++for+(int+i+%3D+0%3B+i+%3C+count%3B+i%2B%2B)+%7B%0A++++++++int+x+%3D+va_arg(args,+int)%3B%0A++++++++printf(%22%25i+:%3D+%25i%5Cn%22,+i,+x)%3B%0A++++%7D%0A++++va_end(args)%3B%0A%7D%0A%0Aint+main()+%7B%0A++++problematic_variadic(2,%0A++++++++//+passing+an+int+is+fine!!%0A++++++++10,%0A++++++++//+passing+a+float+is+dangerous!!%0A++++++++10.0f)%3B%0A%7D%0A'),l:'5',n:'1',o:'C+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:cg141,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'0',intel:'0',libraryCode:'0',trim:'1',verboseDemangling:'0'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:___c,libs:!(),options:'',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+x86-64+gcc+14.1+(Editor+%231)',t:'0'),(h:output,i:(compilerName:'x86-64+gcc+14.1',editorid:1,fontScale:14,fontUsePx:'0',j:1,wrap:'1'),l:'5',n:'0',o:'Output+of+x86-64+gcc+14.1+(Compiler+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:1,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4 ### [Parameter packs (C++)](https://en.cppreference.com/w/cpp/language/parameter_pack) Parameter packs are a maximal implementation of variadic generics, providing the algebraic funamentals required for many [complex compile-time operations](https://stackoverflow.com/questions/17178075/how-do-i-reverse-the-order-of-element-types-in-a-tuple-type). Resolution of variadic parameter packs happens as part of monomorphisation (because they are tied to `template`s). If we follow this paradigm, our implementation of variadic generics will inevitably be as brittle and prone to evoking post-monomorphisation errors as C++ templates are. Rust has taken a firm stance against post-monomorphisation errors, so this approach to implementation is unfavourable. ### [comptime (Zig)](https://ziglang.org/documentation/master/#comptime) Variadic generics fall out naturally from Zig's comptime analysis: because the language supports "type's as first class citizens" (unconstrained higher-kinded types), procedural generation of custom tuple types is easy. With the stabilisation of GATs two years ago, Rust has adopted its own model of constrained higher-kinded types. GATs have a rigid interface which promotes "algebraic" and pure manipulation of types. Any model of comptime for Rust priviledged enough to support variadic generics necessarily defies this precedent by providing unrestrained access for messing with the type system, While comptime is not a detrimental feature in its own right, it is fundamentally incompatible with Rust's current design and philosophy, leading to the same kind of post-monomorphisation tragedies [outlined above](#parameter-packs-c). # Prior art [prior-art]: #prior-art ## Partial implementations in stable Rust * [`frunk`](https://docs.rs/frunk) - Provides a cons list approach to implementing variadic generics. - Construction of nested tuples is made ergonomic through macros. - [The `HCons::sculpt` method](https://docs.rs/frunk/latest/frunk/hlist/struct.HCons.html#method.sculpt) provides structural morphing for tuples to a limited degree. - Unpacking, interpolation & concatenation of multiple variadic tuples is not possible. - Cons list approaches can be limited because they are "directed". With a cons list like `(A, (B, (C, ())))`, explicit care must be taken when interacting with "reverse" cons lists like `((((), A), B), C)`. - Due to the lack of language support, the library is very trait and recursion heavy, which impacts compile times. ## RFCs > [!tip] > Closed discussions are demarcated with a ticked checkbox. * [x] (2017-02-22) [Conservative variadic functions](https://github.com/rust-lang/rfcs/pull/1921) (sgrif) * [x] (2017-02-28) [Tuple-Based variadic generics](https://github.com/rust-lang/rfcs/pull/1935) (cramertj) * [x] (2019-05-22) [Add generalized arity tuples](https://github.com/rust-lang/rfcs/pull/2702) (Woyten) * [x] (2019-10-02) [Variadic tuples](https://github.com/rust-lang/rfcs/pull/2775) (fredericvauchelles) ## Pre-RFCs & Sketches * (2013-10) [Draft RFC: variadic generics](https://github.com/rust-lang/rfcs/issues/376) (eddyb) * (2017-09) [Support flattening of tuples](https://github.com/rust-lang/rfcs/issues/2138) (Nokel81) * (2018-07) [Idea for minimal variadic type parameters](https://internals.rust-lang.org/t/idea-for-minimal-variadic-type-parameters/7686) (tmccombs) * (2019-09) [Brainstorming: variadic & type expressions](https://internals.rust-lang.org/t/brainstorming-variadic-type-expressions/10935) (lordan) * (2023-06) [Variadic generics design sketch][bertholet-sketch] (active) (Jules Bertholet) * (2023-07) [`static for` and `static if` to manipulate variadic generics](https://internals.rust-lang.org/t/static-for-and-static-if-to-manipulate-variadic-generics/19230) (xmh0511) * (2023-09) [A madman's guide to variadic generics](https://internals.rust-lang.org/t/a-madmans-guide-to-variadic-generics/19605) (outdated) (soqb) * (2023-10) [Pre-RFC: Array expansion syntax](https://internals.rust-lang.org/t/pre-rfc-array-expansion-syntax/13490) (nwn) ## Previous Analyses * (2021-01-30) [Analysing variadics, and how to add them to Rust](https://poignardazur.github.io/2021/01/30/variadic-generics/) (PoignardAzur) * (2022-04-03) [rust-variadics-background](https://github.com/alice-i-cecile/rust-variadics-background) (Alice Cecile) * (2023-11-08) [Variadic generics, again](https://poignardazur.github.io/2023/11/08/time-for-variadic-generics/) (PoignardAzur) * (2024-05-25) [Report on variadic generics discussion at RustNL][faure-rustnl] (PoignardAzur, various contributors) [bertholet-sketch]: (https://hackmd.io/@Jules-Bertholet/HJFy6uzDh) [faure-rustnl]: (https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/) ## Special Thanks This RFC is building on the work of many other contributors, and the authors would particularly like recognise the following contributors who have yet walked this accursed path: * [PoignardAzur](https://github.com/PoignardAzur/) for their undying commitment to getting variadics moving, and for being excited and dilligent enough about the feature to conduct [a survey on it at RustNL 2024][faure-rustnl]. * [Jules Bertholet](https://github.com/Jules-Bertholet) for their [variadics design sketch](bertholet-sketch) which serves as an invaluable comparison of different prior designs. * [Alice Cecile](https://github.com/alice-i-cecile) whose work for [Bevy](https://github.com/bevyengine/bevy) exposed especially tenderly what we miss out on without variadics. * [eddyb](https://github.com/eddyb) for getting the ball rolling, all those *(eleven!)* years ago. # Unresolved questions [unresolved-questions]: #unresolved-questions * [ ] What syntax (if any) should eventually be stabilised for composing and destructuring tuples, without introducing performance footguns? * [ ] What should the exact rules be for considering a tuple "not to have a known length". If these rules change, it might reorder field indices, and cause breakages. The rules would have to be changed over an edition boundary. # Future possibilities [future-possibilities]: #future-possibilities ## Variadic functions Should function parameters be permitted to be variadic like the following? ```rust #![feature(tuple_trait, min_variadic_tuples)] // Tentatively suggested syntax: fn many_args<T: Tuple>(args: ..T) { } // Alternative syntax: fn many_args<T: Tuple>(..args: T) { } // Or even.. this: fn many_args<T: Tuple>((args @ ..): T) { } ``` Note that (as written) this allows the following form of overloading, and so may become problematic: ```rust #![feature(min_variadic_tuples)] trait Overload { } impl Overload for (usize,) { } impl Overload for (isize,) { } fn overloaded<T: Overload>(params: ..T) { } ``` ## Variadic closures On the other hand, it would be natural to use the unpacking operator for something like `Fn(..T) -> U`. Though on its own, this is not very useful since there's neither a way to pass arguments variadically to a function (pointer), nor a way to declare them as accepting variadic parameters. Therefore, it might be useful to allow this syntax for closures only. This would likely be less controversial than allowing variadic function overloading, since closures already perform much more inference than functions. This would also enable some powerful (but perhaps unfavourable) patterns: ```rust #![feature(tuple_trait, min_variadic_tuples, fn_traits)] trait CurryOnce { type Arg; type Next; fn cur(self, arg: Self::Arg) -> Self::Next; } impl<T, U, F> CurryOnce for F where F: FnOnce(T) -> U { type Arg = T; type Next = U; fn cur(self, arg: T) -> U { self(arg) } } impl<T, U, R: Tuple, F> CurryOnce for F where F: FnOnce(T, ..R) -> U { type Arg = T; type Next = impl FnOnce(..R) -> U; fn cur(self, arg: T) -> Self::Next { // Proposed syntax: move |rest: ..R| { let args = std::tuple::prepend(rest, arg); self.call_once(args) } } } let sum = |a: usize, b: usize, c: usize| a + b + c; assert_eq!(sum.cur(1).cur(2).cur(3), 6); ``` ## Reverse Indexing If we have a tuple of a type like `(T, ..R, U)` (where `R` is not statically known) its easy to get values of `T` and `U` using the `(t, .., u)` pattern. Equally, we can access `T` with `tuple.0`. However, there is no direct syntax to get the value of the last field, we are *required* to go through a pattern. Perhaps some syntax can be considered to let us do this (`tuple.(-1)` or `tuple.(^0)`, for example).