Tuples in for loops

Why can't the x variable work with this?

2 Likes

Tuples can't be dynamically indexed.

If it makes it clearer - your tuple is (loosely) equivalent to this:

struct MyTuple {
    0: i32,
    1: i32,
    2: &'static str,
}

You (probably!) wouldn't expect to be able to for loop over that, and you can't do it to a tuple for the same reasons.

7 Likes

Shouldn't this be addressed then? Should they allow the tuple to be indexed dynamically?

2 Likes

It would have a different type each iteration of the loop, which doesn't work.

8 Likes

As others have wrote x in your example will use different memory amount on the stack for each item (4 bytes for int, and 8 bytes for string on 64-bit machine), which will not work in Rust. (well, there is a merged RFC for introducing dynamically sized objects on the stack, but it's not for near future, and I highly doubt that it will be used to implement iteration over tuples) If you want to emulate behavior of dynamically typed languages (judging by the neighbor thread it's your use-case), you'll have to use trait objects, which will use dynamic dispatch under the hood, which you will be able to store in arrays.

2 Likes

Here's an (slightly contrived) example of why you can't do stuff like that in Rust:

let myTuple = (1, 2, "false");
let x = randomNumberGenerator();
let y = myTuple[x];

In this case, the compiler wouldn't be able to figure out at compile-time what type y should be! Whereas with an array, you know (and the compiler knows) that indexing is always going to return the same type.

4 Likes

Without necessarily endorsing this as something that should be added to Rust, it's worth considering that there is precedent for this being possible in a statically-typed, monomorphised language: D. If you use a tuple with a for loop in D, the compiler statically unrolls the loop. You can also index into tuples in D, provided the index is evaluable in a constant context.

But, yeah, dynamic indexing is obviously a non-starter.

7 Likes

Probably not.
The real question is: why would you want to? Is there any specific problem you are trying to solve?
If there is a specific problem, we can help you better, and suggest alternative "more rustic" solutions, if you can provide more context.

If you are coming from a python background, where Tuples behave like you demonstrate: Rust avoids this kind of dynamism, because it makes low-level efficiency basically impossible.

Rust is VERY big on correctness, and correctness depends, always, on doing the correct interpretation of fields.
If I have a MyUser { user_id: u32; amount_of_failed_logins: u32 } there is absolutely no sensible way to treat both "numbers" the same way.
Rust categorically avoids "structural typing"; just because two structs have the same field types, doesn't mean they are comparable. Compare for example MyUser to Point { x: u32, y: u32 }, some languages will allow a 'transmute' cast from MyUser to Point, because their layout is the same. Rust's focus on correctness forbids it (temporarily ignoring uses of unsafe to keep the discussion manageable).

If it is "just for printing", Rust has a better tool: #[derive(Debug)], which even gives you pretty-printing for free.

I struggle to invent use cases where that could be useful.. (but am willing to be enlightened!)
Maybe handling N-dimensional points, and iterating over N? ... Though that sounds like areas where better programmers than me start using words like "Higher Ranked Type Boundaries" and "Higher Kinded Types"..

4 Likes

Yeah, afaik iterating over a (statically-typed, non-Pythonic) tuple is only genuinely useful in template metaprogramming, where manipulating lists of types is as commonplace as a for loop.

D is really interesting in that it managed to make template metaprogramming barely distinguishable from "regular" code. But I'm not sure that's desirable for Rust. We probably need to get CTFE stabilized before figuring that out just because CTFE covers so many of the same use cases, and it seems uncontroversial that code in a const fn should look and act more or less the same as code in a runtime-only fn.


For anyone who hasn't been down this rabbit hole before: Dimensional analysis is probably the simplest and most useful example of a legitimate use case for template metaprogramming that is probably not covered by CTFE (compile-time function evaluation, which in Rust is spelled const fn).

2 Likes

I'm not yet versed in what kind of Types this is, but when const generics - RFC 2000 - land, iterating over N-D points will be unrollable at compile time for any N :smiley:

2 Likes

Don't we already have static alternatives for that? Specifically: uom

Thanks, that was exactly what I was looking for, but couldn't find :heart:

But I'm sidetracking here, back to the topic at hand: @joe232: can you elaborate on what you're trying to do?

3 Likes

I've written a (very hackish) derive to iter through fields of a struct. So far, it hasn't proven very useful.

1 Like

Ok, I'll give a use case - What if every member of the tuple implements a common trait? Then it would be perfectly reasonable to want to iterate and map over it, especially for avoiding/minimizing heap allocation as the common way to accomplish that is Vec<Box<dyn CommonTrait>>.

The first step towards improving that would be to go to [Box<dyn CommonTrait>; N] once the layout is fixed and ready for iterating, but that still may require up to N heap calls for each iteration cycle. You could put all the potential options into an enum and go to [EnumCommonTraits; N], but then you've got an enum to maintain. And what if the variant sizes are approximately uniformly spaced from very small to aggressively medium, enough to where [EnumCommonTraits; 6] might start hitting cache limits?

It's admittedly both contrived and quite niche, but I'm seriously considering it as an option to get compile time memory layout AND iterator item type guarantees with the flexibility of dynamic dispatch.