You’re calling .chunks(4) and immediately doing chunk.try_into().unwrap() to get an array. as_chunks gives you &[[T; N]] directly — a slice of properly typed arrays, plus the remainder.
The Problem
When you use .chunks(N), each chunk is a &[T] — a dynamically sized slice. The compiler doesn’t know its length, so you’re stuck converting manually:
1
2
3
4
5
6
7
8
9
10
11
| fn sum_pairs(data: &[i32]) -> Vec<i32> {
data.chunks(2)
.filter(|c| c.len() == 2) // skip incomplete last chunk
.map(|c| c[0] + c[1]) // runtime indexing, no guarantees
.collect()
}
fn main() {
let values = [1, 2, 3, 4, 5];
assert_eq!(sum_pairs(&values), vec![3, 7]);
}
|
That works, but the compiler can’t verify your index access at compile time. You’re also throwing away the last chunk if it’s incomplete, with no easy way to inspect it.
After: as_chunks
Stabilized in Rust 1.88, as_chunks splits a slice into a &[[T; N]] and a remainder &[T] in one call:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fn sum_pairs(data: &[i32]) -> Vec<i32> {
let (chunks, _remainder) = data.as_chunks::<2>();
chunks.iter().map(|[a, b]| a + b).collect()
}
fn main() {
let values = [1, 2, 3, 4, 5];
assert_eq!(sum_pairs(&values), vec![3, 7]);
// The remainder is available too
let (chunks, remainder) = values.as_chunks::<2>();
assert_eq!(chunks, &[[1, 2], [3, 4]]);
assert_eq!(remainder, &[5]);
}
|
Each chunk is &[i32; 2], so you can pattern-match [a, b] directly. The compiler knows the size — no bounds checks, no panics.
Processing Fixed-Width Records
Parsing binary data with fixed-width fields is where as_chunks shines. Imagine RGB pixel data packed as bytes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fn brighten(pixels: &mut [u8], amount: u8) {
let (chunks, _) = pixels.as_chunks_mut::<3>();
for [r, g, b] in chunks {
*r = r.saturating_add(amount);
*g = g.saturating_add(amount);
*b = b.saturating_add(amount);
}
}
fn main() {
let mut pixels = [100, 150, 200, 50, 60, 70, 255, 128, 0];
brighten(&mut pixels, 30);
assert_eq!(pixels, [130, 180, 230, 80, 90, 100, 255, 158, 30]);
}
|
No manual stride arithmetic. Each iteration gives you exactly 3 bytes, pattern-matched into r, g, b.
Don’t Lose the Remainder
Unlike chunks_exact() where you call .remainder() on the iterator after consuming it, as_chunks returns the remainder upfront:
1
2
3
4
5
6
7
8
9
10
11
12
13
| fn main() {
let data = [10, 20, 30, 40, 50, 60, 70];
let (fours, rest) = data.as_chunks::<4>();
assert_eq!(fours.len(), 1); // one complete chunk: [10, 20, 30, 40]
assert_eq!(fours[0], [10, 20, 30, 40]);
assert_eq!(rest, &[50, 60, 70]); // leftover elements
// as_rchunks starts from the right instead
let (rest, fours) = data.as_rchunks::<4>();
assert_eq!(rest, &[10, 20, 30]);
assert_eq!(fours[0], [40, 50, 60, 70]);
}
|
as_rchunks is the mirror — it aligns chunks to the end, putting the remainder at the front. Useful when your trailing data is the structured part (e.g., a checksum or footer).
The Full Family
All stabilized in Rust 1.88, these come in immutable and mutable variants:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| fn main() {
let data: &[u8] = &[1, 2, 3, 4, 5, 6, 7];
// as_chunks — align from left, remainder on right
let (chunks, rem) = data.as_chunks::<3>();
assert_eq!(chunks, &[[1, 2, 3], [4, 5, 6]]);
assert_eq!(rem, &[7]);
// as_rchunks — align from right, remainder on left
let (rem, chunks) = data.as_rchunks::<3>();
assert_eq!(rem, &[1]);
assert_eq!(chunks, &[[2, 3, 4], [5, 6, 7]]);
// Mutable versions: as_chunks_mut, as_rchunks_mut
let mut buf = [0u8; 6];
let (chunks, _) = buf.as_chunks_mut::<2>();
chunks[0] = [0xCA, 0xFE];
chunks[1] = [0xBA, 0xBE];
chunks[2] = [0xDE, 0xAD];
assert_eq!(buf, [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD]);
}
|
Whenever you’re reaching for .chunks(N) with a compile-time constant, as_chunks::<N>() gives you stronger types, better ergonomics, and the remainder without gymnastics.