jddxf
jddxf

Reputation: 1990

How to avoid underflow pitfalls when iterating manually?

The code below would panic because j is typed as usize.

fn foo(vec: &Vec<i32>) {
    let mut i = 0;
    let mut j = vec.len() - 1;
    while i < j {
        while i < j && !some_condition(vec[i]) {
            i += 1;
        }
        while i < j && !some_condition(vec[j]) {
            j -= 1;
        }
        if i < j {
            vec.swap(i, j);
            i += 1;
            j -= 1;
        }
    }
}

foo(&vec![]);

Most of the time iterator helps to avoid such issues. But when we have to manually iterate with indexes we really need to be careful. I could check the size of vec ahead to avoid panic. But the real problem here is I tend to think the program would work as expected even without empty check until a corner case bites me. So I wonder if there is any idiomatic way to do this kind of things in Rust.

Upvotes: 1

Views: 712

Answers (2)

jollyproger
jollyproger

Reputation: 43

You could look at saturating_sub or checked_sub:

fn foo(vec: &Vec<i32>) {
    let mut i = 0;
    let mut j = vec.len().saturating_sub(1); 
    while i < j {
        // Some other logic
        i += 1;
        // EITHER
        j.saturating_sub(1); // avoid getting below 0
        //OR
        j = j.checked_sub(1).expect("usize underflow");
    }
}

foo(&vec![]);

If you want some more complex handling instead of .expect() you can use:

j = j.checked_sub(1).ok_or_else(|| some_your_error_creator())?;

which seems more idiomatic, but will require some additional work with error-handling.

Upvotes: 4

vallentin
vallentin

Reputation: 26235

Personally, I'd just do as you suggest vec.is_empty() and then return, to keep the logic clear.

If you don't want that, then instead you can perform a saturating subtraction using saturating_sub(). If the subtraction would result in an underflow then it would remain at 0.

// let mut j = vec.len() - 1;
let mut j = vec.len().saturating_sub(1);

Upvotes: 3

Related Questions