`for`-loops, pattern matching, and .windows(usize)

When I reach for .windows(), I always find that I want to use it like this:

let slice = ['l', 'O', 'R', 'e', 'm'];

for [a , b] in slice.windows(2) {
    if a.is_uppercase() && b.is_uppercase() {
        println!("Found consecutive capitals: {a}{b}");
    }
}

But this doesn't compile because:

error[E0005]: refutable pattern in `for` loop binding
 --> src/main.rs:4:9
  |
4 |     for [a , b] in slice.windows(2) {
  |         ^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not covered
  |
  = note: the matched value is of type `&[char]`

For more information about this error, try `rustc --explain E0005`.

I'm a bit confused by this error, since the docs for windows() specify that it's an iterator which always returns slices of length size. And, indeed, it will return nothing if slice is too short to give a single window.

I know this can be worked around using if let:

let slice = ['l', 'O', 'R', 'e', 'm'];

for window in slice.windows(2) {
    if let [a , b] = window && a.is_uppercase() && b.is_uppercase() {
            println!("Found consecutive capitals: {a}{b}");
        }
    }
}

But… why is the extra layer of pattern matching required? Isn't the for already assigning items from the slice into new scoped variables? Why isn't the pattern irrefutable in the for-of-windows case?

Anyway, if there's a better/more concise way to use .windows(), I'd love to hear it. Or maybe there's some language feature which will eventually make this easier to do?

unfortunately, the size is not represented at type level. the array_windows() does what you expect, but it is not stable yet.

EDIT:

the easy workaround is to manually convert the slice into array:

// panic if the slice length is incorrect
fn slice_to_array(s: &[char]) -> [char; 2] {
    s.try_into().unwarp()
}

for [a, b] in slice.windows(2).map(slice_to_array) {
    //....
}
3 Likes

windows() is documented to always return a specific length, but the compiler can't read documentation when performing pattern exhaustiveness checking. What you need is array_windows() (currently unstable).

5 Likes

While waiting for array_windows to stabilize, you could also use tuple_windows from the itertools crate.

6 Likes

Thanks all for your speedy and informative comments. Hoping stabilization is in the cards soon, but it's good to know I have options in the meantime.

Implement your own array_windows in the meantime:

fn array_windows<T: Copy, const LEN: usize>(slice: &[T]) -> impl Iterator<Item = [T; LEN]> {
    slice.windows(LEN).map(|window| window.try_into().unwrap())
}

fn main() {
    let slice = ['l', 'O', 'R', 'e', 'm'];

    for [a, b] in array_windows(&slice) {
        if a.is_uppercase() && b.is_uppercase() {
            println!("Found consecutive capitals: {a}{b}");
        }
    }
}
3 Likes

This is really slick, thanks. I would not have expected the compiler to be able to infer that LEN generic.

1 Like

While let is another option.

    let slice = ['l', 'O', 'R', 'e', 'm'];

    let mut iter = slice.windows(2);
    while let Some([a, b]) = iter.next(){
        if a.is_uppercase() && b.is_uppercase() {
            println!("Found consecutive capitals: {a}{b}");
        }
    }
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.