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]
}

Im Beispiel siehst du das Gleitverhalten: das erste Fenster umfasst die ersten drei Elemente, das zweite verschiebt sich um eins (überlappt mit dem ersten in zwei Elementen), das dritte ebenso. Bei einem 5-elementigen Slice und Fenstergröße 3 ergeben sich genau 3 Fenster — allgemein L - n + 1.

Eigenschaften

windows hat ein paar Eigenheiten, die du kennen musst:

Längen-Garantie: jedes ausgegebene Fenster hat exakt n Elemente. Das ist anders als bei chunks, wo der letzte Block kürzer sein kann. Damit kannst du im Loop-Body bedenkenlos mit fester Länge arbeiten — Slice-Patterns wie [a, b, c] greifen verlässlich.

Bei n == 0: Panic. Eine Fenstergröße von Null macht keinen Sinn. Bei dynamischem n vorher prüfen.

Bei Slice kürzer als n: leerer Iterator. Das ist sicher (kein Panic), aber du musst den Fall bei der Schleifen-Logik beachten — windows auf einem zu kurzen Slice gibt einfach gar keine Iteration.

Read-only: windows gibt &[T] zurück, nicht &mut [T]. Es gibt kein windows_mut in der Stdlib — und das ist Absicht: zwei mutable Borrows auf überlappende Bereiche wären eine Aliasing-Verletzung. Wer überlappend mutieren will, muss mit Indizes arbeiten.

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
}

Während windows Überlappung erlaubt, sind die Blöcke von chunks disjunkt: zwischen ihnen gibt es keine Schnittmenge, jedes Element der Eingabe gehört zu genau einem Block. Damit eignet sich chunks für Block-weise Verarbeitung wie Bild-Tiles, Audio-Frames, Network-Pakete, Batch-Sends.

Der Haken: wenn die Gesamtlänge nicht durch n teilbar ist, ist der letzte Block kürzer. Bei einem 7-elementigen Slice und Block-Größe 3 gibt es zwei vollständige Blöcke (jeweils 3 Elemente) und einen Rest-Block mit 1 Element. Du musst im Loop-Body damit rechnen oder zu chunks_exact greifen, das diesen Rest separat behandelt.

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]
}

chunks_exact ist die Performance-bewusste Variante. Drei Eigenschaften zeichnen sie aus:

Jeder iterierte Block hat exakt n Elemente — kein Sonderfall für den letzten. Im Loop-Body kannst du sicher mit block[0], block[1], ..., block[n-1] arbeiten, ohne Längen-Prüfung.

remainder() gibt die nicht zu einem vollen Block gehörenden Elemente separat zurück. Du kannst sie nach der Schleife mit anderer Logik behandeln — etwa „verwerfen", „padden", „separat verarbeiten".

Compiler-freundlich: bei festen Block-Größen wie 4, 8 oder 16 erkennt der LLVM-Optimizer oft, dass die Schleife vektorisierbar ist, und generiert SIMD-Instruktionen. Damit läuft die Verarbeitung 2-4× schneller als bei chunks — ohne dass du selbst Vektor-Operationen schreiben musst.

Für performance-kritische Block-Verarbeitung in numerischem Code, Bild-/Audio-Pipelines und Krypto ist chunks_exact deshalb die idiomatische Wahl.

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));
}

Eine klassische Substring-/Sub-Slice-Suche. windows(pattern.len()) produziert alle Sub-Slices der passenden Länge, position(|w| w == pattern) findet die erste passende Stelle. Die Komplexität ist O(n*m) — für jeden möglichen Start-Index wird der Pattern vollständig verglichen.

Bei großen Texten gibt es effizientere Algorithmen (Boyer-Moore, Knuth-Morris-Pratt mit Pre-Computation), aber für die meisten Anwendungen ist diese naive Variante schnell genug und kommt ohne externe Crates aus. Die Stdlib hat auch &[u8]::contains_slice als spezialisierte Methode für den „existiert irgendwo?"-Fall.

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
}

Der gleitende Durchschnitt (Moving Average) ist eine der häufigsten Operationen in Time-Series-Analyse und DSP. Statt einen Wert als Punkt zu bewerten, bewertest du ihn im Kontext seiner Nachbarn — das glättet Schwankungen und macht Trends sichtbarer.

Die Implementation mit windows(n) ist sehr direkt: jedes Fenster wird summiert und durch die Fenstergröße geteilt. Die Ausgabelänge ist daten.len() - n + 1, weil das erste Fenster mit Index 0 startet und das letzte mit Index daten.len() - n. Für 5 Eingangs-Werte und Fenstergröße 3 ergeben sich also 3 Ausgangs-Werte. Bei Hot-Loops mit großen Fenstern wäre eine Inkrementelle Variante (Summe pflegen, alten Wert subtrahieren, neuen addieren) deutlich schneller; die windows-Variante ist O(n*window_size) statt O(n).

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]);
}

RGB-Bildverarbeitung: jede Drei-Byte-Sequenz ist ein Pixel (Rot, Grün, Blau). chunks_exact_mut(3) iteriert in solchen Pixel-Schritten und gibt pro Iteration ein mutable Sub-Slice der Länge 3 zurück. Die Invertierung pro Kanal ist 255 - wert — einfach, aber für Millionen Pixel-Operationen muss es schnell sein.

Wegen der festen Block-Größe 3 kann der Compiler die Schleife oft auto-vektorisieren. Bei modernen CPUs werden mehrere Pixel pro Maschinen-Instruktion verarbeitet, was die Performance um Faktor 4 oder mehr erhöht. Mit chunks_mut(3) (ohne exact) wäre das schwieriger, weil der Compiler den Sonderfall „letzter kürzerer Block" mit einplanen müsste.

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
}

Blockweiser Vergleich zweier Byte-Streams: nützlich, um den ersten unterschiedlichen Block zu finden, ohne den ganzen Stream zu vergleichen. Die chunks(block)-Iteratoren laufen synchron, zip paart sie elementweise, enumerate gibt den Block-Index dazu.

Anwendungen sind z. B. inkrementelle Datei-Vergleiche (welcher Block hat sich geändert?), Delta-Updates in Backup-Systemen, oder Diff-Algorithmen. Bei sehr großen Streams wäre eine Hash-basierte Variante (Rolling Hash pro Block) effizienter, aber die direkte Variante mit chunks + zip ist für viele Fälle ausreichend und sehr einfach zu lesen.

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
}

Eine sehr elegante Kombination von zwei Slice-Konzepten: windows(3) produziert alle Drei-Element-Fenster, und das innere if let [a, b, c] = fenster zerlegt jedes Fenster in seine drei Komponenten. Die Bedingung a < b && b < c prüft auf streng aufsteigende Triplets.

Diese Variante ist deutlich lesbarer als die Index-basierte Form (for i in 0..daten.len()-2 { if daten[i] < daten[i+1] && daten[i+1] < daten[i+2] { ... } }) und vermeidet Off-by-One-Fehler. Der enumerate gibt zusätzlich den Index zurück, sodass die Funktion die Start-Position des ersten passenden Triplets zurückgeben kann.

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

Rust Batches
pub fn process_batches_u64(
    daten: &[u64],
    batch_size: usize,
    mut handler: impl FnMut(&[u64]),
) {
    for batch in daten.chunks(batch_size) {
        handler(batch);
    }
}

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

Das Batch-Pattern ist überall dort wichtig, wo einzelne Operationen teuer sind und sich durch Bündelung amortisieren lassen — etwa Datenbank-Inserts (eine INSERT-Anweisung mit 1000 Rows ist schneller als 1000 einzelne), API-Calls (Bulk-Endpoints), Network-Sends (große Pakete statt vieler kleiner).

Die Funktion process_batches ist generisch über den Element-Typ und nimmt eine Callback-Closure. Der Aufrufer kann dann beliebige Logik pro Batch ausführen — etwa „sende diesen Batch an die API". chunks(batch_size) macht die Aufteilung; der letzte Batch ist möglicherweise kleiner. Wenn dein Endpoint nur volle Batches akzeptiert, würdest du stattdessen chunks_exact plus separate Behandlung des remainder nutzen.

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()
}

Skalarprodukt ist eine zentrale numerische Operation in Linearer Algebra, Machine Learning, Signal-Verarbeitung. Die Implementation mit chunks_exact(4) und manueller Aufsummierung pro 4-Tupel ist nicht zufällig: sie führt den LLVM-Optimizer zu Auto-Vektorisierung. Moderne CPUs haben SIMD-Register (SSE, AVX), die 4 oder 8 f32-Werte gleichzeitig multiplizieren können — was die Operation um Faktor 4 oder 8 beschleunigt.

Bei sehr großen Vektoren wäre eine Crate wie wide oder packed_simd für noch explizitere Kontrolle interessant. Aber für viele Fälle reicht die chunks_exact-basierte Hand-Optimierung, die ohne externe Dependencies auskommt und vom Compiler erkannt wird.

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