Is magic method indexing Tuples an anti-pattern?

Why aren't tuples indexed as heterogeneous slices? I find method chaining digits confusing (I am relatively new to Rust).

This should be asked on users.rust-lang.org, internals is for changes to the Rust language, and questions about the Rust compiler internals.

To answer your question: tuples are more like anonymous structs than a collection. So it doesn't make sense to index a tuple any more than it makes sense to index a struct.

4 Likes

Here are some reasons that indexing doesn't work for tuples in Rust:

fn foo(tuple: (u8, u32), n: usize) {
    let x = tuple[n]; // what is the type of `x`?
}
impl std::ops::Index<usize> for (u8, u32) {
    type Output = ???; // what to put here?

    fn index(&self, n: usize) -> Self::Output {
        todo!()
   }
}
10 Likes

Thank you that makes sense. I was thinking positionally.

I am now here:

I do believe indexing and slicing with tuples could still be useful with const generics. For this purpose I don't think the dot-index syntax would work, since you'd have to access a (compile-time) constant, not a name.

E.g., something like the C++ get function for tuples:

// Assumes 'Tuple' trait, just for clarification.
const fn get<T: Tuple, const I: usize>(t: T) -> T[I] {  // I-th type in the tuple
    t[I]                                                // I-th member of the tuple
}
4 Likes

...in addition to variardic generics ? e.g.

  • variardics would process each element of a tuple using a plymorphic fn + a special map! built-in
  • indexing would provide access to a single tuple member by number?

.

I guess after harmonization with the proposed "variardic" syntax the `get` function would be spelled:
// ... is a splice operator that turns a tuple type into a list of types
// (...T) is tuple type T spliced and wrapped back into a tuple, e.g. equals T
// yet using (...T) in a generic parameter position communicates the constraint that T is a tuple type
const fn get<(...T), const I: uszie>(t : T) -> T[I] { t[I] }
// or equivalently
const fn get<T, const I: uszie>(t : (...T)) -> T[I] { t[I] }
// or equivalently
const fn get<(...T), const I: uszie>(t : (...T)) -> T[I] { t[I] }
// the point is that we do use `(...T)` somewhere in the signature
// to communicate T is a tuple type
1 Like

For the purposes of this thread, I'd say: same difference - indexing & slicing would seem to be used in much the same way, regardless of whether a tuple is destructured into a parameter pack or not.

I'm sorry perhaps others on this thread understand better but.. what is slicing?

And generally I wanted to ask for your opinion: do you think both

  • variardict generics - as in map!(tuple, polymorphic_fn)
  • indexing - a in ...<const i : usize> ... T[i] ... t[i]..

would be useful alongside each other but for different purposes? Or do you view them as "either .. or"?

I mean taking sub-ranges out of a type or compile-time tuple/parameter pack: Something along the lines of:

type a = (u8, u16, u32, u64)[..2];    // (u8, u16)
type b = (u8, u16, u32, u64)[1..3];   // (u16, u32)
type c = (u8, u16, u32, u64)[2..];    // (u32, u64)
// and so on

// analogous for tuple values, or parameter packs for the folks who prefer those

The question here (and with indexing) is what to do with bounds that are out of range. I suspect it'd be more useful if those result in empty tuples for slices, rather than errors (but still error with single indices). Could be wrong, though.

Regarding your second question, if you mean between indexing/slicing and some map! functionality: I'm not really familiar with the latter suggestion, but from the name I'd think they'd be orthogonal.

FWIW, as discussed in the other thread I'm generally in the camp "type system rather than parameter packs". :wink:

Thx a lot @lordan for explaining. Now if I could continue to selfishly abuse the forum and your time to further my own understanding:

  • it's pretty clear what "variardic" approaches are intended for: implement hashcode or serialization in user code without recourse to macros (if they become powerful enough to iterate a struct as if it was a tuple)
  • what are the motivating use cases for slicing and indexing tuples?

Obviously if the language had more powerful templating/reflection mechanisms indexing could replace map! and friends. So it does keep seeming to me that you're suggesting indexing as part of the

meta-project. So I keep asking - is this an alternative to map! or is this genuinely something that is needed on its own?

As they say: whynotboth.jpg. I don't have a concrete use case in mind at the moment, other than perhaps to mirror C++ get and related functions. Again, I'm not familiar with the map! proposal, so can't comment on that.

Not to hijack this thread but if closures have names why don't anonymous types? That's better than slicing. For lazy method chaining this makes a lot of sense readability wise. Like if we called map().map().collect() and needed an interim one time data structure. Forgive my jargon ineptitude.

@Corallus-Caninus, it sounds like you want named existential types, which are currently available as an experimental feature in the nightly toolchain.

That's really an entirely separate subject from this thread, so please create a new topic if you want to discuss it further.

Thank you, that is not quite what I meant I mean strictly naming tuples

let menagerie = (number_giraffes: 2, number_rats: 11);
assert_eq!(menagerie.number_giraffes, 2);

but this can be closed.

1 Like

Structural records?

1 Like

Yes exactly thank you! These should at least take precedence over tuples in my personal opinion. Its not long writing an algorithm before I find tuple.1.2.3