Decrementing mutable i by one after while loop

Hello

I have the following code:

fn is_operator(c: char) -> bool {
    let operators : [char; 4] = ['+', '-', '*', '/'];
    if operators.contains(&c) {
        true
    } else {
        false
    }
}


fn main() {
    
    let my_string : String = "7+7".to_string();
    
    for mut i in 0..my_string.len() {
        
        if !is_operator(my_string.chars().nth(i).unwrap()) {
            
            while !is_operator(my_string.chars().nth(i).unwrap()) && i < my_string.len() {
                i = i + 1;
            }
            
            //This is not getting executed
            i = i - 1;
            
        } else {
            println!("{}", my_string.chars().nth(i).unwrap());
        }
        
    }
}

Executing this code fails, because somewhere in my_string.chars().nth(i).unwrap() i is out of bounds (bigger than the length of my_string).

warning: value assigned to `i` is never read
  --> src/main.rs:26:13
   |
26 |             i = i - 1;
   |             ^
   |
   = note: `#[warn(unused_assignments)]` on by default
   = help: maybe it is overwritten before being read?

    Finished dev [unoptimized + debuginfo] target(s) in 0.83s
     Running `target/debug/playground`
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:19:32

This is because line 24 is not working correctly. I decrement the value of i by one after the while loop so it can't get out of bounds. But I get the warning i is never read and it seems it doesn't get decreased.

The solution for this is:

while !is_operator(my_string.chars().nth(i).unwrap()) && i < my_string.len() {
                if i + 1 < my_string.len() {
                    i = i + 1;
                } else {break;}
}

However, I'm very curious why my former code is not working?? In C++ this kind of code works, why not in Rust?

Rust playground.

Thanks!

It is getting executed, if I change your code to this:

            //This is not getting executed
            i = i - 1;
            println!("This is getting executed: {}", i);

Then the code runs and prints out:

This is getting executed: 0
+

The reason it seems like it's not getting executed is that every time the for loop does an iteration, a new i gets created. If you want only one i, you can declare it outside the for loop, but then you'll need to manage it yourself (Playground):

    let mut i = 0;
    while i < my_string.len() {
        
        if !is_operator(my_string.chars().nth(i).unwrap()) {
            
            while !is_operator(my_string.chars().nth(i).unwrap()) && i < my_string.len() {
               // if i + 1 < my_string.len() {
                    i = i + 1;
              //  } else {break;}
            }
            
            //This is not getting executed
            i = i - 1;
            println!("This is getting executed: {}", i);
            
        } else {
            println!("{}", my_string.chars().nth(i).unwrap());
        }
        
        i += 1;
    }
2 Likes

A Rust for loop is similar to using C++ ranges, for (auto i : iterable), which also won't let you advance/reverse the iterator manually.

1 Like

Possibly ancillary to your actual question, but this loop's logic seems a bit off. You create a range up to the len of your string and then select the nth item from an iterator of chars in the string. Why not just use the string.chars() iterator? This will never overflow the string's len.

if let Some(operator) = my_string.chars().find(|&c| is_operator(c)) { 
    println!("{}", operator) 
}
1 Like

Rust's loop like:

for mut i in 0..3 {
    i += 1;
}

are executed as if they were like this:

{
    let i = 0;
    i += 1;
}
{
    let i = 1;
    i += 1;
}
{
    let i = 2;
    i += 1;
}
2 Likes