Why can't the x variable work with this?
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.
Shouldn't this be addressed then? Should they allow the tuple to be indexed dynamically?
It would have a different type each iteration of the loop, which doesn't work.
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.
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.
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.
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"..
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
).
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
Don't we already have static alternatives for that? Specifically: uom
Thanks, that was exactly what I was looking for, but couldn't find
But I'm sidetracking here, back to the topic at hand: @joe232: can you elaborate on what you're trying to do?
I've written a (very hackish) derive to iter through fields of a struct
. So far, it hasn't proven very useful.
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.