Wenn du in einer Sequenz nach gleitenden Fenstern oder festen Blöcken suchen willst, sind windows und chunks die zwei Iterator-Adapter, die du brauchst. windows(n) liefert überlappende Fenster der Länge n — perfekt für Pattern-Suche, Bigram-Analyse, Sliding-Average. chunks(n) liefert disjunkte Blöcke — ideal für Block-weise Verarbeitung wie SIMD-Vektorisierung, Bild-Tiles, Audio-Frames. Dazu kommt chunks_exact für garantierte Block-Längen und chunks_mut für In-Place-Verarbeitung. Dieser Artikel zeigt alle Varianten mit konkreten Praxis-Beispielen.

windows(n) — überlappende Fenster

windows(n) gibt einen Iterator über überlappende Fenster der Länge n zurück:

Rust windows
fn main() {
    let v = [1, 2, 3, 4, 5];
    for fenster in v.windows(3) {
        println!("{fenster:?}");
    }
    // [1, 2, 3]
    // [2, 3, 4]
    // [3, 4, 5]
}

Jedes Fenster ist ein &[T] der Länge n. Aufeinanderfolgende Fenster überlappen um n-1 Elemente. Bei einem Slice der Länge L gibt es L - n + 1 Fenster (oder 0, wenn L < n).

Eigenschaften

  • Längen-Garantie: jedes Fenster hat genau n Elemente.
  • Bei n == 0: Panic.
  • Bei Slice kürzer als n: leerer Iterator.
  • Read-only: gibt &[T] zurück, nicht &mut [T]. (Mutable Windows gäbe es nicht, weil sie überlappen.)

chunks(n) — disjunkte Blöcke

chunks(n) gibt einen Iterator über nicht-überlappende Blöcke der Länge n zurück. Der letzte Block kann kürzer sein, wenn die Gesamtlänge kein Vielfaches von n ist.

Rust chunks
fn main() {
    let v = [1, 2, 3, 4, 5, 6, 7];
    for block in v.chunks(3) {
        println!("{block:?}");
    }
    // [1, 2, 3]
    // [4, 5, 6]
    // [7]           ← letzter Block ist kürzer
}

Bei einem Slice der Länge L gibt es ceil(L / n) Blöcke. Jeder Block ist ein &[T], der erste bis vorletzte hat Länge n, der letzte kann zwischen 1 und n haben.

chunks_exact(n) — garantierte Blöcke

Wenn du garantiert Blöcke der Länge n willst (und Rest-Bytes separat behandeln möchtest), nutzt du chunks_exact:

Rust chunks_exact
fn main() {
    let v = [1, 2, 3, 4, 5, 6, 7];
    let chunks = v.chunks_exact(3);
    let rest = chunks.remainder();        // letzte 1 Element

    for block in chunks {
        println!("{block:?}");
    }
    println!("Rest: {rest:?}");
    // [1, 2, 3]
    // [4, 5, 6]
    // Rest: [7]
}

Vorteile gegenüber chunks:

  • Jeder iterierte Block hat exakt n Elemente — kein „letzter Block kürzer".
  • remainder() gibt die übrigen Elemente separat.
  • Compiler-freundlich: bei festen Block-Größen kann der Compiler die Loop auto-vektorisieren (SIMD).

chunks_exact ist die idiomatische Wahl für performance-kritische Block-Verarbeitung.

Mutable Varianten

chunks_mut

Rust chunks_mut
fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];
    for block in v.chunks_mut(2) {
        // block ist &mut [i32]
        for x in block.iter_mut() {
            *x *= 10;
        }
    }
    assert_eq!(v, vec![10, 20, 30, 40, 50, 60]);
}

chunks_exact_mut

Rust chunks_exact_mut
fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    let mut iter = v.chunks_exact_mut(2);
    for block in &mut iter {
        block[0] += 100;
        block[1] += 200;
    }
    let rest = iter.into_remainder();        // letzter Wert
    for x in rest {
        *x = 0;
    }
    assert_eq!(v, vec![101, 202, 103, 204, 0]);
}

Keine windows_mut

Es gibt bewusst kein windows_mut in der Stdlib — überlappende mutable Slices wären Aliasing-Konflikte. Wer ähnliches braucht: über Indices iterieren mit split_at_mut-Tricks oder spezialisierte Crates.

rchunks und rwindows

Beide Adapter haben rückwärts-Varianten — Iteration vom Ende her:

Rust rchunks
fn main() {
    let v = [1, 2, 3, 4, 5, 6, 7];
    for block in v.rchunks(3) {
        println!("{block:?}");
    }
    // [5, 6, 7]
    // [2, 3, 4]
    // [1]
    // (rückwärts gruppiert!)
}

Nützlich, wenn die natürliche Lese-Richtung das Ende ist (z. B. Hash-Vergleiche von hinten, Multi-Byte-Integer-Lesen).

Vergleich windows vs. chunks

windows(n)chunks(n)
Überlappungja, je n-1 Elementenein
Anzahl IterationenL - n + 1ceil(L / n)
Längen-Garantiejeder Block nletzter kann kürzer
Mutable Variantenein (Aliasing)ja (chunks_mut)
Typische AnwendungPattern-Suche, Sliding-AverageBlock-weise Verarbeitung
Edge Case bei kurzem Sliceleerer Iteratorletzter Block kürzer

Praxis: windows und chunks im echten Code

Pattern-Suche in Bytes (windows)

Rust Magic-Byte-Suche
pub fn finde_pattern(daten: &[u8], pattern: &[u8]) -> Option<usize> {
    daten.windows(pattern.len())
        .position(|fenster| fenster == pattern)
}

fn main() {
    let data = b"Hallo Welt mit Magic-PNG-Header: \x89PNG\r\n\x1a\n weiter";
    let png_magic = b"\x89PNG\r\n\x1a\n";
    assert_eq!(finde_pattern(data, png_magic), Some(33));
}

windows(n) plus position — klassische Pattern-Suche. Effizient und ohne externe Dependency.

Sliding-Window-Average (windows)

Rust Moving Average
pub fn moving_average(daten: &[f64], fenstergroesse: usize) -> Vec<f64> {
    if daten.len() < fenstergroesse { return Vec::new(); }
    daten.windows(fenstergroesse)
        .map(|w| w.iter().sum::<f64>() / fenstergroesse as f64)
        .collect()
}

fn main() {
    let werte = vec![1.0, 2.0, 3.0, 4.0, 5.0];
    let avg = moving_average(&werte, 3);
    assert_eq!(avg, vec![2.0, 3.0, 4.0]);   // (1+2+3)/3, (2+3+4)/3, (3+4+5)/3
}

Klassischer DSP-/Time-Series-Algorithmus. Drei Mittelwerte für vier Fenster aus fünf Eingangs-Werten.

Bigram-Analyse für Text (windows)

Rust Bigrams
use std::collections::HashMap;

pub fn bigram_haeufigkeiten(woerter: &[&str]) -> HashMap<(String, String), u32> {
    let mut counts = HashMap::new();
    for paar in woerter.windows(2) {
        let key = (paar[0].to_string(), paar[1].to_string());
        *counts.entry(key).or_insert(0) += 1;
    }
    counts
}

fn main() {
    let w = ["der", "schnelle", "braune", "fuchs", "der", "schnelle"];
    let bigrams = bigram_haeufigkeiten(&w);
    assert_eq!(bigrams.get(&("der".into(), "schnelle".into())), Some(&2));
}

RGB-Pixel-Verarbeitung (chunks_exact)

Rust Pixel-Iteration
pub fn invertieren_rgb(pixels: &mut [u8]) {
    for pixel in pixels.chunks_exact_mut(3) {
        pixel[0] = 255 - pixel[0];
        pixel[1] = 255 - pixel[1];
        pixel[2] = 255 - pixel[2];
    }
    // Falls Länge nicht durch 3 teilbar — Rest unverändert (Stand der iter).
}

fn main() {
    let mut bild = vec![100u8, 150, 200,  50, 100, 150];
    invertieren_rgb(&mut bild);
    assert_eq!(bild, vec![155, 105, 55,  205, 155, 105]);
}

chunks_exact_mut(3) iteriert in Drei-Byte-Schritten. Wegen der festen Block-Größe kann der Compiler den Code oft auto-vektorisieren.

Audio-Frame-Verarbeitung (chunks)

Rust Audio-Frames
pub fn frame_peaks(samples: &[i16], frame_size: usize) -> Vec<i16> {
    samples.chunks(frame_size)
        .map(|frame| frame.iter().map(|s| s.abs()).max().unwrap_or(0))
        .collect()
}

fn main() {
    let samples: Vec<i16> = (-5..=5).collect();
    let peaks = frame_peaks(&samples, 3);
    // Frames: [-5,-4,-3], [-2,-1,0], [1,2,3], [4,5]
    assert_eq!(peaks, vec![5, 2, 3, 5]);
}

Hash-Vergleich blockweise (chunks)

Rust Block-Compare
pub fn sind_blockgleich(a: &[u8], b: &[u8], block: usize) -> Option<usize> {
    // Gibt den Index des ersten Blocks zurück, der NICHT gleich ist
    for (i, (ba, bb)) in a.chunks(block).zip(b.chunks(block)).enumerate() {
        if ba != bb {
            return Some(i);
        }
    }
    None
}

chunks plus zip — paarweise Block-Vergleich.

Sliding-Maximum (windows)

Rust Max in Sliding Window
pub fn sliding_max(daten: &[i32], n: usize) -> Vec<i32> {
    daten.windows(n)
        .map(|w| *w.iter().max().unwrap())
        .collect()
}

fn main() {
    let v = vec![1, 3, 2, 5, 4, 1, 6];
    assert_eq!(sliding_max(&v, 3), vec![3, 5, 5, 5, 6]);
}

Triplet-Erkennung (windows + match)

Rust Triple-Pattern
pub fn finde_aufsteigend(daten: &[i32]) -> Option<usize> {
    for (i, fenster) in daten.windows(3).enumerate() {
        if let [a, b, c] = fenster {
            if a < b && b < c {
                return Some(i);
            }
        }
    }
    None
}

fn main() {
    let v = [5, 3, 1, 2, 4, 7, 6];
    assert_eq!(finde_aufsteigend(&v), Some(2));   // 1, 2, 4
}

Kombination aus windows(3) und Slice-Pattern-Matching — sehr lesbar.

Daten-Partitionierung in feste Größen (chunks)

Rust Batches
pub fn process_batches<T, F>(daten: &[T], batch_size: usize, mut handler: F)
where
    F: FnMut(&[T]),
{
    for batch in daten.chunks(batch_size) {
        handler(batch);
    }
}

fn main() {
    let user_ids: Vec<u64> = (1..=10).collect();
    process_batches(&user_ids, 3, |b| {
        println!("Batch: {b:?}");
    });
    // Batch: [1, 2, 3]
    // Batch: [4, 5, 6]
    // Batch: [7, 8, 9]
    // Batch: [10]
}

Klassisches Batch-Pattern für API-Calls, DB-Inserts, Network-Sends.

Vector-Pair-Operation (chunks_exact)

Rust Vec-Komponenten
pub fn skalar_produkt(a: &[f32], b: &[f32]) -> f32 {
    a.chunks_exact(4)
        .zip(b.chunks_exact(4))
        .map(|(ca, cb)| {
            ca[0]*cb[0] + ca[1]*cb[1] + ca[2]*cb[2] + ca[3]*cb[3]
        })
        .sum()
}

Mit chunks_exact(4) wird die innere Schleife vom LLVM-Optimizer typischerweise auf SIMD-Instruktionen abgebildet — automatischer Vector-Speedup.

Interessantes

windows(n) überlappt, chunks(n) nicht.

Das ist der zentrale Unterschied. windows für gleitende Pattern-Suche, chunks für disjunkte Block-Verarbeitung. Wer das durcheinander bringt, bekommt entweder zu viele oder zu wenige Iterationen.

chunks_exact ist Performance-freundlich.

Die feste Block-Größe macht es dem LLVM-Optimizer leicht, die Schleife zu vektorisieren (SIMD). In numerischem Code mit chunks_exact(4) oder chunks_exact(8) oft Faktor 2-4× schneller als chunks.

Es gibt kein windows_mut.

Überlappende mutable Slices würden die Aliasing-Regel brechen. Wer ähnliches braucht: über Indices iterieren mit split_at_mut-Tricks, oder die arrayvec/itertools-Crates für spezialisierte Adapter.

Bei kurzem Slice: windows liefert leere Iteration, chunks liefert einen kurzen Block.

[1, 2].windows(3) ergibt 0 Iterationen. [1, 2].chunks(3) ergibt 1 Iteration mit [1, 2]. Wer garantiert volle Blöcke braucht: chunks_exact plus remainder() für den Rest.

n == 0 panickt bei allen Varianten.

windows(0) und chunks(0) panicken zur Laufzeit. Bei dynamischem n vorher prüfen.

rchunks und rwindows für rückwärtige Iteration.

Iteration vom Slice-Ende her. Bei [1,2,3,4,5].rchunks(2) kommen [4,5], [2,3], [1]. Sehr nützlich bei Algorithmen, die natürlich am Ende beginnen.

chunks_exact-Iterator hat remainder().

Während des Iterierens ist der Rest noch verfügbar via iter.remainder(). Nach into_remainder() (verbrauchende Variante) bekommst du den Rest als &[T] für separate Behandlung.

windows kombiniert sich gut mit Slice-Patterns.

for win in daten.windows(3) { if let [a, b, c] = win { ... } } ist ein idiomatischer Weg, Triplets in einer Sequenz zu prüfen. Pattern-Matching plus Slice-Adapter — sehr elegant.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Slices & Views

Zur Übersicht