Search code examples
rustinteger-overflowunsigned-integer

How to loop over 0 .. v.len() - 1 when v might be empty?


What's the correct way to loop over 0 .. v.len() - 1 when the vector v might be empty?

Consider the following code, which prints all choices of 2 elements in a vector [5, 6, 7]:

fn main() {
  let v: Vec<i32> = vec![5, 6, 7];
  for i in 0 .. v.len() - 1 {
    for j in i + 1 .. v.len() {
      println!("{} {}", v[i], v[j]);
    }
  }
}

It prints

5 6
5 7
6 7

as expected.

Now, if we instead have

let v: Vec<i32> = vec![];

then the program panics due to overflow, which is also expected.

Q: How to fix it without changing the structure and the logic of the program, and without involving signed integers?

The following are some examples of the workarounds that I don't want:

  • Handling empty v separately (changes the logic). Overall, I don't want any checks that v is empty, e.g. by if v.len() == 0 { 0 } else { v.len() - 1 }

  • Changing the order of loops (changes the logic and the output):

    for j in 0 .. v.len() {
        for i in 0 .. j {
    
  • Shifting the indices (changes the logic):

    for i in 1 .. v.len() {
        for j in i .. v.len() {
            // Now i is shifted by 1
    
  • Using i32 (casts):

    for i in 0 .. v.len() as i32 - 1 {
        for j in i + 1 .. v.len() as i32 {
            // cast i and j to usize
    

Overall, I hope that there is a clean way to handle 0 .. v.len() - 1 without any hacks.


Solution

  • Notice that, if i == v.len()-1, nothing happens in the body of the outer loop, because i+1..v.len() is an empty iterator. Therefore, the simplest way to avoid integer underflows is simply to remove the -1:

    fn main() {
      let v: Vec<i32> = vec![5, 6, 7];
      for i in 0 .. v.len() {
        for j in i + 1 .. v.len() {
          println!("{} {}", v[i], v[j]);
        }
      }
    }
    

    produces

    5 6
    5 7
    6 7
    

    and if I replace v with Vec::new(), you get no output, as expected.