BSpirit
BSpirit

Reputation: 21

Arithmetic operation order issue with a range

I'm trying to learn Rust using the Rust book and the Exercism.io website. I have an issue with this specific exercise. The code is as follows:

pub fn series(_digits: &str, _len: usize) -> Vec<String> {
    (0.._digits.len() + 1 - _len)
        .map(|i| _digits[i..i + _len].to_string())
        .collect()
}

For example, series("12345", 3)should return a Vec containing ["123", "234", "345"].

Instead of (0.._digits.len() + 1 - _len), I experimented using (0.._digits.len() - _len + 1) instead, but in this case, the unit test "test_too_long" fails:

#[test]
#[ignore]
fn test_too_long() {
    let expected: Vec<String> = vec![];
    assert_eq!(series("92017", 6), expected);
}

I'm surprised because it looks like it's the same to me. Why did it fail?

Upvotes: 0

Views: 138

Answers (1)

trent
trent

Reputation: 27945

This happens because in debug mode, arithmetic operations that would overflow instead panic, and panicking causes tests to fail.

With the rearranged version (playground), in series("12345", 6), digits.len() - len + 1 becomes 5usize - 6usize + 1usize. The program doesn't even get to the + 1, because just 5usize - 6usize panics. (usize can't represent negative numbers, so subtracting 6 from 5 causes overflow.)

The error message contains a strong hint at the nature of the failure:

---- test_too_long stdout ----
thread 'test_too_long' panicked at 'attempt to subtract with overflow', src/lib.rs:2:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.

digits.len() + 1 - len works, however, because 6 is exactly one more than the length of the string, and so 5 + 1 - 6 can evaluate to zero without overflow. But if you change test_too_long to call series("12345", 7) instead, both versions panic. This seems like an oversight on the part of whoever wrote the test suite, especially considering that the instructions don't specify the expected behavior:

And if you ask for a 6-digit series from a 5-digit string, you deserve whatever you get.

For what it's worth, here's one way to make series return an empty vector for any len greater than the length of the input: (digits.len() + 1).saturating_sub(len) is like digits.len() + 1 - len, but if the result of the subtraction would be less than 0, it just returns 0.

Upvotes: 1

Related Questions