windows(size)
Rust

How Rust’s .windows(size) Method Works

The .windows(size) method in Rust returns an iterator that yields overlapping slices, where each slice has a length defined by the size parameter.

Each window is a slice that starts one element after the previous window, and all windows are references into the original slice. This behavior contrasts with .chunks(size), which produces non-overlapping slices instead of overlapping ones.

The .windows(size) method is defined on the slice type &[T], but you can also call it on a vector Vec<T>. This works because Vec<T> implements Deref<Target = [T]>, allowing it to automatically dereference to a slice when needed. In other words, when you call a slice method on a vector, Rust automatically dereferences it to a slice &[T] through the Deref trait (Rust performs an automatic coercion that temporarily treats the vector as a slice).

Internally, .windows(size) uses pointer arithmetic. A slice is stored in memory as a pointer to its first element and its length. As the iterator advances, Rust shifts this pointer by one element at a time and creates a new sub-slice from the original slice or vector.

For example:

When you call .windows(3) on a vector nums = [10, 20, 30, 40, 50], Rust first takes [10, 20, 30] as the initial window. Then it moves the starting position forward by one element each time, producing the next windows [20, 30, 40] and [30, 40, 50]. Each window is an overlapping view of length 3 into the same nums vector. You can see this flow in the code below:

fn main() {

	let nums = [10, 20, 30, 40, 50];

	for window in nums.windows(3) {

			println!("{:?}", window);

		}

}

Output:

[10, 20, 30]
[20, 30, 40]
[30, 40, 50]

Visualizing the windows model layout

Let’s model the slice nums ( from our previous example) in memory using the diagram below as a starting point.

Index:  0    1    2    3    4
Value: [10] [20] [30] [40] [50]
Ptr:    ^

.windows(3) means: start the pointer at index 0, take 3 elements, yield a reference [10, 20, 30]. Then move the pointer forward by 1 and take another view. Here is a visual representation of that process.

Index:     0    1    2    3    4
Value:   [10] [20] [30] [40] [50]

Window 1: 10   20   30
Window 2:      20   30   40
Window 3:           30   40   50

Internal implementation

To further understand how .windows(size) is implemented under the hood, let’s look at a simplified version of its source code from the Rust standard library.

From the code below, the public .windows(size) method first checks that the window size isn’t zero, then returns a Windows iterator.

// The public method
pub fn windows(&self, size: usize) -> Windows<'_, T> {
    assert!(size != 0);
    Windows::new(self, NonZero::new(size).unwrap())
}

The Windows itself is a struct. It holds two pieces of data: a reference to the slice being iterated over (v), and the window size (size). The lifetime 'a ensures each window reference is tied to the lifetime of the original slice.

// The iterator struct
pub struct Windows<'a, T> {
    v: &'a [T],
    size: NonZero<usize>,
}

Finally, here’s a simplified version of how the window iterator itself is implemented. Each call to next() checks if there are enough elements left for a full window. If so, it creates a slice reference for the current window, then advances the internal slice pointer by one element (creating the overlap). If there aren’t enough elements remaining, it returns None to signal the iteration is complete.

// The Iterator implementation (simplified)
impl<'a, T> Iterator for Windows<'a, T> {
    type Item = &'a [T];
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.v.len() >= self.size.get() {
            let window = &self.v[..self.size.get()];
            self.v = &self.v[1..];  // Advance by one element
            Some(window)
        } else {
            None
        }
    }
}

Counting the number of windows

If the slice has length len and the window size is n, the total number of windows is:

len - n + 1

For example, with [10, 20, 30, 40, 50] (length 5) and n = 3:

5 - 3 + 1 = 3 windows

This matches what we saw earlier.

What happens when the size Is too large or zero

Two special cases are worth acknowledging:

1. If size > len, you get no windows:

let v = [1, 2];

assert_eq!(v.windows(3).count(), 0); // .count() is syntatic sugar for v.len() 

Because there’s not enough data to fill one full window.

2. If size == 0, Rust panics:

let v = [1, 2, 3];

v.windows(0); // panic!

A zero-length window doesn’t make logical sense, and it’s disallowed to avoid edge cases.

Verifying if a vector is sorted

This example will demonstrate how .windows work in practice. We’ll also show how using .windows() compares to manual indexing.

The code below has a vector of i32 and a function is_sorted (which is not implemented yet). The goal is to check if the vector passed to the is_sorted function is actually sorted in ascending order. If it’s sorted, we’ll return true, otherwise, we’ll return false.

fn main() {

	let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
	
	let result = is_sorted(v);
	
	println!("{}", result);

}

pub fn is_sorted(v: Vec<i32>) -> bool {

	// We’ll fill this in.
	
	true

}

We’ll use two different approaches. The first and most common approach is indexing.

Using vector indexing to verify if a vector is sorted

The code below checks every adjacent pair of the elements in the vector: v[i] and v[i + 1].

If a previous element is greater than the next one, the list isn’t sorted.

pub fn is_sorted(v: Vec<i32>) -> bool {

		for i in 0..v.len() - 1 {
		
				if v[i] > v[i + 1] {
			
						return false;

				}

		}

		true

}

Let’s trace how it works:

v = [1, 2, 3, 4]
     ^  ^
     |  |
     i  i+1

Comparisons:
  1 < 2
  2 < 3
  3 < 4

Each iteration compares the current element with the next one.

But that’s exactly what .windows(2) gives us.

Using .windows(size) to verify if a vector is sorted

Now let’s rewrite the same logic using .windows(size):

pub fn is_sorted(v: Vec<i32>) -> bool {

    for window in v.windows(2){
        if window[0] > window[1]{
            return false
        }
    }
    
    true
} 

Let’s visualize what it produces for [1, 2, 3, 4]:

Window 1: [1, 2]

Window 2: [2, 3]

Window 3: [3, 4]

For each window, window[0] is the current element and window[1] is the next one. That means for Window 1: [1, 2], window[0] would be 1 while window[1] would be 2

We check that each window[0] <= window[1]. If all comparisons hold, the list is sorted.

.windows() vs .chunks()

.chunks(2) divides the slice into non-overlapping parts.

.windows(2) slides the view by one each time.

let v = [1, 2, 3, 4];
println!("{:?}", v.chunks(2).collect::<Vec<_>>());

println!("{:?}", v.windows(2).collect::<Vec<_>>());

Output:

[[1, 2], [3, 4]]

[[1, 2], [2, 3], [3, 4]]

Exercise

To solidify your understanding of .windows implement your own version of it. You can call it my_windows. You can’t name it windows since that would conflict with Rust’s standard library implementation.

Conclusion

The .windows(size) method is a clean way to work with overlapping parts of a slice or vector in Rust. It avoids manual indexing and makes many algorithms easier to express, such as checking if a list is sorted or finding patterns in a sequence. Under the hood, it works through pointer arithmetic, shifting the slice’s start position one element at a time to create new views into the same data.

Hi, welcome to my blog. I am Software Engineer and Technical Writer. And in this blog, I focus on sharing my views on the tech and tools I use. If you love my content and wish to stay in the loop, then by all means share this page and bookmark this website.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.