Slices

#140 May 2026

140. slice::as_array — Lock a Slice Into a Fixed-Size Array Reference

A function hands you &[u8] but the next step wants &[u8; 32]. The old answer was <&[u8; 32]>::try_from(slice) — a turbofish-and-trait dance for what is really just a length check. Rust 1.93 stabilises slice::as_array, the method-call version that does exactly that.

The pain: TryFrom for what should be a method

Cryptography APIs, parsers, and FFI all funnel data through &[T], but consumers usually want a concrete &[T; N] so they can index without bounds checks or pattern-match the fields out. The canonical conversion looks like this:

1
2
3
4
5
6
7
8
9
use std::convert::TryFrom;

fn fingerprint(digest: &[u8]) -> Result<&[u8; 32], &'static str> {
    <&[u8; 32]>::try_from(digest).map_err(|_| "expected 32 bytes")
}

let bytes = [0u8; 32];
assert!(fingerprint(&bytes[..]).is_ok());
assert!(fingerprint(&bytes[..16]).is_err());

It works, but every time you have to remember to write <&[u8; 32]>::try_from(...) with the reference inside the turbofish — write <[u8; 32]>::try_from instead and you’ll get an owned array (and a T: Clone bound) that you didn’t ask for. The error message when a coworker gets the form wrong is its own little adventure.

The fix: a method on [T] that returns Option<&[T; N]>

<[T]>::as_array::<N> is a plain method call. The length check is the same — the whole slice must be exactly N long — but the call site reads like every other slice method:

1
2
3
4
5
6
7
let bytes: &[u8] = &[0u8; 32];

let arr: Option<&[u8; 32]> = bytes.as_array();
assert!(arr.is_some());

let short: &[u8] = &bytes[..16];
assert!(short.as_array::<32>().is_none());

No TryFrom import, no angle-brackets-around-a-reference. The turbofish goes on the method, where it belongs, and the return type is Option — which is what you wanted anyway when the conversion can fail.

as_mut_array for the writeable side

The same trick works through &mut [T]. Useful when you’ve been handed a sub-slice of a buffer and want to write a fixed-size record into it without indexing each field:

1
2
3
4
5
let mut buf = [0u8; 16];
let header: &mut [u8; 4] = (&mut buf[..4]).as_mut_array().unwrap();
*header = *b"RIFF";

assert_eq!(&buf[..4], b"RIFF");

as_array and as_mut_array mirror <&[T; N]>::try_from / <&mut [T; N]>::try_from exactly — same semantics, fewer keystrokes, better error type.

Pair it with split_first_chunk for parsers

If you only need the first N bytes (and want to keep the rest), reach for split_first_chunk::<N> from the existing family. Use as_array when the slice is supposed to be exactly the right length — return values from a digest() call, decoded base64 of known size, FFI buffers carved to spec:

1
2
3
4
5
6
fn parse_ipv4(bytes: &[u8]) -> Option<[u8; 4]> {
    bytes.as_array::<4>().copied()
}

assert_eq!(parse_ipv4(&[192, 168, 1, 1]), Some([192, 168, 1, 1]));
assert_eq!(parse_ipv4(&[10, 0, 0]), None);

.copied() turns &[u8; 4] into [u8; 4] when you want an owned array — works because [u8; 4]: Copy.

When to reach for it

Anywhere you currently write <&[T; N]>::try_from(slice).ok() or slice.try_into().ok() and have to annotate the type to steer the inference. as_array is shorter, the failure case is an Option instead of a Result<_, TryFromSliceError>, and the turbofish sits where every other generic slice method puts it. Small ergonomics, but you’ll write it ten times this week.

#133 May 2026

133. slice::rotate_left — Cycle Elements Through a Slice Without a Second Buffer

Need the first few elements to wrap around to the back? slice.rotate_left(n) cycles them through in place — no scratch Vec, no clever indexing, no borrow checker drama.

The Problem

Rotating a slice “the obvious way” means allocating a temporary, copying things twice, and being very careful about ranges:

1
2
3
4
5
6
7
8
9
fn rotate_left_manual(v: &mut Vec<i32>, n: usize) {
    let mut tmp: Vec<i32> = v[..n].to_vec();
    v.drain(..n);
    v.append(&mut tmp);
}

let mut data = vec![1, 2, 3, 4, 5];
rotate_left_manual(&mut data, 2);
assert_eq!(data, vec![3, 4, 5, 1, 2]);

It works, but it allocates and only runs on Vec. The moment you only have a &mut [T] — a window inside a larger buffer, say — to_vec/drain aren’t options at all.

After: rotate_left and rotate_right

Every slice already knows how to rotate itself in place:

1
2
3
4
5
6
7
let mut data = [1, 2, 3, 4, 5];
data.rotate_left(2);
assert_eq!(data, [3, 4, 5, 1, 2]);

let mut data = [1, 2, 3, 4, 5];
data.rotate_right(2);
assert_eq!(data, [4, 5, 1, 2, 3]);

rotate_left(n) moves the first n elements to the end; rotate_right(n) moves the last n to the front. Both run in O(len) with zero allocations, and they work on any &mut [T] — arrays, vector slices, sub-ranges of bigger buffers.

Where It Earns Its Keep

Round-robin scheduling: the first runner takes a turn, then moves to the back of the line.

1
2
3
4
5
6
7
8
let mut queue = ["alice", "bob", "carol", "dave"];

for _ in 0..queue.len() {
    println!("now serving: {}", queue[0]);
    queue.rotate_left(1);
}
// queue is back to its original order
assert_eq!(queue, ["alice", "bob", "carol", "dave"]);

Scrolling a fixed-size display buffer — drop the oldest row, leave a slot at the end for the newest:

1
2
3
4
let mut rows = [10, 20, 30, 40];
rows.rotate_left(1);
rows[rows.len() - 1] = 99;
assert_eq!(rows, [20, 30, 40, 99]);

And because it operates on &mut [T], you can rotate a window inside a larger buffer without splitting it:

1
2
3
let mut buf = [0, 1, 2, 3, 4, 5, 6, 7];
buf[2..6].rotate_left(1);
assert_eq!(buf, [0, 1, 3, 4, 5, 2, 6, 7]);

Anywhere you’d reach for “shift everything left and stick the front on the end,” rotate_left does it in one line with no allocation.

#128 May 2026

128. slice::copy_within — Shift Bytes In-Place Without Fighting the Borrow Checker

You want to copy buf[2..5] over buf[0..3] — same buffer, no allocation. Reach for copy_from_slice and the borrow checker says no. copy_within is the one-call answer.

The two-borrow trap

The obvious code can’t compile — buf would be borrowed mutably and immutably at the same time:

1
2
3
4
5
let mut buf = [1, 2, 3, 4, 5];

// error[E0502]: cannot borrow `buf` as immutable
//               while also borrowed as mutable
// buf[0..3].copy_from_slice(&buf[2..5]);

The usual workarounds are noisy. split_at_mut to carve the slice into two non-overlapping halves:

1
2
3
4
let mut buf = [1, 2, 3, 4, 5];
let (left, right) = buf.split_at_mut(2);
left[0..2].copy_from_slice(&right[0..2]);
assert_eq!(buf, [3, 4, 3, 4, 5]);

…which only works when source and destination land on opposite sides of the split. Otherwise you allocate a throwaway Vec just to break the borrow:

1
2
3
let mut buf = [1, 2, 3, 4, 5];
let tmp: Vec<_> = buf[2..5].to_vec();   // allocation just to copy
buf[0..3].copy_from_slice(&tmp);

copy_within is the primitive

<[T]>::copy_within(src, dest) copies a range of elements to a destination index inside the same slice — one call, no allocation, no split:

1
2
3
let mut buf = [1, 2, 3, 4, 5];
buf.copy_within(2..5, 0);
assert_eq!(buf, [3, 4, 5, 4, 5]);

It’s memmove semantics, so overlapping source and destination just work — the elements that get overwritten don’t matter, the surviving order does:

1
2
3
4
let mut buf = [1, 2, 3, 4, 5];
// shift everything one slot to the right
buf.copy_within(0..4, 1);
assert_eq!(buf, [1, 1, 2, 3, 4]);

Try writing that with split_at_mut — you can’t, the source and destination overlap.

A real shape: drop the first N from a Vec

Removing the first n elements without reallocating is copy_within plus a truncate:

1
2
3
4
5
6
7
8
let mut data: Vec<u8> = vec![10, 20, 30, 40, 50];
let n = 2;
let len = data.len();

data.copy_within(n..len, 0);
data.truncate(len - n);

assert_eq!(data, [30, 40, 50]);

Same allocation, same backing buffer — the values just shift down. Vec::drain(..n) reads cleaner for one-offs, but copy_within is what you want when you’re already holding &mut [T] and can’t reach for Vec methods (think ring buffers, fixed-size scratch arrays, no_std crates).

Constraints

T: Copy is required — the method does a memmove, it doesn’t run destructors or call Clone. Source and destination ranges must both fit inside the slice; otherwise it panics. The destination is a single index (where the copy starts), not a range — the length is taken from the source range.

When to reach for it

Any time you’d otherwise write split_at_mut just to satisfy the borrow checker, or allocate a temporary buffer to break a self-borrow. copy_within reads as what you actually meant: move these bytes over there, in place.

Stable since Rust 1.37. Works on [T], Vec<T>, and any DerefMut<Target = [T]>.

76. slice::as_chunks — Split Slices into Fixed-Size Arrays

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.

75. select_nth_unstable — Find the Kth Element Without Sorting

You’re sorting an entire Vec just to grab the median or the top 3 elements. select_nth_unstable does it in O(n) — no full sort required.

The classic approach: sort everything, then index:

1
2
3
4
let mut scores = vec![82, 45, 99, 67, 73, 91, 55];
scores.sort();
let median = scores[scores.len() / 2];
assert_eq!(median, 73);

That’s O(n log n) to answer an O(n) question. select_nth_unstable uses a partial-sort algorithm (quickselect) to put the element at a given index into its final sorted position — everything before it is smaller or equal, everything after is greater or equal:

1
2
3
4
let mut scores = vec![82, 45, 99, 67, 73, 91, 55];
let mid = scores.len() / 2;
scores.select_nth_unstable(mid);
assert_eq!(scores[mid], 73);

The method returns three mutable slices — elements below, the pivot element, and elements above — so you can work with each partition directly:

1
2
3
4
5
6
7
let mut data = vec![10, 80, 30, 90, 50, 70, 20];
let (lower, median, upper) = data.select_nth_unstable(3);
// lower contains 3 elements all <= *median
// upper contains 3 elements all >= *median
assert_eq!(*median, 50);
assert!(lower.iter().all(|&x| x <= 50));
assert!(upper.iter().all(|&x| x >= 50));

Need the top 3 scores without sorting the full list? Select the boundary, then sort only the small tail:

1
2
3
4
5
6
let mut scores = vec![82, 45, 99, 67, 73, 91, 55];
let k = scores.len() - 3;
scores.select_nth_unstable(k);
let top_3 = &mut scores[k..];
top_3.sort_unstable(); // sort only 3 elements, not all 7
assert_eq!(top_3, &[82, 91, 99]);

Custom ordering works too. Find the median by absolute value:

1
2
3
4
let mut vals = vec![-20, 5, -10, 15, -3, 8, 1];
let mid = vals.len() / 2;
vals.select_nth_unstable_by_key(mid, |v| v.abs());
assert_eq!(vals[mid].abs(), 8);

The “unstable” in the name means equal elements might be reordered (like sort_unstable) — it doesn’t mean the API is experimental. This is stable since Rust 1.49, available on any [T] where T: Ord.

#074 Apr 2026

74. slice::is_sorted — Ask the Slice if It's Already Sorted

You’ve written windows(2).all(|w| w[0] <= w[1]) one too many times. The is_sorted family of methods says what you actually mean — in one call.

Checking whether data is already in order used to mean rolling your own predicate:

1
2
3
4
5
let data = vec![1, 3, 5, 7, 9];

// The old way — correct but noisy:
let sorted = data.windows(2).all(|w| w[0] <= w[1]);
assert!(sorted);

It works, but you have to remember windows(2), get the comparison direction right, and hope the next reader recognizes the pattern.

Now there’s a method that does exactly this:

1
2
3
4
5
let data = vec![1, 3, 5, 7, 9];
assert!(data.is_sorted());

let messy = vec![1, 9, 3, 7, 5];
assert!(!messy.is_sorted());

Empty slices and single-element slices are considered sorted — no edge-case surprises:

1
2
3
let empty: Vec<i32> = vec![];
assert!(empty.is_sorted());
assert!(vec![42].is_sorted());

Need a custom comparator? is_sorted_by takes a closure over pairs of references and returns bool:

1
2
3
4
// Check if sorted by absolute value
let vals: Vec<i32> = vec![-1, 2, -3, 4];
let sorted_by_abs = vals.is_sorted_by(|a, b| a.abs() <= b.abs());
assert!(sorted_by_abs);

And is_sorted_by_key extracts a key first — perfect for structs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct Task {
    priority: u8,
    name: &'static str,
}

let tasks = vec![
    Task { priority: 1, name: "urgent" },
    Task { priority: 3, name: "normal" },
    Task { priority: 5, name: "backlog" },
];

assert!(tasks.is_sorted_by_key(|t| t.priority));

A practical use: skip sorting when the data is already ordered:

1
2
3
4
5
let mut data = vec![1, 2, 3, 4, 5];
if !data.is_sorted() {
    data.sort();
}
// Avoids the O(n log n) sort when data is already O(n)-verified sorted

Available on slices and by extension on Vec, arrays, and anything that derefs to [T]. Stabilized in Rust 1.82 — no crate needed.

#059 Apr 2026

59. split_first_chunk — Destructure Slices into Arrays

Parsing a header from a byte buffer? Extracting the first N elements of a slice? split_first_chunk hands you a fixed-size array and the remainder in one call — no manual indexing, no panics.

The Problem

You have a byte slice and need to pull out a fixed-size prefix — say a 4-byte magic number or a 2-byte length field. The manual approach is fragile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn parse_header(data: &[u8]) -> Option<([u8; 4], &[u8])> {
    if data.len() < 4 {
        return None;
    }
    let header: [u8; 4] = data[..4].try_into().unwrap();
    let rest = &data[4..];
    Some((header, rest))
}

fn main() {
    let packet = b"RUST is awesome";
    let (header, rest) = parse_header(packet).unwrap();
    assert_eq!(&header, b"RUST");
    assert_eq!(rest, b" is awesome");
}

That try_into().unwrap() is ugly, and if you get the index arithmetic wrong, you get a panic at runtime.

After: split_first_chunk

Stabilized in Rust 1.77, split_first_chunk splits a slice into a &[T; N] array reference and the remaining slice — returning None if the slice is too short:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn parse_header(data: &[u8]) -> Option<(&[u8; 4], &[u8])> {
    data.split_first_chunk::<4>()
}

fn main() {
    let packet = b"RUST is awesome";
    let (magic, rest) = parse_header(packet).unwrap();
    assert_eq!(magic, b"RUST");
    assert_eq!(rest, b" is awesome");

    // Too short — returns None instead of panicking
    let tiny = b"RS";
    assert!(tiny.split_first_chunk::<4>().is_none());
}

One method call. No manual slicing, no try_into, and the const generic N ensures the compiler knows the exact array size.

Chaining Chunks for Protocol Parsing

Real protocols have multiple fields. Chain split_first_chunk calls to peel them off one at a time:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn parse_packet(data: &[u8]) -> Option<([u8; 2], [u8; 4], &[u8])> {
    let (version, rest) = data.split_first_chunk::<2>()?;
    let (length, payload) = rest.split_first_chunk::<4>()?;
    Some((*version, *length, payload))
}

fn main() {
    let raw = b"\x01\x02\x00\x00\x00\x05hello";
    let (version, length, payload) = parse_packet(raw).unwrap();

    assert_eq!(version, [0x01, 0x02]);
    assert_eq!(length, [0x00, 0x00, 0x00, 0x05]);
    assert_eq!(payload, b"hello");
}

Each ? short-circuits if the remaining data is too short. No bounds checks scattered across your code.

From the Other End: split_last_chunk

Need to grab a suffix instead — like a trailing checksum? split_last_chunk mirrors the API from the back:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn strip_checksum(data: &[u8]) -> Option<(&[u8], &[u8; 2])> {
    data.split_last_chunk::<2>()
}

fn main() {
    let msg = b"payload\xAB\xCD";
    let (body, checksum) = strip_checksum(msg).unwrap();
    assert_eq!(body, b"payload");
    assert_eq!(checksum, &[0xAB, 0xCD]);

    let short = b"\x01";
    assert!(strip_checksum(short).is_none());
}

Same safety, same ergonomics — just peeling from the tail.

The Full Family

These methods come in mutable variants too, all stabilized in 1.77:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
fn main() {
    // Immutable — borrow array refs from a slice
    let data: &[u8] = &[1, 2, 3, 4, 5];
    let (head, tail) = data.split_first_chunk::<2>().unwrap();
    assert_eq!(head, &[1, 2]);
    assert_eq!(tail, &[3, 4, 5]);

    // split_last_chunk — from the back
    let (init, last) = data.split_last_chunk::<2>().unwrap();
    assert_eq!(init, &[1, 2, 3]);
    assert_eq!(last, &[4, 5]);

    // first_chunk / last_chunk — just the array, no remainder
    let first: &[u8; 3] = data.first_chunk::<3>().unwrap();
    assert_eq!(first, &[1, 2, 3]);

    let last: &[u8; 3] = data.last_chunk::<3>().unwrap();
    assert_eq!(last, &[3, 4, 5]);
}

Wherever you reach for &data[..N] and a try_into(), there’s probably a chunk method that does it better. Type-safe, bounds-checked, and zero-cost.

#053 Mar 2026

53. element_offset — Find an Element's Index by Reference

Ever had a reference to an element inside a slice but needed its index? Before Rust 1.94, you’d reach for .position() with value equality or resort to pointer math. Now there’s a cleaner way.

The problem

Imagine you’re scanning a slice and a helper function hands you back a reference to the element it found. You know the reference points somewhere inside your slice, but you need the index — not a value-based search.

1
2
3
fn first_long_word<'a>(words: &'a [&str]) -> Option<&'a &'a str> {
    words.iter().find(|w| w.len() > 5)
}

You could call .position() with value comparison, but that re-scans the slice and compares by value — which is wasteful when you already hold the exact reference.

The solution: element_offset

<[T]>::element_offset takes a reference to an element and returns its Option<usize> index by comparing pointers, not values. If the reference points into the slice, you get Some(index). If it doesn’t, you get None.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    let words = ["hi", "hello", "rustacean", "world"];

    // A helper hands us a reference into the slice
    let found: &&str = words.iter().find(|w| w.len() > 5).unwrap();

    // Get the index by reference identity — no value scan needed
    let index = words.element_offset(found).unwrap();

    assert_eq!(index, 2);
    assert_eq!(words[index], "rustacean");

    println!("Found '{}' at index {}", found, index);
}

Why not .position()?

.position() compares by value and has to walk the slice from the start. element_offset is an O(1) pointer comparison — it checks whether your reference falls within the slice’s memory range and computes the offset directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let values = [10, 20, 10, 30];

    let third = &values[2]; // points at the second '10'

    // position() finds the FIRST 10 (index 0) — wrong!
    let by_value = values.iter().position(|v| v == third);
    assert_eq!(by_value, Some(0));

    // element_offset() finds THIS exact element (index 2) — correct!
    let by_ref = values.element_offset(third);
    assert_eq!(by_ref, Some(2));

    println!("By value: {:?}, By reference: {:?}", by_value, by_ref);
}

This distinction matters whenever your slice has duplicate values.

When the reference is outside the slice

If the reference doesn’t point into the slice, you get None:

1
2
3
4
5
6
7
8
fn main() {
    let a = [1, 2, 3];
    let outside = &42;

    assert_eq!(a.element_offset(outside), None);

    println!("Outside reference: {:?}", a.element_offset(outside));
}

Clean, safe, and no unsafe pointer arithmetic required. Available since Rust 1.94.0.

50. slice::chunk_by — Group Consecutive Elements

Need to split a slice into groups of consecutive elements that share a property? chunk_by does exactly that — no allocations, no manual index tracking.

The problem

Imagine you have a sorted list of temperatures and want to group them into runs of non-decreasing values. Without chunk_by, you’d write a loop tracking where each group starts and ends:

1
2
let temps = [18, 20, 22, 19, 21, 25, 24];
// Manual grouping... indices, slicing, off-by-one bugs 😬

Enter chunk_by

Stabilized in Rust 1.77, slice::chunk_by splits a slice between consecutive elements where the predicate returns false. Each chunk is a sub-slice where every adjacent pair satisfies the predicate:

1
2
3
4
5
6
7
8
9
let temps = [18, 20, 22, 19, 21, 25, 24];

let runs: Vec<&[i32]> = temps.chunk_by(|a, b| a <= b).collect();

assert_eq!(runs, vec![
    &[18, 20, 22] as &[i32],
    &[19, 21, 25],
    &[24],
]);

The predicate |a, b| a <= b keeps elements in the same chunk as long as values are non-decreasing. The moment a value drops, a new chunk begins.

Group by equality

A common use case is grouping runs of equal elements:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let data = [1, 1, 2, 3, 3, 3, 2, 2];

let groups: Vec<&[i32]> = data.chunk_by(|a, b| a == b).collect();

assert_eq!(groups, vec![
    &[1, 1] as &[i32],
    &[2],
    &[3, 3, 3],
    &[2, 2],
]);

Notice this groups consecutive equal elements — it’s not the same as a GROUP BY in SQL. The two runs of 2 stay separate because they aren’t adjacent.

Mutable chunks

There’s also chunk_by_mut if you need to modify elements within each group:

1
2
3
4
5
6
7
8
let mut data = [1, 1, 2, 3, 3, 3, 2, 2];

for chunk in data.chunk_by_mut(|a, b| a == b) {
    // Double the first element in each run
    chunk[0] *= 2;
}

assert_eq!(data, [2, 1, 4, 6, 3, 3, 4, 2]);

Key details

  • Zero-cost: returns sub-slices of the original data — no allocations
  • Predicate sees pairs: |a, b| receives each consecutive pair; a new chunk starts where it returns false
  • Works on any slice: &[T], &mut [T], Vec<T> (via deref)
  • Stable since Rust 1.77

Next time you reach for a manual loop to group consecutive elements, try chunk_by instead.

34. array_windows

Need to look at consecutive pairs (or triples) in a slice? Stop manually indexing — array_windows gives you fixed-size windows as arrays, not slices.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let temps = [18.0, 21.5, 19.0, 23.0, 22.5];

// Before: manual indexing 😬
for i in 0..temps.len() - 1 {
    let diff = temps[i + 1] - temps[i];
    println!({diff:+.1}");
}

// After: array_windows ✨
for [prev, next] in temps.array_windows() {
    let diff = next - prev;
    println!({diff:+.1}");
}

Stabilized in Rust 1.94, array_windows works like .windows(n) but the window size is a const generic — so you get &[T; N] instead of &[T]. That means you can destructure directly in the pattern.

It’s great for detecting trends, computing deltas, or validating sequences:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let readings = [3, 7, 2, 9, 1, 8];

let all_increasing = readings
    .array_windows()
    .all(|[a, b]| b > a);

assert!(!all_increasing);

// Works with triples too
let has_valley = readings
    .array_windows()
    .any(|[a, b, c]| b < a && b < c);

assert!(has_valley); // 2 is a valley between 7 and 9

No bounds checks, no .try_into().unwrap() dance. Just clean pattern matching on fixed-size windows.