Feature idea: impl Iterator for tuple (1,2..n) as range with step

Range with step is useful. If we implement Iterator for tuple (A, Range) or (A, RangeInclusive), we can get a syntax that matches the math syntax without break change to current syntax.

For example, if we implement Iterator for these tuples, we can:

for i in (0,2..10) { // because it is actually a tuple, braces are necessary.
    println!("{}", i);
}
// print: 0, 2, 4, 6, 8
// and
for i in (0,2..=10) { // we can also use equal to express inclusive range.
     println!("{}", i);
}
// print: 0, 2, 4, 6, 8, 10
// and we can create reverse range
for i in (5, 4..=0) { // we can also use equal to express inclusive range.
     println!("{}", i);
}
// print: 5, 4, 3, 2, 1, 0

// the implementation can be like this:
(0,2..10).next() == Some((2,4..10))
(5,4..=0).next() == Some((4,3..=0))

Apart from for-loops, if you had used numpy, you may know range with step is useful in slicing matrix in axises, or reversing a axis. This syntax is helpful for develop a matrix library.

Finally, it can be used for float loops like (1.0, 1.1..2.0). However, it may has numerical issues about float adding and subbing. (although no issue was found on my personal testing.)

This is just an idea, but I hope it can be useful.

It's not clear why this needs to / should be done on tuples. Denoting one part of a range with a single tuple element and two other parts lumped together (with yet another tuple element) seems very confusing.

It also worries me how the range is being abused. The lower bound of the range now stands for a non-bounding value. That doesn't make much sense at all.


Is there a reason why (0..=10).step_by(2) isn't good enough? Is there a reason why you couldn't write your own struct with separate range and step size members, then implement Iterator for it?

7 Likes

Well, (1,2..n)is shorter, and if we use (0..=10).step_by(2), it's harder to get start and end of this range. And if we use (1,2..n), we can reuse it on slice syntax like numpy. If we further allow rust to do the same thing in numpy(not in std, just in matrix library), a more official syntax may be better.

And if we want to reverse range with step, it may write like for i in (0..100).rev().step(2):. But in my opinion for i in (100, 98..=0) is shorter and clearer.

And a, b ... n mathes math syntax for such sequence(range). Even though this syntax may overuse ranges, it is still understandable for people who are familiar with math. For better understanding, we may think the whole tuple as a range with step syntax, even though in implementation it is just a tuple.

2 Likes

For what it's worth, as a long-time Rust user, my first thought when looking at the syntax was "Huh? So we've got the range 2..10, but then there's a 0 modifying it somehow?" and it took me a whole minute to look through the examples and realize what was intended. So that's a point against the idea of this being "clearer". To be fair, someone who was newer to Rust might not have that reaction. But I'm also concerned that it would teach an imprecise understanding of syntax: in math notation, b ... n isn't a value with a type, so in programming languages, we have different priorities for clarity.

8 Likes

With this "step by example“ syntax, I've always wondered, if I can provide more than just 2 first elements of the sequence and how will that behave. Should that be a polynomial of the right order? What if I write?:

(0, 1, 1, 2, 3, 5, 8, ...)
2 Likes

Honestly, I think this is probably "too cute". I'd rather someone just call a function. (For example, as discussed back in 2015, it'd be nice to have linspace to iterate a floating-point range.)

Note also that this implementation for tuples is incompatible with one where (a, b).into_iterator() behaves like a.into_iterator().zip(b), like in impl<A, B> IntoIterator for (A, B) as Zip by cuviper · Pull Request #78204 · rust-lang/rust · GitHub

5 Likes

That's not a good argument in itself. Brevity is only an advantage if it results in increased clarity, which this syntax just doesn't.

1 Like

Naturally the program should make a HTTP request to OEIS and pick one of the matching sequences :wink:

15 Likes

Maybe it is not a great idea, but in my profession, visual slam, there are a lot needs to manipulate images, matrices, point clouds, and sometimes neural networks. Although c++ has enough performance, it may occurs segment faults and other safety issues. Python does great job with matrix and neural networks. However, the loop performance is poor when dealing with point clouds.

In my opinion, the rust has a great chance to solve these problems all together. However, there is a realistic problem blocking me that the array/matrix librarys are not good enough. They are not clear and easy to use, comparing to the numpy and the eigen.

The closest library maybe the ndarray. However, it has to use a method and a macro to express slice with step. The slicing syntax also uses a non-official syntax (0..n;step), although it is in the macro. And it can not use indexing syntax like the numpy, or use function call like the eigen. It just seems like rust team does not want such matrix library to work like a first-class citizen.

Although this may be not a good idea, the slice syntax with step is a real need.

To add onto this the syntax in ndarray winds up looking like

my_array.slice(s![x..y;step, ..-1])

(negatives go from the end). It's quite convenient! It could be more convenient. I'm very sympathetic to this feature idea, generally; I don't think it has any place in the standard library.

What I would like to see in this space are minimal changes to rust that could make libraries like ndarray more convenient. Extra operators, maybe, or the really low hanging fruit of postfix macros, changing this example to

my_array.s![x..y;step, ..-1]

@xjkdev Along this vein, it might be worthwhile trying to get an iterable version of the s! macro into itertools. I could imagine myself using for (a, b) in s![..n; 3, ..m; 17] quite a lot.

3 Likes